').appendTo(loading).kendoProgressBar({
type: 'chunk',
chunkCount: 10,
min: 0,
max: 1,
value: 0
}).data('kendoProgressBar');
deferred.progress(function (e) {
pb.value(e.progress);
}).always(function () {
kendo.destroy(loading);
loading.remove();
});
},
_workbookPdfExport: function (e) {
if (this.trigger('pdfExport', e)) {
e.preventDefault();
}
},
_bindWorkbookEvents: function () {
this._workbook.bind('change', this._workbookChange.bind(this));
this._workbook.bind('excelExport', this._workbookExcelExport.bind(this));
this._workbook.bind('excelImport', this._workbookExcelImport.bind(this));
this._workbook.bind('pdfExport', this._workbookPdfExport.bind(this));
},
destroy: function () {
kendo.ui.Widget.fn.destroy.call(this);
this._workbook.destroy();
this._controller.destroy();
this._view.destroy();
if (this._resizeHandler) {
$(window).off('resize' + NS, this._resizeHandler);
}
},
options: {
name: 'Spreadsheet',
toolbar: true,
sheetsbar: true,
rows: 200,
columns: 50,
rowHeight: 20,
columnWidth: 64,
headerHeight: 20,
headerWidth: 32,
excel: {
proxyURL: '',
fileName: 'Workbook.xlsx'
},
pdf: {
area: 'workbook',
fileName: 'Workbook.pdf',
proxyURL: '',
paperSize: 'a4',
landscape: true,
margin: null,
title: null,
author: null,
subject: null,
keywords: null,
creator: 'Kendo UI PDF Generator v.' + kendo.version,
date: null
}
},
events: [
'pdfExport',
'excelExport',
'excelImport',
'change',
'render'
]
});
kendo.spreadsheet.ALL_REASONS = ALL_REASONS;
kendo.ui.plugin(Spreadsheet);
$.extend(true, Spreadsheet, { classNames: classNames });
}(window.kendo));
}, typeof define == 'function' && define.amd ? define : function (a1, a2, a3) {
(a3 || a2)();
}));
(function (f, define) {
define('kendo.pivot.configurator', ['kendo.dom'], f);
}(function () {
var __meta__ = {
id: 'pivot.configurator',
name: 'PivotConfigurator',
category: 'web',
depends: [
'dropdownlist',
'treeview',
'pivot.fieldmenu'
],
hidden: true
};
(function ($, undefined) {
var kendo = window.kendo, ui = kendo.ui, Widget = ui.Widget, ns = '.kendoPivotConfigurator', HOVEREVENTS = 'mouseenter' + ns + ' mouseleave' + ns, SETTING_CONTAINER_TEMPLATE = kendo.template('
${name}
' + '
');
function addKPI(data) {
var found;
var idx = 0;
var length = data.length;
for (; idx < length; idx++) {
if (data[idx].type == 2) {
found = true;
break;
}
}
if (found) {
data.splice(idx + 1, 0, {
caption: 'KPIs',
defaultHierarchy: '[KPIs]',
name: 'KPIs',
uniqueName: '[KPIs]'
});
}
}
function kpiNode(node) {
return {
name: node.uniqueName,
type: node.type
};
}
function normalizeKPIs(data) {
for (var idx = 0, length = data.length; idx < length; idx++) {
data[idx].uniqueName = data[idx].name;
data[idx].type = 'kpi';
}
return data;
}
function settingTargetFromNode(node) {
var target = $(node).closest('.k-pivot-setting');
if (target.length) {
return target.data('kendoPivotSettingTarget');
}
return null;
}
var PivotConfigurator = Widget.extend({
init: function (element, options) {
Widget.fn.init.call(this, element, options);
this.element.addClass('k-widget k-fieldselector k-alt k-edit-form-container');
this._dataSource();
this._layout();
this.refresh();
kendo.notify(this);
},
events: [],
options: {
name: 'PivotConfigurator',
filterable: false,
sortable: false,
messages: {
measures: 'Drop Data Fields Here',
columns: 'Drop Column Fields Here',
rows: 'Drop Rows Fields Here',
measuresLabel: 'Measures',
columnsLabel: 'Columns',
rowsLabel: 'Rows',
fieldsLabel: 'Fields'
}
},
_dataSource: function () {
var that = this;
if (that.dataSource && that._refreshHandler) {
that.dataSource.unbind('change', that._refreshHandler).unbind('error', that._errorHandler).unbind('progress', that._progressHandler);
} else {
that._errorHandler = $.proxy(that._error, that);
that._refreshHandler = $.proxy(that.refresh, that);
that._progressHandler = $.proxy(that._requestStart, that);
}
that.dataSource = kendo.data.PivotDataSource.create(that.options.dataSource);
that.dataSource.bind('change', that._refreshHandler).bind('error', that._errorHandler).bind('progress', that._progressHandler);
},
setDataSource: function (dataSource) {
this.options.dataSource = dataSource;
this._dataSource();
if (this.measures) {
this.measures.setDataSource(dataSource);
}
if (this.rows) {
this.rows.setDataSource(dataSource);
}
if (this.columns) {
this.columns.setDataSource(dataSource);
}
this.refresh();
},
_treeViewDataSource: function () {
var that = this;
return kendo.data.HierarchicalDataSource.create({
schema: {
model: {
id: 'uniqueName',
hasChildren: function (item) {
return !('hierarchyUniqueName' in item) && !('aggregator' in item);
}
}
},
transport: {
read: function (options) {
var promise;
var node;
var kpi;
if ($.isEmptyObject(options.data)) {
promise = that.dataSource.schemaDimensions();
promise.done(function (data) {
if (!that.dataSource.cubeBuilder) {
addKPI(data);
}
options.success(data);
}).fail(options.error);
} else {
node = that.treeView.dataSource.get(options.data.uniqueName);
if (node.uniqueName === '[KPIs]') {
kpi = true;
promise = that.dataSource.schemaKPIs();
promise.done(function (data) {
options.success(normalizeKPIs(data));
}).fail(options.error);
} else if (node.type == 'kpi') {
kpi = true;
options.success(buildKPImeasures(node));
}
if (!kpi) {
if (node.type == 2) {
promise = that.dataSource.schemaMeasures();
} else if (node.dimensionUniqueName) {
promise = that.dataSource.schemaLevels(options.data.uniqueName);
} else {
promise = that.dataSource.schemaHierarchies(options.data.uniqueName);
}
promise.done(options.success).fail(options.error);
}
}
}
}
});
},
_progress: function (toggle) {
kendo.ui.progress(this.element, toggle);
},
_error: function () {
this._progress(false);
},
_requestStart: function () {
this._progress(true);
},
_layout: function () {
this.form = $('
').appendTo(this.element);
this._fields();
this._targets();
},
_fields: function () {
var container = $('
' + this.options.messages.fieldsLabel + '
').appendTo(this.form);
var template = '# if (item.type == 2 || item.uniqueName == "[KPIs]") { #' + '
' + '# } else if (item.type && item.type !== "kpi") { #' + '
' + '# } #' + '#: item.caption || item.name #';
this.treeView = $('
').appendTo(container).kendoTreeView({
template: template,
dataTextField: 'caption',
dragAndDrop: true,
autoBind: false,
dataSource: this._treeViewDataSource(),
dragstart: function (e) {
var dataItem = this.dataItem(e.sourceNode);
if (!dataItem.hasChildren && !dataItem.aggregator && !dataItem.measure || dataItem.type == 2 || dataItem.uniqueName === '[KPIs]') {
e.preventDefault();
}
},
drag: function (e) {
var status = 'k-denied';
var setting = settingTargetFromNode(e.dropTarget);
if (setting && setting.validate(this.dataItem(e.sourceNode))) {
status = 'k-add';
}
e.setStatusClass(status);
},
drop: function (e) {
e.preventDefault();
var setting = settingTargetFromNode(e.dropTarget);
var node = this.dataItem(e.sourceNode);
var idx, length, measures;
var name;
if (setting && setting.validate(node)) {
name = node.defaultHierarchy || node.uniqueName;
if (node.type === 'kpi') {
measures = buildKPImeasures(node);
length = measures.length;
name = [];
for (idx = 0; idx < length; idx++) {
name.push(kpiNode(measures[idx]));
}
} else if (node.kpi) {
name = [kpiNode(node)];
}
setting.add(name);
}
}
}).data('kendoTreeView');
},
_createTarget: function (element, options) {
var template = '';
return new kendo.ui.PivotSettingTarget(element, $.extend({
dataSource: this.dataSource,
hint: function (element) {
var wrapper = $('
');
wrapper.find('.k-list').append(element.clone());
return wrapper;
},
template: template,
emptyTemplate: '
${data}'
}, options));
},
_targets: function () {
var container = $('
').appendTo(this.form);
var columnsContainer = $(SETTING_CONTAINER_TEMPLATE({
name: this.options.messages.columnsLabel,
icon: 'k-i-vbars'
})).appendTo(container);
var columns = $('
').appendTo(columnsContainer.last());
var rowsContainer = $(SETTING_CONTAINER_TEMPLATE({
name: this.options.messages.rowsLabel,
icon: 'k-i-hbars'
})).appendTo(container);
var rows = $('
').appendTo(rowsContainer.last());
var measuresContainer = $(SETTING_CONTAINER_TEMPLATE({
name: this.options.messages.measuresLabel,
icon: 'k-i-sum'
})).appendTo(container);
var measures = $('
').appendTo(measuresContainer.last());
var options = this.options;
this.columns = this._createTarget(columns, {
filterable: options.filterable,
sortable: options.sortable,
connectWith: rows,
messages: {
empty: options.messages.columns,
fieldMenu: options.messages.fieldMenu
}
});
this.rows = this._createTarget(rows, {
filterable: options.filterable,
setting: 'rows',
connectWith: columns,
messages: {
empty: this.options.messages.rows,
fieldMenu: this.options.messages.fieldMenu
}
});
this.measures = this._createTarget(measures, {
setting: 'measures',
messages: { empty: options.messages.measures }
});
columns.add(rows).add(measures).on(HOVEREVENTS, '.k-item:not(.k-empty)', this._toggleHover);
},
_toggleHover: function (e) {
$(e.currentTarget).toggleClass('k-state-hover', e.type === 'mouseenter');
},
_resize: function () {
var element = this.element;
var height = this.options.height;
var border, fields;
if (!height) {
return;
}
element.height(height);
if (element.is(':visible')) {
fields = element.children('.k-columns').children('div.k-state-default');
height = element.innerHeight();
border = (element.outerHeight() - height) / 2;
height = height - (fields.outerHeight(true) - fields.height()) - border;
fields.height(height);
}
},
refresh: function () {
var dataSource = this.dataSource;
if (dataSource.cubeBuilder || this._cube !== dataSource.cube() || this._catalog !== dataSource.catalog()) {
this.treeView.dataSource.fetch();
}
this._catalog = this.dataSource.catalog();
this._cube = this.dataSource.cube();
this._resize();
this._progress(false);
},
destroy: function () {
Widget.fn.destroy.call(this);
this.dataSource.unbind('change', this._refreshHandler);
this.form.find('.k-list').off(ns);
this.rows.destroy();
this.columns.destroy();
this.measures.destroy();
this.treeView.destroy();
this.element = null;
this._refreshHandler = null;
}
});
function kpiMeasure(name, measure, type) {
return {
hierarchyUniqueName: name,
uniqueName: measure,
caption: measure,
measure: measure,
name: measure,
type: type,
kpi: true
};
}
function buildKPImeasures(node) {
var name = node.name;
return [
kpiMeasure(name, node.value, 'value'),
kpiMeasure(name, node.goal, 'goal'),
kpiMeasure(name, node.status, 'status'),
kpiMeasure(name, node.trend, 'trend')
];
}
ui.plugin(PivotConfigurator);
}(window.kendo.jQuery));
return window.kendo;
}, typeof define == 'function' && define.amd ? define : function (a1, a2, a3) {
(a3 || a2)();
}));
(function (f, define) {
define('kendo.angular', ['kendo.core'], f);
}(function () {
var __meta__ = {
id: 'angular',
name: 'AngularJS Directives',
category: 'framework',
description: 'Adds Kendo UI for AngularJS directives',
depends: ['core'],
defer: true
};
(function ($, angular, undefined) {
'use strict';
if (!angular || !angular.injector) {
return;
}
var module = angular.module('kendo.directives', []), $injector = angular.injector(['ng']), $parse = $injector.get('$parse'), $timeout = $injector.get('$timeout'), $defaultCompile, $log = $injector.get('$log');
function withoutTimeout(f) {
var save = $timeout;
try {
$timeout = function (f) {
return f();
};
return f();
} finally {
$timeout = save;
}
}
var OPTIONS_NOW;
var createDataSource = function () {
var types = {
TreeList: 'TreeListDataSource',
TreeView: 'HierarchicalDataSource',
Scheduler: 'SchedulerDataSource',
PanelBar: '$PLAIN',
Menu: '$PLAIN',
ContextMenu: '$PLAIN'
};
var toDataSource = function (dataSource, type) {
if (type == '$PLAIN') {
return dataSource;
}
return kendo.data[type].create(dataSource);
};
return function (scope, element, role, source) {
var type = types[role] || 'DataSource';
var current = scope.$eval(source);
var ds = toDataSource(current, type);
scope.$watch(source, function (mew) {
var widget = kendoWidgetInstance(element);
if (widget && typeof widget.setDataSource == 'function') {
if (mew !== current) {
var ds = toDataSource(mew, type);
widget.setDataSource(ds);
current = mew;
}
}
});
return ds;
};
}();
var ignoredAttributes = {
kDataSource: true,
kOptions: true,
kRebind: true,
kNgModel: true,
kNgDelay: true
};
var ignoredOwnProperties = {
name: true,
title: true,
style: true
};
function createWidget(scope, element, attrs, widget, origAttr, controllers) {
if (!(element instanceof jQuery)) {
throw new Error('The Kendo UI directives require jQuery to be available before AngularJS. Please include jquery before angular in the document.');
}
var kNgDelay = attrs.kNgDelay, delayValue = scope.$eval(kNgDelay);
controllers = controllers || [];
var ngModel = controllers[0], ngForm = controllers[1];
var ctor = $(element)[widget];
if (!ctor) {
window.console.error('Could not find: ' + widget);
return null;
}
var parsed = parseOptions(scope, element, attrs, widget, ctor);
var options = parsed.options;
if (parsed.unresolved.length) {
var promises = [];
for (var i = 0, len = parsed.unresolved.length; i < len; i++) {
var unresolved = parsed.unresolved[i];
var promise = $.Deferred(function (d) {
var unwatch = scope.$watch(unresolved.path, function (newValue) {
if (newValue !== undefined) {
unwatch();
d.resolve();
}
});
}).promise();
promises.push(promise);
}
$.when.apply(null, promises).then(createIt);
return;
}
if (kNgDelay && !delayValue) {
var root = scope.$root || scope;
var register = function () {
var unregister = scope.$watch(kNgDelay, function (newValue) {
if (newValue !== undefined) {
unregister();
element.removeAttr(attrs.$attr.kNgDelay);
kNgDelay = null;
$timeout(createIt);
}
});
};
if (/^\$(digest|apply)$/.test(root.$$phase)) {
register();
} else {
scope.$apply(register);
}
return;
} else {
return createIt();
}
function createIt() {
var originalElement;
if (attrs.kRebind) {
originalElement = $($(element)[0].cloneNode(true));
}
options = parseOptions(scope, element, attrs, widget, ctor).options;
if (element.is('select')) {
(function (options) {
if (options.length > 0) {
var first = $(options[0]);
if (!/\S/.test(first.text()) && /^\?/.test(first.val())) {
first.remove();
}
}
}(element[0].options));
}
var object = ctor.call(element, OPTIONS_NOW = options).data(widget);
exposeWidget(object, scope, attrs, widget, origAttr);
scope.$emit('kendoWidgetCreated', object);
var destroyRegister = destroyWidgetOnScopeDestroy(scope, object);
if (attrs.kRebind) {
setupRebind(object, scope, element, originalElement, attrs.kRebind, destroyRegister, attrs);
}
if (attrs.kNgDisabled) {
var kNgDisabled = attrs.kNgDisabled;
var isDisabled = scope.$eval(kNgDisabled);
if (isDisabled) {
object.enable(!isDisabled);
}
bindToKNgDisabled(object, scope, element, kNgDisabled);
}
if (attrs.kNgReadonly) {
var kNgReadonly = attrs.kNgReadonly;
var isReadonly = scope.$eval(kNgReadonly);
if (isReadonly) {
object.readonly(isReadonly);
}
bindToKNgReadonly(object, scope, element, kNgReadonly);
}
if (attrs.kNgModel) {
bindToKNgModel(object, scope, attrs.kNgModel);
}
if (ngModel) {
bindToNgModel(object, scope, element, ngModel, ngForm);
}
if (object) {
propagateClassToWidgetWrapper(object, element);
}
return object;
}
}
function parseOptions(scope, element, attrs, widget, ctor) {
var role = widget.replace(/^kendo/, '');
var unresolved = [];
var optionsPath = attrs.kOptions || attrs.options;
var optionsValue = scope.$eval(optionsPath);
if (optionsPath && optionsValue === undefined) {
unresolved.push({
option: 'options',
path: optionsPath
});
}
var options = angular.extend({}, attrs.defaultOptions, optionsValue);
function addOption(name, value) {
var scopeValue = angular.copy(scope.$eval(value));
if (scopeValue === undefined) {
unresolved.push({
option: name,
path: value
});
} else {
options[name] = scopeValue;
}
}
var widgetOptions = ctor.widget.prototype.options;
var widgetEvents = ctor.widget.prototype.events;
$.each(attrs, function (name, value) {
if (name === 'source' || name === 'kDataSource' || name === 'kScopeField' || name === 'scopeField') {
return;
}
var dataName = 'data' + name.charAt(0).toUpperCase() + name.slice(1);
if (name.indexOf('on') === 0) {
var eventKey = name.replace(/^on./, function (prefix) {
return prefix.charAt(2).toLowerCase();
});
if (widgetEvents.indexOf(eventKey) > -1) {
options[eventKey] = value;
}
}
if (widgetOptions.hasOwnProperty(dataName)) {
addOption(dataName, value);
} else if (widgetOptions.hasOwnProperty(name) && !ignoredOwnProperties[name]) {
addOption(name, value);
} else if (!ignoredAttributes[name]) {
var match = name.match(/^k(On)?([A-Z].*)/);
if (match) {
var optionName = match[2].charAt(0).toLowerCase() + match[2].slice(1);
if (match[1] && name != 'kOnLabel') {
options[optionName] = value;
} else {
if (name == 'kOnLabel') {
optionName = 'onLabel';
}
addOption(optionName, value);
}
}
}
});
var dataSource = attrs.kDataSource || attrs.source;
if (dataSource) {
options.dataSource = createDataSource(scope, element, role, dataSource);
}
options.$angular = [scope];
return {
options: options,
unresolved: unresolved
};
}
function bindToKNgDisabled(widget, scope, element, kNgDisabled) {
if (kendo.ui.PanelBar && widget instanceof kendo.ui.PanelBar || kendo.ui.Menu && widget instanceof kendo.ui.Menu) {
$log.warn('k-ng-disabled specified on a widget that does not have the enable() method: ' + widget.options.name);
return;
}
scope.$watch(kNgDisabled, function (newValue, oldValue) {
if (newValue != oldValue) {
widget.enable(!newValue);
}
});
}
function bindToKNgReadonly(widget, scope, element, kNgReadonly) {
if (typeof widget.readonly != 'function') {
$log.warn('k-ng-readonly specified on a widget that does not have the readonly() method: ' + widget.options.name);
return;
}
scope.$watch(kNgReadonly, function (newValue, oldValue) {
if (newValue != oldValue) {
widget.readonly(newValue);
}
});
}
function exposeWidget(widget, scope, attrs, kendoWidget, origAttr) {
if (attrs[origAttr]) {
var set = $parse(attrs[origAttr]).assign;
if (set) {
set(scope, widget);
} else {
throw new Error(origAttr + ' attribute used but expression in it is not assignable: ' + attrs[kendoWidget]);
}
}
}
function formValue(element) {
if (/checkbox|radio/i.test(element.attr('type'))) {
return element.prop('checked');
}
return element.val();
}
var formRegExp = /^(input|select|textarea)$/i;
function isForm(element) {
return formRegExp.test(element[0].tagName);
}
function bindToNgModel(widget, scope, element, ngModel, ngForm) {
if (!widget.value) {
return;
}
var value;
var haveChangeOnElement = false;
if (isForm(element)) {
value = function () {
return formValue(element);
};
} else {
value = function () {
return widget.value();
};
}
ngModel.$render = function () {
var val = ngModel.$viewValue;
if (val === undefined) {
val = ngModel.$modelValue;
}
if (val === undefined) {
val = null;
}
haveChangeOnElement = true;
setTimeout(function () {
haveChangeOnElement = false;
if (widget) {
var kNgModel = scope[widget.element.attr('k-ng-model')];
if (kNgModel) {
val = kNgModel;
}
if (widget.options.autoBind === false && !widget.listView.bound()) {
if (val) {
widget.value(val);
}
} else {
widget.value(val);
}
}
}, 0);
};
if (isForm(element)) {
element.on('change', function () {
haveChangeOnElement = true;
});
}
var onChange = function (pristine) {
return function () {
var formPristine;
if (haveChangeOnElement) {
return;
}
if (pristine && ngForm) {
formPristine = ngForm.$pristine;
}
ngModel.$setViewValue(value());
if (pristine) {
ngModel.$setPristine();
if (formPristine) {
ngForm.$setPristine();
}
}
digest(scope);
};
};
widget.first('change', onChange(false));
if (!(kendo.ui.AutoComplete && widget instanceof kendo.ui.AutoComplete)) {
widget.first('dataBound', onChange(true));
}
var currentVal = value();
if (!isNaN(ngModel.$viewValue) && currentVal != ngModel.$viewValue) {
if (!ngModel.$isEmpty(ngModel.$viewValue)) {
widget.value(ngModel.$viewValue);
} else if (currentVal != null && currentVal !== '' && currentVal != ngModel.$viewValue) {
ngModel.$setViewValue(currentVal);
}
}
ngModel.$setPristine();
}
function bindToKNgModel(widget, scope, kNgModel) {
if (typeof widget.value != 'function') {
$log.warn('k-ng-model specified on a widget that does not have the value() method: ' + widget.options.name);
return;
}
var form = $(widget.element).parents('form');
var ngForm = scope[form.attr('name')];
var getter = $parse(kNgModel);
var setter = getter.assign;
var updating = false;
var valueIsCollection = kendo.ui.MultiSelect && widget instanceof kendo.ui.MultiSelect;
var length = function (value) {
return valueIsCollection ? value.length : 0;
};
var currentValueLength = length(getter(scope));
widget.$angular_setLogicValue(getter(scope));
var watchHandler = function (newValue, oldValue) {
if (newValue === undefined) {
newValue = null;
}
if (updating || newValue == oldValue && length(newValue) == currentValueLength) {
return;
}
currentValueLength = length(newValue);
widget.$angular_setLogicValue(newValue);
};
if (valueIsCollection) {
scope.$watchCollection(kNgModel, watchHandler);
} else {
scope.$watch(kNgModel, watchHandler);
}
widget.first('change', function () {
updating = true;
if (ngForm && ngForm.$pristine) {
ngForm.$setDirty();
}
digest(scope, function () {
setter(scope, widget.$angular_getLogicValue());
currentValueLength = length(getter(scope));
});
updating = false;
});
}
function destroyWidgetOnScopeDestroy(scope, widget) {
var deregister = scope.$on('$destroy', function () {
deregister();
if (widget) {
kendo.destroy(widget.element);
widget = null;
}
});
return deregister;
}
function propagateClassToWidgetWrapper(widget, element) {
if (!(window.MutationObserver && widget.wrapper)) {
return;
}
var prevClassList = [].slice.call($(element)[0].classList);
var mo = new MutationObserver(function (changes) {
suspend();
if (!widget) {
return;
}
changes.forEach(function (chg) {
var w = $(widget.wrapper)[0];
switch (chg.attributeName) {
case 'class':
var currClassList = [].slice.call(chg.target.classList);
currClassList.forEach(function (cls) {
if (prevClassList.indexOf(cls) < 0) {
w.classList.add(cls);
if (kendo.ui.ComboBox && widget instanceof kendo.ui.ComboBox) {
widget.input[0].classList.add(cls);
}
}
});
prevClassList.forEach(function (cls) {
if (currClassList.indexOf(cls) < 0) {
w.classList.remove(cls);
if (kendo.ui.ComboBox && widget instanceof kendo.ui.ComboBox) {
widget.input[0].classList.remove(cls);
}
}
});
prevClassList = currClassList;
break;
case 'disabled':
if (typeof widget.enable == 'function' && !widget.element.attr('readonly')) {
widget.enable(!$(chg.target).attr('disabled'));
}
break;
case 'readonly':
if (typeof widget.readonly == 'function' && !widget.element.attr('disabled')) {
widget.readonly(!!$(chg.target).attr('readonly'));
}
break;
}
});
resume();
});
function suspend() {
mo.disconnect();
}
function resume() {
mo.observe($(element)[0], { attributes: true });
}
resume();
widget.first('destroy', suspend);
}
function setupRebind(widget, scope, element, originalElement, rebindAttr, destroyRegister, attrs) {
var unregister = scope.$watch(rebindAttr, function (newValue, oldValue) {
if (!widget._muteRebind && newValue !== oldValue) {
unregister();
if (attrs._cleanUp) {
attrs._cleanUp();
}
var templateOptions = WIDGET_TEMPLATE_OPTIONS[widget.options.name];
if (templateOptions) {
templateOptions.forEach(function (name) {
var templateContents = scope.$eval(attrs['k' + name]);
if (templateContents) {
originalElement.append($(templateContents).attr(kendo.toHyphens('k' + name), ''));
}
});
}
var _wrapper = $(widget.wrapper)[0];
var _element = $(widget.element)[0];
var isUpload = widget.options.name === 'Upload';
if (isUpload) {
element = $(_element);
}
var compile = element.injector().get('$compile');
widget._destroy();
if (destroyRegister) {
destroyRegister();
}
widget = null;
if (_element) {
if (_wrapper) {
_wrapper.parentNode.replaceChild(_element, _wrapper);
}
$(element).replaceWith(originalElement);
}
compile(originalElement)(scope);
}
}, true);
digest(scope);
}
function bind(f, obj) {
return function (a, b) {
return f.call(obj, a, b);
};
}
function setTemplate(key, value) {
this[key] = kendo.stringify(value);
}
module.factory('directiveFactory', [
'$compile',
function (compile) {
var kendoRenderedTimeout;
var RENDERED = false;
$defaultCompile = compile;
var create = function (role, origAttr) {
return {
restrict: 'AC',
require: [
'?ngModel',
'^?form'
],
scope: false,
controller: [
'$scope',
'$attrs',
'$element',
function ($scope, $attrs) {
this.template = bind(setTemplate, $attrs);
$attrs._cleanUp = bind(function () {
this.template = null;
$attrs._cleanUp = null;
}, this);
}
],
link: function (scope, element, attrs, controllers) {
var $element = $(element);
var roleattr = role.replace(/([A-Z])/g, '-$1');
$element.attr(roleattr, $element.attr('data-' + roleattr));
$element[0].removeAttribute('data-' + roleattr);
var widget = createWidget(scope, element, attrs, role, origAttr, controllers);
if (!widget) {
return;
}
if (kendoRenderedTimeout) {
clearTimeout(kendoRenderedTimeout);
}
kendoRenderedTimeout = setTimeout(function () {
scope.$emit('kendoRendered');
if (!RENDERED) {
RENDERED = true;
$('form').each(function () {
var form = $(this).controller('form');
if (form) {
form.$setPristine();
}
});
}
});
}
};
};
return { create: create };
}
]);
var TAGNAMES = {
Editor: 'textarea',
NumericTextBox: 'input',
DatePicker: 'input',
DateTimePicker: 'input',
TimePicker: 'input',
AutoComplete: 'input',
ColorPicker: 'input',
MaskedTextBox: 'input',
MultiSelect: 'input',
Upload: 'input',
Validator: 'form',
Button: 'button',
MobileButton: 'a',
MobileBackButton: 'a',
MobileDetailButton: 'a',
ListView: 'ul',
MobileListView: 'ul',
TreeView: 'ul',
Menu: 'ul',
ContextMenu: 'ul',
ActionSheet: 'ul'
};
var SKIP_SHORTCUTS = [
'MobileView',
'MobileDrawer',
'MobileLayout',
'MobileSplitView',
'MobilePane',
'MobileModalView'
];
var MANUAL_DIRECTIVES = [
'MobileApplication',
'MobileView',
'MobileModalView',
'MobileLayout',
'MobileActionSheet',
'MobileDrawer',
'MobileSplitView',
'MobilePane',
'MobileScrollView',
'MobilePopOver'
];
angular.forEach([
'MobileNavBar',
'MobileButton',
'MobileBackButton',
'MobileDetailButton',
'MobileTabStrip',
'MobileScrollView',
'MobileScroller'
], function (widget) {
MANUAL_DIRECTIVES.push(widget);
widget = 'kendo' + widget;
module.directive(widget, function () {
return {
restrict: 'A',
link: function (scope, element, attrs) {
createWidget(scope, element, attrs, widget, widget);
}
};
});
});
function createDirectives(klass, isMobile) {
function make(directiveName, widgetName) {
module.directive(directiveName, [
'directiveFactory',
function (directiveFactory) {
return directiveFactory.create(widgetName, directiveName);
}
]);
}
var name = isMobile ? 'Mobile' : '';
name += klass.fn.options.name;
var className = name;
var shortcut = 'kendo' + name.charAt(0) + name.substr(1).toLowerCase();
name = 'kendo' + name;
var dashed = name.replace(/([A-Z])/g, '-$1');
if (SKIP_SHORTCUTS.indexOf(name.replace('kendo', '')) == -1) {
var names = name === shortcut ? [name] : [
name,
shortcut
];
angular.forEach(names, function (directiveName) {
module.directive(directiveName, function () {
return {
restrict: 'E',
replace: true,
template: function (element, attributes) {
var tag = TAGNAMES[className] || 'div';
var scopeField = attributes.kScopeField || attributes.scopeField;
return '<' + tag + ' ' + dashed + (scopeField ? '="' + scopeField + '"' : '') + '>' + element.html() + '' + tag + '>';
}
};
});
});
}
if (MANUAL_DIRECTIVES.indexOf(name.replace('kendo', '')) > -1) {
return;
}
make(name, name);
if (shortcut != name) {
make(shortcut, name);
}
}
function kendoWidgetInstance(el) {
el = $(el);
return kendo.widgetInstance(el, kendo.ui) || kendo.widgetInstance(el, kendo.mobile.ui) || kendo.widgetInstance(el, kendo.dataviz.ui);
}
function digest(scope, func) {
var root = scope.$root || scope;
var isDigesting = /^\$(digest|apply)$/.test(root.$$phase);
if (func) {
if (isDigesting) {
func();
} else {
root.$apply(func);
}
} else if (!isDigesting) {
root.$digest();
}
}
function destroyScope(scope, el) {
scope.$destroy();
if (el) {
$(el).removeData('$scope').removeData('$$kendoScope').removeData('$isolateScope').removeData('$isolateScopeNoTemplate').removeClass('ng-scope');
}
}
var pendingPatches = [];
function defadvice(klass, methodName, func) {
if ($.isArray(klass)) {
return angular.forEach(klass, function (klass) {
defadvice(klass, methodName, func);
});
}
if (typeof klass == 'string') {
var a = klass.split('.');
var x = kendo;
while (x && a.length > 0) {
x = x[a.shift()];
}
if (!x) {
pendingPatches.push([
klass,
methodName,
func
]);
return false;
}
klass = x.prototype;
}
var origMethod = klass[methodName];
klass[methodName] = function () {
var self = this, args = arguments;
return func.apply({
self: self,
next: function () {
return origMethod.apply(self, arguments.length > 0 ? arguments : args);
}
}, args);
};
return true;
}
kendo.onWidgetRegistered(function (entry) {
pendingPatches = $.grep(pendingPatches, function (args) {
return !defadvice.apply(null, args);
});
createDirectives(entry.widget, entry.prefix == 'Mobile');
});
defadvice([
'ui.Widget',
'mobile.ui.Widget'
], 'angular', function (cmd, arg) {
var self = this.self;
if (cmd == 'init') {
if (!arg && OPTIONS_NOW) {
arg = OPTIONS_NOW;
}
OPTIONS_NOW = null;
if (arg && arg.$angular) {
self.$angular_scope = arg.$angular[0];
self.$angular_init(self.element, arg);
}
return;
}
var scope = self.$angular_scope;
if (scope) {
withoutTimeout(function () {
var x = arg(), elements = x.elements, data = x.data;
if (elements.length > 0) {
switch (cmd) {
case 'cleanup':
angular.forEach(elements, function (el) {
var itemScope = $(el).data('$$kendoScope');
if (itemScope && itemScope !== scope && itemScope.$$kendoScope) {
destroyScope(itemScope, el);
}
});
break;
case 'compile':
var injector = self.element.injector();
var compile = injector ? injector.get('$compile') : $defaultCompile;
angular.forEach(elements, function (el, i) {
var itemScope;
if (x.scopeFrom) {
itemScope = x.scopeFrom;
} else {
var vars = data && data[i];
if (vars !== undefined) {
itemScope = $.extend(scope.$new(), vars);
itemScope.$$kendoScope = true;
} else {
itemScope = scope;
}
}
$(el).data('$$kendoScope', itemScope);
compile(el)(itemScope);
});
digest(scope);
break;
}
}
});
}
});
defadvice('ui.Widget', '$angular_getLogicValue', function () {
return this.self.value();
});
defadvice('ui.Widget', '$angular_setLogicValue', function (val) {
this.self.value(val);
});
defadvice('ui.Select', '$angular_getLogicValue', function () {
var item = this.self.dataItem(), valueField = this.self.options.dataValueField;
if (item) {
if (this.self.options.valuePrimitive) {
if (!!valueField) {
return item[valueField];
} else {
return item;
}
} else {
return item.toJSON();
}
} else {
return null;
}
});
defadvice('ui.Select', '$angular_setLogicValue', function (val) {
var self = this.self;
var options = self.options;
var valueField = options.dataValueField;
var text = options.text || '';
if (val === undefined) {
val = '';
}
if (valueField && !options.valuePrimitive && val) {
text = val[options.dataTextField] || '';
val = val[valueField || options.dataTextField];
}
if (self.options.autoBind === false && !self.listView.bound()) {
if (!text && val && options.valuePrimitive) {
self.value(val);
} else {
self._preselect(val, text);
}
} else {
self.value(val);
}
});
defadvice('ui.MultiSelect', '$angular_getLogicValue', function () {
var value = this.self.dataItems().slice(0);
var valueField = this.self.options.dataValueField;
if (valueField && this.self.options.valuePrimitive) {
value = $.map(value, function (item) {
return item[valueField];
});
}
return value;
});
defadvice('ui.MultiSelect', '$angular_setLogicValue', function (val) {
if (val == null) {
val = [];
}
var self = this.self;
var options = self.options;
var valueField = options.dataValueField;
var data = val;
if (valueField && !options.valuePrimitive) {
val = $.map(val, function (item) {
return item[valueField];
});
}
if (options.autoBind === false && !options.valuePrimitive && !self.listView.bound()) {
self._preselect(data, val);
} else {
self.value(val);
}
});
defadvice('ui.AutoComplete', '$angular_getLogicValue', function () {
var options = this.self.options;
var values = this.self.value().split(options.separator);
var valuePrimitive = options.valuePrimitive;
var data = this.self.dataSource.data();
var dataItems = [];
for (var idx = 0, length = data.length; idx < length; idx++) {
var item = data[idx];
var dataValue = options.dataTextField ? item[options.dataTextField] : item;
for (var j = 0; j < values.length; j++) {
if (dataValue === values[j]) {
if (valuePrimitive) {
dataItems.push(dataValue);
} else {
dataItems.push(item.toJSON());
}
break;
}
}
}
return dataItems;
});
defadvice('ui.AutoComplete', '$angular_setLogicValue', function (value) {
if (value == null) {
value = [];
}
var self = this.self, dataTextField = self.options.dataTextField;
if (dataTextField && !self.options.valuePrimitive) {
if (value.length !== undefined) {
value = $.map(value, function (item) {
return item[dataTextField];
});
} else {
value = value[dataTextField];
}
}
self.value(value);
});
defadvice('ui.Widget', '$angular_init', function (element, options) {
var self = this.self;
if (options && !$.isArray(options)) {
var scope = self.$angular_scope;
for (var i = self.events.length; --i >= 0;) {
var event = self.events[i];
var handler = options[event];
if (handler && typeof handler == 'string') {
options[event] = self.$angular_makeEventHandler(event, scope, handler);
}
}
}
});
defadvice('ui.Widget', '$angular_makeEventHandler', function (event, scope, handler) {
handler = $parse(handler);
return function (e) {
digest(scope, function () {
handler(scope, { kendoEvent: e });
});
};
});
defadvice([
'ui.Grid',
'ui.ListView',
'ui.TreeView'
], '$angular_makeEventHandler', function (event, scope, handler) {
if (event != 'change') {
return this.next();
}
handler = $parse(handler);
return function (ev) {
var widget = ev.sender;
var options = widget.options;
var cell, multiple, locals = { kendoEvent: ev }, elems, items, columns, colIdx;
if (angular.isString(options.selectable)) {
cell = options.selectable.indexOf('cell') !== -1;
multiple = options.selectable.indexOf('multiple') !== -1;
}
elems = locals.selected = this.select();
items = locals.data = [];
columns = locals.columns = [];
for (var i = 0; i < elems.length; i++) {
var item = cell ? elems[i].parentNode : elems[i];
var dataItem = widget.dataItem(item);
if (cell) {
if (angular.element.inArray(dataItem, items) < 0) {
items.push(dataItem);
}
colIdx = angular.element(elems[i]).index();
if (angular.element.inArray(colIdx, columns) < 0) {
columns.push(colIdx);
}
} else {
items.push(dataItem);
}
}
if (!multiple) {
locals.dataItem = locals.data = items[0];
locals.angularDataItem = kendo.proxyModelSetters(locals.dataItem);
locals.selected = elems[0];
}
digest(scope, function () {
handler(scope, locals);
});
};
});
defadvice('ui.Grid', '$angular_init', function (element, options) {
this.next();
if (options.columns) {
var settings = $.extend({}, kendo.Template, options.templateSettings);
angular.forEach(options.columns, function (col) {
if (col.field && !col.template && !col.format && !col.values && (col.encoded === undefined || col.encoded)) {
col.template = '
#: ' + kendo.expr(col.field, settings.paramName) + '#';
}
});
}
});
{
defadvice('mobile.ui.ButtonGroup', 'value', function (mew) {
var self = this.self;
if (mew != null) {
self.select(self.element.children('li.km-button').eq(mew));
self.trigger('change');
self.trigger('select', { index: self.selectedIndex });
}
return self.selectedIndex;
});
defadvice('mobile.ui.ButtonGroup', '_select', function () {
this.next();
this.self.trigger('change');
});
}
module.directive('kendoMobileApplication', function () {
return {
terminal: true,
link: function (scope, element, attrs) {
createWidget(scope, element, attrs, 'kendoMobileApplication', 'kendoMobileApplication');
}
};
}).directive('kendoMobileView', function () {
return {
scope: true,
link: {
pre: function (scope, element, attrs) {
attrs.defaultOptions = scope.viewOptions;
attrs._instance = createWidget(scope, element, attrs, 'kendoMobileView', 'kendoMobileView');
},
post: function (scope, element, attrs) {
attrs._instance._layout();
attrs._instance._scroller();
}
}
};
}).directive('kendoMobileDrawer', function () {
return {
scope: true,
link: {
pre: function (scope, element, attrs) {
attrs.defaultOptions = scope.viewOptions;
attrs._instance = createWidget(scope, element, attrs, 'kendoMobileDrawer', 'kendoMobileDrawer');
},
post: function (scope, element, attrs) {
attrs._instance._layout();
attrs._instance._scroller();
}
}
};
}).directive('kendoMobileModalView', function () {
return {
scope: true,
link: {
pre: function (scope, element, attrs) {
attrs.defaultOptions = scope.viewOptions;
attrs._instance = createWidget(scope, element, attrs, 'kendoMobileModalView', 'kendoMobileModalView');
},
post: function (scope, element, attrs) {
attrs._instance._layout();
attrs._instance._scroller();
}
}
};
}).directive('kendoMobileSplitView', function () {
return {
terminal: true,
link: {
pre: function (scope, element, attrs) {
attrs.defaultOptions = scope.viewOptions;
attrs._instance = createWidget(scope, element, attrs, 'kendoMobileSplitView', 'kendoMobileSplitView');
},
post: function (scope, element, attrs) {
attrs._instance._layout();
}
}
};
}).directive('kendoMobilePane', function () {
return {
terminal: true,
link: {
pre: function (scope, element, attrs) {
attrs.defaultOptions = scope.viewOptions;
createWidget(scope, element, attrs, 'kendoMobilePane', 'kendoMobilePane');
}
}
};
}).directive('kendoMobileLayout', function () {
return {
link: {
pre: function (scope, element, attrs) {
createWidget(scope, element, attrs, 'kendoMobileLayout', 'kendoMobileLayout');
}
}
};
}).directive('kendoMobileActionSheet', function () {
return {
restrict: 'A',
link: function (scope, element, attrs) {
element.find('a[k-action]').each(function () {
$(this).attr('data-' + kendo.ns + 'action', $(this).attr('k-action'));
});
createWidget(scope, element, attrs, 'kendoMobileActionSheet', 'kendoMobileActionSheet');
}
};
}).directive('kendoMobilePopOver', function () {
return {
terminal: true,
link: {
pre: function (scope, element, attrs) {
attrs.defaultOptions = scope.viewOptions;
createWidget(scope, element, attrs, 'kendoMobilePopOver', 'kendoMobilePopOver');
}
}
};
}).directive('kendoViewTitle', function () {
return {
restrict: 'E',
replace: true,
template: function (element) {
return '
' + element.html() + '';
}
};
}).directive('kendoMobileHeader', function () {
return {
restrict: 'E',
link: function (scope, element) {
element.addClass('km-header').attr('data-role', 'header');
}
};
}).directive('kendoMobileFooter', function () {
return {
restrict: 'E',
link: function (scope, element) {
element.addClass('km-footer').attr('data-role', 'footer');
}
};
}).directive('kendoMobileScrollViewPage', function () {
return {
restrict: 'E',
replace: true,
template: function (element) {
return '
' + element.html() + '
';
}
};
});
angular.forEach([
'align',
'icon',
'rel',
'transition',
'actionsheetContext'
], function (attr) {
var kAttr = 'k' + attr.slice(0, 1).toUpperCase() + attr.slice(1);
module.directive(kAttr, function () {
return {
restrict: 'A',
priority: 2,
link: function (scope, element, attrs) {
element.attr(kendo.attr(kendo.toHyphens(attr)), scope.$eval(attrs[kAttr]));
}
};
});
});
var WIDGET_TEMPLATE_OPTIONS = {
'TreeMap': ['Template'],
'MobileListView': [
'HeaderTemplate',
'Template'
],
'MobileScrollView': [
'EmptyTemplate',
'Template'
],
'Grid': [
'AltRowTemplate',
'DetailTemplate',
'RowTemplate'
],
'ListView': [
'EditTemplate',
'Template',
'AltTemplate'
],
'Pager': [
'SelectTemplate',
'LinkTemplate'
],
'PivotGrid': [
'ColumnHeaderTemplate',
'DataCellTemplate',
'RowHeaderTemplate'
],
'Scheduler': [
'AllDayEventTemplate',
'DateHeaderTemplate',
'EventTemplate',
'MajorTimeHeaderTemplate',
'MinorTimeHeaderTemplate'
],
'TreeView': ['Template'],
'Validator': ['ErrorTemplate']
};
(function () {
var templateDirectives = {};
angular.forEach(WIDGET_TEMPLATE_OPTIONS, function (templates, widget) {
angular.forEach(templates, function (template) {
if (!templateDirectives[template]) {
templateDirectives[template] = [];
}
templateDirectives[template].push('?^^kendo' + widget);
});
});
angular.forEach(templateDirectives, function (parents, directive) {
var templateName = 'k' + directive;
var attrName = kendo.toHyphens(templateName);
module.directive(templateName, function () {
return {
restrict: 'A',
require: parents,
terminal: true,
compile: function ($element, $attrs) {
if ($attrs[templateName] !== '') {
return;
}
$element.removeAttr(attrName);
var template = $element[0].outerHTML;
return function (scope, element, attrs, controllers) {
var controller;
while (!controller && controllers.length) {
controller = controllers.shift();
}
if (!controller) {
$log.warn(attrName + ' without a matching parent widget found. It can be one of the following: ' + parents.join(', '));
} else {
controller.template(templateName, template);
element.remove();
}
};
}
};
});
});
}());
}(window.kendo.jQuery, window.angular));
return window.kendo;
}, typeof define == 'function' && define.amd ? define : function (a1, a2, a3) {
(a3 || a2)();
}));
(function (f, define) {
define('kendo.webcomponents', ['kendo.core'], f);
}(function () {
var __meta__ = {
id: 'webcomponents',
name: 'Web Components',
category: 'framework',
description: 'Adds Kendo UI custom elements for Web Components',
depends: ['core']
};
(function ($, angular, undefined) {
if (!kendo.support.customElements || kendo.webComponents.length) {
return;
}
if (angular && (angular.version.major == 1 || angular.injector)) {
return;
}
var TAGNAMES = {
editor: 'textarea',
numerictextbox: 'input',
datepicker: 'input',
datetimepicker: 'input',
timepicker: 'input',
autocomplete: 'input',
colorpicker: 'input',
maskedtextbox: 'input',
dropdownlist: 'select',
multiselect: 'select',
upload: 'input',
validator: 'form',
button: 'button',
mobilebutton: 'a',
mobilebackbutton: 'a',
mobiledetailbutton: 'a',
listview: 'ul',
mobilelistview: 'ul',
treeview: 'ul',
menu: 'ul',
contextmenu: 'ul',
actionsheet: 'ul'
};
var EVENT_PREFIX = 'on-';
var registered = [];
kendo.onWidgetRegistered(function (entry) {
var elementName = entry.prefix + entry.widget.prototype.options.name.toLowerCase();
if (registered.indexOf(elementName) === -1) {
registered.push(elementName);
registerElement(elementName, entry.widget);
}
});
var jsonRegExp = /^\s*(?:\{(?:.|\r\n|\n)*\}|\[(?:.|\r\n|\n)*\])\s*$/;
var jsonFormatRegExp = /^\{(\d+)(:[^\}]+)?\}|^\[[A-Za-z_]*\]$/;
var numberRegExp = /^(\+|-?)\d+(\.?)\d*$/;
function parseOption(element, option) {
var value = element.getAttribute(option);
if (value === null) {
value = undefined;
} else if (value === 'null') {
value = null;
} else if (value === 'true') {
value = true;
} else if (value === 'false') {
value = false;
} else if (numberRegExp.test(value)) {
value = parseFloat(value);
} else if (jsonRegExp.test(value) && !jsonFormatRegExp.test(value)) {
value = new Function('return (' + value + ')')();
}
return value;
}
function parseOptions(element, options) {
var result = {};
Object.keys(options).concat('dataSource').forEach(function (name) {
if (element.hasAttribute(kendo.toHyphens(name))) {
result[name] = parseOption(element, kendo.toHyphens(name));
}
});
return result;
}
function cloneEvent(e) {
var result = {};
Object.keys(e).forEach(function (key) {
if (key[0] != '_') {
result[key] = e[key];
}
});
return result;
}
function eventHandler(eventName, e) {
var event = document.createEvent('CustomEvent');
event.initCustomEvent(eventName, false, true, cloneEvent(e));
this.dispatchEvent(event);
if (event.defaultPrevented) {
e.preventDefault();
}
}
function expose(component, obj) {
var props = Object.keys(obj);
for (var idx = 0; idx <= props.length; idx++) {
if (typeof obj[props[idx]] === 'function') {
if (!component[props[idx]]) {
component[props[idx]] = obj[props[idx]].bind(component.widget);
}
} else {
if (props[idx] === 'options') {
continue;
}
component[props[idx]] = component[props[idx]] || obj[props[idx]];
}
}
}
function registerElement(name, widget) {
var options = widget.prototype.options;
var prototype = Object.create(HTMLElement.prototype);
Object.defineProperty(prototype, 'options', {
get: function () {
return this.widget.options;
},
set: function (options) {
var instance = this.widget;
options = $.extend(true, {}, instance.options, options);
var _wrapper = $(instance.wrapper)[0];
var _element = $(instance.element)[0];
instance._destroy();
var newElement = document.createElement(TAGNAMES[name] || 'div');
if (_wrapper && _element) {
_wrapper.parentNode.replaceChild(_element, _wrapper);
$(_element).replaceWith(newElement);
}
if (instance.value) {
options.value = instance.value();
}
instance.init(newElement, options);
this.bindEvents();
}
});
prototype.bindEvents = function () {
widget.prototype.events.forEach(function (eventName) {
this.widget.bind(eventName, eventHandler.bind(this, eventName));
if (this.hasAttribute(EVENT_PREFIX + eventName)) {
this.bind(eventName, function (e) {
window[this.getAttribute(EVENT_PREFIX + eventName)].call(this, e);
}.bind(this));
}
}.bind(this));
};
prototype.attachedCallback = function () {
var that = this;
var element = document.createElement(TAGNAMES[name] || 'div');
$(element).append(that.childNodes);
$(element).attr('class', $(that).attr('class'));
$(element).attr('style', $(that).attr('style'));
that.appendChild(element);
that.widget = new widget(element, parseOptions(that, options));
var obj = that.widget;
do {
expose(that, obj);
} while (obj = Object.getPrototypeOf(obj));
this.bindEvents();
};
prototype.detachedCallback = function () {
kendo.destroy(this.element);
};
kendo.webComponents.push('kendo-' + name);
document.registerElement('kendo-' + name, { prototype: prototype });
}
}(window.kendo.jQuery, window.angular));
return window.kendo;
}, typeof define == 'function' && define.amd ? define : function (a1, a2, a3) {
(a3 || a2)();
}));
(function (f, define) {
define('kendo.angular2', [
'kendo.core',
'kendo.webcomponents'
], f);
}(function () {
var __meta__ = {
id: 'angular2',
name: 'Angular 2',
category: 'framework',
description: 'Supports angular2 value accessors',
depends: ['core']
};
(function (kendo, System) {
if (!System || !System.register) {
return;
}
var __decorate = this && this.__decorate || function (decorators, target, key, desc) {
if (typeof Reflect === 'object' && typeof Reflect.decorate === 'function') {
return Reflect.decorate(decorators, target, key, desc);
}
switch (arguments.length) {
case 2:
return decorators.reduceRight(function (o, d) {
return d && d(o) || o;
}, target);
case 3:
return decorators.reduceRight(function (o, d) {
return d && d(target, key), void 0;
}, void 0);
case 4:
return decorators.reduceRight(function (o, d) {
return d && d(target, key, o) || o;
}, desc);
}
};
var __metadata = this && this.__metadata || function (k, v) {
if (typeof Reflect === 'object' && typeof Reflect.metadata === 'function') {
return Reflect.metadata(k, v);
}
};
System.register('kendo/angular2', ['angular2/angular2'], function (exports_1) {
var angular2_1;
var KendoValueAccessor;
return {
setters: [function (_angular2_1) {
angular2_1 = _angular2_1;
}],
execute: function () {
KendoValueAccessor = function () {
function KendoValueAccessor(cd, elementRef) {
var _this = this;
this.elementRef = elementRef;
this.onChange = function (_) {
};
this.onTouched = function () {
};
this.element = elementRef.nativeElement;
this.element.addEventListener('change', function () {
_this.onChange(_this.element.value());
});
this.element.addEventListener('spin', function () {
_this.onChange(_this.element.value());
});
cd.valueAccessor = this;
this.cd = cd;
cd.valueAccessor = this;
}
KendoValueAccessor.prototype.writeValue = function (value) {
this.element.value(value);
};
KendoValueAccessor.prototype.registerOnChange = function (fn) {
this.onChange = fn;
};
KendoValueAccessor.prototype.registerOnTouched = function (fn) {
this.onTouched = fn;
};
KendoValueAccessor = __decorate([
angular2_1.Directive({ selector: kendo.webComponents.join(',') }),
__metadata('design:paramtypes', [
angular2_1.NgControl,
angular2_1.ElementRef
])
], KendoValueAccessor);
return KendoValueAccessor;
}();
exports_1('KendoValueAccessor', KendoValueAccessor);
}
};
});
}(window.kendo, window.System));
}, typeof define == 'function' && define.amd ? define : function (a1, a2, a3) {
(a3 || a2)();
}));
(function (f, define) {
define('kendo.web', [
'kendo.core',
'kendo.router',
'kendo.view',
'kendo.fx',
'kendo.dom',
'kendo.data.odata',
'kendo.data.xml',
'kendo.data',
'kendo.ooxml',
'kendo.excel',
'kendo.data.signalr',
'kendo.binder',
'kendo.drawing',
'kendo.validator',
'kendo.userevents',
'kendo.draganddrop',
'kendo.mobile.scroller',
'kendo.groupable',
'kendo.reorderable',
'kendo.resizable',
'kendo.sortable',
'kendo.selectable',
'kendo.button',
'kendo.pager',
'kendo.popup',
'kendo.notification',
'kendo.tooltip',
'kendo.list',
'kendo.calendar',
'kendo.datepicker',
'kendo.autocomplete',
'kendo.dropdownlist',
'kendo.combobox',
'kendo.multiselect',
'kendo.colorpicker',
'kendo.columnmenu',
'kendo.columnsorter',
'kendo.grid',
'kendo.listview',
'kendo.filebrowser',
'kendo.imagebrowser',
'kendo.editor',
'kendo.numerictextbox',
'kendo.maskedtextbox',
'kendo.menu',
'kendo.editable',
'kendo.pivot.fieldmenu',
'kendo.filtercell',
'kendo.panelbar',
'kendo.progressbar',
'kendo.responsivepanel',
'kendo.tabstrip',
'kendo.timepicker',
'kendo.toolbar',
'kendo.datetimepicker',
'kendo.treeview.draganddrop',
'kendo.treeview',
'kendo.slider',
'kendo.splitter',
'kendo.upload',
'kendo.window',
'kendo.virtuallist',
'kendo.scheduler.view',
'kendo.scheduler.dayview',
'kendo.scheduler.agendaview',
'kendo.scheduler.monthview',
'kendo.scheduler.recurrence',
'kendo.scheduler',
'kendo.gantt.list',
'kendo.gantt.timeline',
'kendo.gantt',
'kendo.treelist',
'kendo.pivotgrid',
'kendo.spreadsheet',
'kendo.pivot.configurator',
'kendo.angular',
'kendo.webcomponents',
'kendo.angular2'
], f);
}(function () {
'bundle all';
return window.kendo;
}, typeof define == 'function' && define.amd ? define : function (a1, a2, a3) {
(a3 || a2)();
}));
(function (f, define) {
define('kendo.dataviz.core', [
'kendo.core',
'kendo.drawing'
], f);
}(function () {
var __meta__ = {
id: 'dataviz.core',
name: 'Core',
description: 'The DataViz core functions',
category: 'dataviz',
depends: [
'core',
'drawing'
],
hidden: true
};
(function ($, undefined) {
var kendo = window.kendo, util = kendo.util, append = util.append, defined = util.defined, last = util.last, valueOrDefault = util.valueOrDefault, dataviz = kendo.dataviz, geom = dataviz.geometry, draw = dataviz.drawing, measureText = draw.util.measureText, Class = kendo.Class, template = kendo.template, noop = $.noop, indexOf = $.inArray, isPlainObject = $.isPlainObject, trim = $.trim, math = Math, deepExtend = kendo.deepExtend;
var AXIS_LABEL_CLICK = 'axisLabelClick', BLACK = '#000', BOTTOM = 'bottom', CENTER = 'center', COORD_PRECISION = 3, CLIP = 'clip', CIRCLE = 'circle', CROSS = 'cross', DEFAULT_FONT = '12px sans-serif', DEFAULT_HEIGHT = 400, DEFAULT_ICON_SIZE = 7, DEFAULT_PRECISION = 10, DEFAULT_WIDTH = 600, DEG_TO_RAD = math.PI / 180, FORMAT_REGEX = /\{\d+:?/, HEIGHT = 'height', COORDINATE_LIMIT = 100000, INITIAL_ANIMATION_DURATION = 600, INSIDE = 'inside', LEFT = 'left', LINEAR = 'linear', MAX_VALUE = Number.MAX_VALUE, MIN_VALUE = -Number.MAX_VALUE, NONE = 'none', NOTE_CLICK = 'noteClick', NOTE_HOVER = 'noteHover', OUTSIDE = 'outside', RADIAL = 'radial', RIGHT = 'right', TOP = 'top', TRIANGLE = 'triangle', WIDTH = 'width', WHITE = '#fff', X = 'x', Y = 'y', ZERO_THRESHOLD = 0.2;
function getSpacing(value, defaultSpacing) {
var spacing = {
top: 0,
right: 0,
bottom: 0,
left: 0
};
defaultSpacing = defaultSpacing || 0;
if (typeof value === 'number') {
spacing[TOP] = spacing[RIGHT] = spacing[BOTTOM] = spacing[LEFT] = value;
} else {
spacing[TOP] = value[TOP] || defaultSpacing;
spacing[RIGHT] = value[RIGHT] || defaultSpacing;
spacing[BOTTOM] = value[BOTTOM] || defaultSpacing;
spacing[LEFT] = value[LEFT] || defaultSpacing;
}
return spacing;
}
var Point2D = function (x, y) {
var point = this;
if (!(point instanceof Point2D)) {
return new Point2D(x, y);
}
point.x = x || 0;
point.y = y || 0;
};
Point2D.fn = Point2D.prototype = {
clone: function () {
var point = this;
return new Point2D(point.x, point.y);
},
equals: function (point) {
return point && point.x === this.x && point.y === this.y;
},
rotate: function (center, degrees) {
var point = this, theta = degrees * DEG_TO_RAD, cosT = math.cos(theta), sinT = math.sin(theta), cx = center.x, cy = center.y, x = point.x, y = point.y;
point.x = round(cx + (x - cx) * cosT + (y - cy) * sinT, COORD_PRECISION);
point.y = round(cy + (y - cy) * cosT - (x - cx) * sinT, COORD_PRECISION);
return point;
},
multiply: function (a) {
var point = this;
point.x *= a;
point.y *= a;
return point;
},
distanceTo: function (point) {
var dx = this.x - point.x, dy = this.y - point.y;
return math.sqrt(dx * dx + dy * dy);
}
};
Point2D.onCircle = function (c, a, r) {
a *= DEG_TO_RAD;
return new Point2D(c.x - r * math.cos(a), c.y - r * math.sin(a));
};
var Box2D = function (x1, y1, x2, y2) {
var box = this;
if (!(box instanceof Box2D)) {
return new Box2D(x1, y1, x2, y2);
}
box.x1 = x1 || 0;
box.x2 = x2 || 0;
box.y1 = y1 || 0;
box.y2 = y2 || 0;
};
Box2D.fn = Box2D.prototype = {
width: function () {
return this.x2 - this.x1;
},
height: function () {
return this.y2 - this.y1;
},
translate: function (dx, dy) {
var box = this;
box.x1 += dx;
box.x2 += dx;
box.y1 += dy;
box.y2 += dy;
return box;
},
move: function (x, y) {
var box = this, height = box.height(), width = box.width();
if (defined(x)) {
box.x1 = x;
box.x2 = box.x1 + width;
}
if (defined(y)) {
box.y1 = y;
box.y2 = box.y1 + height;
}
return box;
},
wrap: function (targetBox) {
var box = this;
box.x1 = math.min(box.x1, targetBox.x1);
box.y1 = math.min(box.y1, targetBox.y1);
box.x2 = math.max(box.x2, targetBox.x2);
box.y2 = math.max(box.y2, targetBox.y2);
return box;
},
wrapPoint: function (point) {
this.wrap(new Box2D(point.x, point.y, point.x, point.y));
return this;
},
snapTo: function (targetBox, axis) {
var box = this;
if (axis == X || !axis) {
box.x1 = targetBox.x1;
box.x2 = targetBox.x2;
}
if (axis == Y || !axis) {
box.y1 = targetBox.y1;
box.y2 = targetBox.y2;
}
return box;
},
alignTo: function (targetBox, anchor) {
var box = this, height = box.height(), width = box.width(), axis = anchor == TOP || anchor == BOTTOM ? Y : X, offset = axis == Y ? height : width;
if (anchor === CENTER) {
var targetCenter = targetBox.center();
var center = box.center();
box.x1 += targetCenter.x - center.x;
box.y1 += targetCenter.y - center.y;
} else if (anchor === TOP || anchor === LEFT) {
box[axis + 1] = targetBox[axis + 1] - offset;
} else {
box[axis + 1] = targetBox[axis + 2];
}
box.x2 = box.x1 + width;
box.y2 = box.y1 + height;
return box;
},
shrink: function (dw, dh) {
var box = this;
box.x2 -= dw;
box.y2 -= dh;
return box;
},
expand: function (dw, dh) {
this.shrink(-dw, -dh);
return this;
},
pad: function (padding) {
var box = this, spacing = getSpacing(padding);
box.x1 -= spacing.left;
box.x2 += spacing.right;
box.y1 -= spacing.top;
box.y2 += spacing.bottom;
return box;
},
unpad: function (padding) {
var box = this, spacing = getSpacing(padding);
spacing.left = -spacing.left;
spacing.top = -spacing.top;
spacing.right = -spacing.right;
spacing.bottom = -spacing.bottom;
return box.pad(spacing);
},
clone: function () {
var box = this;
return new Box2D(box.x1, box.y1, box.x2, box.y2);
},
center: function () {
var box = this;
return new Point2D(box.x1 + box.width() / 2, box.y1 + box.height() / 2);
},
containsPoint: function (point) {
var box = this;
return point.x >= box.x1 && point.x <= box.x2 && point.y >= box.y1 && point.y <= box.y2;
},
points: function () {
var box = this;
return [
new Point2D(box.x1, box.y1),
new Point2D(box.x2, box.y1),
new Point2D(box.x2, box.y2),
new Point2D(box.x1, box.y2)
];
},
getHash: function () {
var box = this;
return [
box.x1,
box.y1,
box.x2,
box.y2
].join(',');
},
overlaps: function (box) {
return !(box.y2 < this.y1 || this.y2 < box.y1 || box.x2 < this.x1 || this.x2 < box.x1);
},
rotate: function (rotation) {
var box = this;
var width = box.width();
var height = box.height();
var center = box.center();
var cx = center.x;
var cy = center.y;
var r1 = rotatePoint(0, 0, cx, cy, rotation);
var r2 = rotatePoint(width, 0, cx, cy, rotation);
var r3 = rotatePoint(width, height, cx, cy, rotation);
var r4 = rotatePoint(0, height, cx, cy, rotation);
width = math.max(r1.x, r2.x, r3.x, r4.x) - math.min(r1.x, r2.x, r3.x, r4.x);
height = math.max(r1.y, r2.y, r3.y, r4.y) - math.min(r1.y, r2.y, r3.y, r4.y);
box.x2 = box.x1 + width;
box.y2 = box.y1 + height;
return box;
},
toRect: function () {
return new geom.Rect([
this.x1,
this.y1
], [
this.width(),
this.height()
]);
},
hasSize: function () {
return this.width() !== 0 && this.height() !== 0;
},
align: function (targetBox, axis, alignment) {
var box = this, c1 = axis + 1, c2 = axis + 2, sizeFunc = axis === X ? WIDTH : HEIGHT, size = box[sizeFunc]();
if (inArray(alignment, [
LEFT,
TOP
])) {
box[c1] = targetBox[c1];
box[c2] = box[c1] + size;
} else if (inArray(alignment, [
RIGHT,
BOTTOM
])) {
box[c2] = targetBox[c2];
box[c1] = box[c2] - size;
} else if (alignment == CENTER) {
box[c1] = targetBox[c1] + (targetBox[sizeFunc]() - size) / 2;
box[c2] = box[c1] + size;
}
}
};
var Ring = Class.extend({
init: function (center, innerRadius, radius, startAngle, angle) {
var ring = this;
ring.c = center;
ring.ir = innerRadius;
ring.r = radius;
ring.startAngle = startAngle;
ring.angle = angle;
},
clone: function () {
var r = this;
return new Ring(r.c, r.ir, r.r, r.startAngle, r.angle);
},
middle: function () {
return this.startAngle + this.angle / 2;
},
radius: function (newRadius, innerRadius) {
var that = this;
if (innerRadius) {
that.ir = newRadius;
} else {
that.r = newRadius;
}
return that;
},
point: function (angle, innerRadius) {
var ring = this, radianAngle = angle * DEG_TO_RAD, ax = math.cos(radianAngle), ay = math.sin(radianAngle), radius = innerRadius ? ring.ir : ring.r, x = round(ring.c.x - ax * radius, COORD_PRECISION), y = round(ring.c.y - ay * radius, COORD_PRECISION);
return new Point2D(x, y);
},
adjacentBox: function (distance, width, height) {
var sector = this.clone().expand(distance), midAndle = sector.middle(), midPoint = sector.point(midAndle), hw = width / 2, hh = height / 2, x = midPoint.x - hw, y = midPoint.y - hh, sa = math.sin(midAndle * DEG_TO_RAD), ca = math.cos(midAndle * DEG_TO_RAD);
if (math.abs(sa) < 0.9) {
x += hw * -ca / math.abs(ca);
}
if (math.abs(ca) < 0.9) {
y += hh * -sa / math.abs(sa);
}
return new Box2D(x, y, x + width, y + height);
},
containsPoint: function (p) {
var ring = this, c = ring.c, ir = ring.ir, r = ring.r, startAngle = ring.startAngle, endAngle = ring.startAngle + ring.angle, dx = p.x - c.x, dy = p.y - c.y, vector = new Point2D(dx, dy), startPoint = ring.point(startAngle), startVector = new Point2D(startPoint.x - c.x, startPoint.y - c.y), endPoint = ring.point(endAngle), endVector = new Point2D(endPoint.x - c.x, endPoint.y - c.y), dist = round(dx * dx + dy * dy, COORD_PRECISION);
return (startVector.equals(vector) || clockwise(startVector, vector)) && !clockwise(endVector, vector) && dist >= ir * ir && dist <= r * r;
},
getBBox: function () {
var ring = this, box = new Box2D(MAX_VALUE, MAX_VALUE, MIN_VALUE, MIN_VALUE), sa = round(ring.startAngle % 360), ea = round((sa + ring.angle) % 360), innerRadius = ring.ir, allAngles = [
0,
90,
180,
270,
sa,
ea
].sort(numericComparer), saIndex = indexOf(sa, allAngles), eaIndex = indexOf(ea, allAngles), angles, i, point;
if (sa == ea) {
angles = allAngles;
} else {
if (saIndex < eaIndex) {
angles = allAngles.slice(saIndex, eaIndex + 1);
} else {
angles = [].concat(allAngles.slice(0, eaIndex + 1), allAngles.slice(saIndex, allAngles.length));
}
}
for (i = 0; i < angles.length; i++) {
point = ring.point(angles[i]);
box.wrapPoint(point);
box.wrapPoint(point, innerRadius);
}
if (!innerRadius) {
box.wrapPoint(ring.c);
}
return box;
},
expand: function (value) {
this.r += value;
return this;
}
});
var Sector = Ring.extend({
init: function (center, radius, startAngle, angle) {
Ring.fn.init.call(this, center, 0, radius, startAngle, angle);
},
expand: function (value) {
return Ring.fn.expand.call(this, value);
},
clone: function () {
var sector = this;
return new Sector(sector.c, sector.r, sector.startAngle, sector.angle);
},
radius: function (newRadius) {
return Ring.fn.radius.call(this, newRadius);
},
point: function (angle) {
return Ring.fn.point.call(this, angle);
}
});
var ShapeBuilder = function () {
};
ShapeBuilder.fn = ShapeBuilder.prototype = {
createRing: function (sector, options) {
var startAngle = sector.startAngle + 180;
var endAngle = sector.angle + startAngle;
var center = new geom.Point(sector.c.x, sector.c.y);
var radius = math.max(sector.r, 0);
var innerRadius = math.max(sector.ir, 0);
var arc = new geom.Arc(center, {
startAngle: startAngle,
endAngle: endAngle,
radiusX: radius,
radiusY: radius
});
var path = draw.Path.fromArc(arc, options).close();
if (innerRadius) {
arc.radiusX = arc.radiusY = innerRadius;
var innerEnd = arc.pointAt(endAngle);
path.lineTo(innerEnd.x, innerEnd.y);
path.arc(endAngle, startAngle, innerRadius, innerRadius, true);
} else {
path.lineTo(center.x, center.y);
}
return path;
}
};
ShapeBuilder.current = new ShapeBuilder();
var ChartElement = Class.extend({
init: function (options) {
var element = this;
element.children = [];
element.options = deepExtend({}, element.options, options);
},
reflow: function (targetBox) {
var element = this, children = element.children, box, i, currentChild;
for (i = 0; i < children.length; i++) {
currentChild = children[i];
currentChild.reflow(targetBox);
box = box ? box.wrap(currentChild.box) : currentChild.box.clone();
}
element.box = box || targetBox;
},
destroy: function () {
var element = this, children = element.children, i;
if (this.animation) {
this.animation.destroy();
}
for (i = 0; i < children.length; i++) {
children[i].destroy();
}
},
getRoot: function () {
var parent = this.parent;
return parent ? parent.getRoot() : null;
},
getChart: function () {
var root = this.getRoot();
if (root) {
return root.chart;
}
},
translateChildren: function (dx, dy) {
var element = this, children = element.children, childrenCount = children.length, i;
for (i = 0; i < childrenCount; i++) {
children[i].box.translate(dx, dy);
}
},
append: function () {
append(this.children, arguments);
for (var i = 0; i < arguments.length; i++) {
arguments[i].parent = this;
}
},
renderVisual: function () {
if (this.options.visible === false) {
return;
}
this.createVisual();
this.addVisual();
this.renderChildren();
this.createAnimation();
this.renderComplete();
},
addVisual: function () {
if (this.visual) {
this.visual.chartElement = this;
if (this.parent) {
this.parent.appendVisual(this.visual);
}
}
},
renderChildren: function () {
var children = this.children;
for (var i = 0; i < children.length; i++) {
children[i].renderVisual();
}
},
createVisual: function () {
this.visual = new dataviz.drawing.Group({
zIndex: this.options.zIndex,
visible: valueOrDefault(this.options.visible, true)
});
},
createAnimation: function () {
if (this.visual) {
this.animation = draw.Animation.create(this.visual, this.options.animation);
}
},
appendVisual: function (childVisual) {
if (!childVisual.chartElement) {
childVisual.chartElement = this;
}
if (childVisual.options.noclip) {
this.clipRoot().visual.append(childVisual);
} else if (defined(childVisual.options.zIndex)) {
this.stackRoot().stackVisual(childVisual);
} else if (this.visual) {
this.visual.append(childVisual);
} else {
this.parent.appendVisual(childVisual);
}
},
clipRoot: function () {
if (this.parent) {
return this.parent.clipRoot();
}
return this;
},
stackRoot: function () {
if (this.parent) {
return this.parent.stackRoot();
}
return this;
},
stackVisual: function (childVisual) {
var zIndex = childVisual.options.zIndex || 0;
var visuals = this.visual.children;
for (var pos = 0; pos < visuals.length; pos++) {
var sibling = visuals[pos];
var here = valueOrDefault(sibling.options.zIndex, 0);
if (here > zIndex) {
break;
}
}
this.visual.insertAt(childVisual, pos);
},
traverse: function (callback) {
var children = this.children;
for (var i = 0; i < children.length; i++) {
var child = children[i];
callback(child);
if (child.traverse) {
child.traverse(callback);
}
}
},
closest: function (match) {
var element = this;
var matched = false;
while (element && !matched) {
matched = match(element);
if (!matched) {
element = element.parent;
}
}
if (matched) {
return element;
}
},
renderComplete: $.noop,
hasHighlight: function () {
var options = (this.options || {}).highlight;
return !(!this.createHighlight || options && options.visible === false);
},
toggleHighlight: function (show) {
var that = this;
var highlight = that._highlight;
var options = (that.options || {}).highlight;
var customVisual = (options || {}).visual;
if (!highlight) {
var highlightOptions = {
fill: {
color: WHITE,
opacity: 0.2
},
stroke: {
color: WHITE,
width: 1,
opacity: 0.2
}
};
if (customVisual) {
highlight = that._highlight = customVisual($.extend(that.highlightVisualArgs(), {
createVisual: function () {
return that.createHighlight(highlightOptions);
},
sender: that.getChart(),
series: that.series,
dataItem: that.dataItem,
category: that.category,
value: that.value,
percentage: that.percentage,
runningTotal: that.runningTotal,
total: that.total
}));
if (!highlight) {
return;
}
} else {
highlight = that._highlight = that.createHighlight(highlightOptions);
}
highlight.options.zIndex = that.options.zIndex;
that.appendVisual(highlight);
}
highlight.visible(show);
},
createGradientOverlay: function (element, options, gradientOptions) {
var overlay = new draw.Path(deepExtend({
stroke: { color: NONE },
fill: this.createGradient(gradientOptions),
closed: element.options.closed
}, options));
overlay.segments.elements(element.segments.elements());
return overlay;
},
createGradient: function (options) {
if (this.parent) {
return this.parent.createGradient(options);
}
}
});
var RootElement = ChartElement.extend({
init: function (options) {
var root = this;
root.gradients = {};
ChartElement.fn.init.call(root, options);
},
options: {
width: DEFAULT_WIDTH,
height: DEFAULT_HEIGHT,
background: WHITE,
border: {
color: BLACK,
width: 0
},
margin: getSpacing(5),
zIndex: -2
},
reflow: function () {
var root = this, options = root.options, children = root.children, currentBox = new Box2D(0, 0, options.width, options.height);
root.box = currentBox.unpad(options.margin);
for (var i = 0; i < children.length; i++) {
children[i].reflow(currentBox);
currentBox = boxDiff(currentBox, children[i].box) || Box2D();
}
},
createVisual: function () {
this.visual = new draw.Group();
this.createBackground();
},
createBackground: function () {
var options = this.options;
var border = options.border || {};
var box = this.box.clone().pad(options.margin).unpad(border.width);
var background = draw.Path.fromRect(box.toRect(), {
stroke: {
color: border.width ? border.color : '',
width: border.width,
dashType: border.dashType
},
fill: {
color: options.background,
opacity: options.opacity
},
zIndex: -10
});
this.visual.append(background);
},
getRoot: function () {
return this;
},
createGradient: function (options) {
var gradients = this.gradients;
var hashCode = util.objectKey(options);
var gradient = dataviz.Gradients[options.gradient];
var drawingGradient;
if (gradients[hashCode]) {
drawingGradient = gradients[hashCode];
} else {
var gradientOptions = deepExtend({}, gradient, options);
if (gradient.type == 'linear') {
drawingGradient = new draw.LinearGradient(gradientOptions);
} else {
if (options.innerRadius) {
gradientOptions.stops = innerRadialStops(gradientOptions);
}
drawingGradient = new draw.RadialGradient(gradientOptions);
drawingGradient.supportVML = gradient.supportVML !== false;
}
gradients[hashCode] = drawingGradient;
}
return drawingGradient;
}
});
var BoxElement = ChartElement.extend({
options: {
align: LEFT,
vAlign: TOP,
margin: {},
padding: {},
border: {
color: BLACK,
width: 0
},
background: '',
shrinkToFit: false,
width: 0,
height: 0,
visible: true
},
reflow: function (targetBox) {
var element = this, box, contentBox, options = element.options, width = options.width, height = options.height, hasSetSize = width && height, shrinkToFit = options.shrinkToFit, margin = getSpacing(options.margin), padding = getSpacing(options.padding), borderWidth = options.border.width, children = element.children, i, item;
function reflowPaddingBox() {
element.align(targetBox, X, options.align);
element.align(targetBox, Y, options.vAlign);
element.paddingBox = box.clone().unpad(margin).unpad(borderWidth);
}
contentBox = targetBox.clone();
if (hasSetSize) {
contentBox.x2 = contentBox.x1 + width;
contentBox.y2 = contentBox.y1 + height;
}
if (shrinkToFit) {
contentBox.unpad(margin).unpad(borderWidth).unpad(padding);
}
ChartElement.fn.reflow.call(element, contentBox);
if (hasSetSize) {
box = element.box = Box2D(0, 0, width, height);
} else {
box = element.box;
}
if (shrinkToFit && hasSetSize) {
reflowPaddingBox();
contentBox = element.contentBox = element.paddingBox.clone().unpad(padding);
} else {
contentBox = element.contentBox = box.clone();
box.pad(padding).pad(borderWidth).pad(margin);
reflowPaddingBox();
}
element.translateChildren(box.x1 - contentBox.x1 + margin.left + borderWidth + padding.left, box.y1 - contentBox.y1 + margin.top + borderWidth + padding.top);
for (i = 0; i < children.length; i++) {
item = children[i];
item.reflow(item.box);
}
},
align: function (targetBox, axis, alignment) {
this.box.align(targetBox, axis, alignment);
},
hasBox: function () {
var options = this.options;
return options.border.width || options.background;
},
createVisual: function () {
ChartElement.fn.createVisual.call(this);
var options = this.options;
if (options.visible && this.hasBox()) {
this.visual.append(draw.Path.fromRect(this.paddingBox.toRect(), this.visualStyle()));
}
},
visualStyle: function () {
var boxElement = this, options = boxElement.options, border = options.border || {};
return {
stroke: {
width: border.width,
color: border.color,
opacity: valueOrDefault(border.opacity, options.opacity),
dashType: border.dashType
},
fill: {
color: options.background,
opacity: options.opacity
},
cursor: options.cursor
};
}
});
var Text = ChartElement.extend({
init: function (content, options) {
var text = this;
ChartElement.fn.init.call(text, options);
text.content = content;
text.reflow(Box2D());
},
options: {
font: DEFAULT_FONT,
color: BLACK,
align: LEFT,
vAlign: ''
},
reflow: function (targetBox) {
var text = this, options = text.options, size;
size = options.size = measureText(text.content, { font: options.font });
text.baseline = size.baseline;
text.box = Box2D(targetBox.x1, targetBox.y1, targetBox.x1 + size.width, targetBox.y1 + size.height);
},
createVisual: function () {
var opt = this.options;
this.visual = new draw.Text(this.content, this.box.toRect().topLeft(), {
font: opt.font,
fill: {
color: opt.color,
opacity: opt.opacity
},
cursor: opt.cursor
});
}
});
var FloatElement = ChartElement.extend({
init: function (options) {
ChartElement.fn.init.call(this, options);
this._initDirection();
},
_initDirection: function () {
var options = this.options;
if (options.vertical) {
this.groupAxis = X;
this.elementAxis = Y;
this.groupSizeField = WIDTH;
this.elementSizeField = HEIGHT;
this.groupSpacing = options.spacing;
this.elementSpacing = options.vSpacing;
} else {
this.groupAxis = Y;
this.elementAxis = X;
this.groupSizeField = HEIGHT;
this.elementSizeField = WIDTH;
this.groupSpacing = options.vSpacing;
this.elementSpacing = options.spacing;
}
},
options: {
vertical: true,
wrap: true,
vSpacing: 0,
spacing: 0
},
reflow: function (targetBox) {
this.box = targetBox.clone();
this.reflowChildren();
},
reflowChildren: function () {
var floatElement = this;
var box = floatElement.box;
var elementAxis = floatElement.elementAxis;
var groupAxis = floatElement.groupAxis;
var elementSizeField = floatElement.elementSizeField;
var groupSizeField = floatElement.groupSizeField;
var groupOptions = floatElement.groupOptions();
var groups = groupOptions.groups;
var groupsCount = groups.length;
var groupsStart = box[groupAxis + 1] + floatElement.alignStart(groupOptions.groupsSize, box[groupSizeField]());
var groupStart = groupsStart;
var elementStart;
var groupElementStart;
var group;
var groupElements;
var groupElementsCount;
var idx;
var groupIdx;
var element;
var elementBox;
var elementSize;
if (groupsCount) {
for (groupIdx = 0; groupIdx < groupsCount; groupIdx++) {
group = groups[groupIdx];
groupElements = group.groupElements;
groupElementsCount = groupElements.length;
elementStart = box[elementAxis + 1];
for (idx = 0; idx < groupElementsCount; idx++) {
element = groupElements[idx];
elementSize = floatElement.elementSize(element);
groupElementStart = groupStart + floatElement.alignStart(elementSize[groupSizeField], group.groupSize);
elementBox = Box2D();
elementBox[groupAxis + 1] = groupElementStart;
elementBox[groupAxis + 2] = groupElementStart + elementSize[groupSizeField];
elementBox[elementAxis + 1] = elementStart;
elementBox[elementAxis + 2] = elementStart + elementSize[elementSizeField];
element.reflow(elementBox);
elementStart += elementSize[elementSizeField] + floatElement.elementSpacing;
}
groupStart += group.groupSize + floatElement.groupSpacing;
}
box[groupAxis + 1] = groupsStart;
box[groupAxis + 2] = groupsStart + groupOptions.groupsSize;
box[elementAxis + 2] = box[elementAxis + 1] + groupOptions.maxGroupElementsSize;
}
},
alignStart: function (size, maxSize) {
var start = 0;
var align = this.options.align;
if (align == RIGHT || align == BOTTOM) {
start = maxSize - size;
} else if (align == CENTER) {
start = (maxSize - size) / 2;
}
return start;
},
groupOptions: function () {
var floatElement = this;
var box = floatElement.box;
var children = floatElement.children;
var childrenCount = children.length;
var elementSizeField = this.elementSizeField;
var groupSizeField = this.groupSizeField;
var elementSpacing = this.elementSpacing;
var groupSpacing = this.groupSpacing;
var maxSize = round(box[elementSizeField]());
var idx = 0;
var groupSize = 0;
var elementSize;
var element;
var groupElementsSize = 0;
var groupsSize = 0;
var groups = [];
var groupElements = [];
var maxGroupElementsSize = 0;
for (idx = 0; idx < childrenCount; idx++) {
element = children[idx];
if (!element.box) {
element.reflow(box);
}
elementSize = this.elementSize(element);
if (floatElement.options.wrap && round(groupElementsSize + elementSpacing + elementSize[elementSizeField]) > maxSize) {
groups.push({
groupElements: groupElements,
groupSize: groupSize,
groupElementsSize: groupElementsSize
});
maxGroupElementsSize = math.max(maxGroupElementsSize, groupElementsSize);
groupsSize += groupSpacing + groupSize;
groupSize = 0;
groupElementsSize = 0;
groupElements = [];
}
groupSize = math.max(groupSize, elementSize[groupSizeField]);
if (groupElementsSize > 0) {
groupElementsSize += elementSpacing;
}
groupElementsSize += elementSize[elementSizeField];
groupElements.push(element);
}
groups.push({
groupElements: groupElements,
groupSize: groupSize,
groupElementsSize: groupElementsSize
});
maxGroupElementsSize = math.max(maxGroupElementsSize, groupElementsSize);
groupsSize += groupSize;
return {
groups: groups,
groupsSize: groupsSize,
maxGroupElementsSize: maxGroupElementsSize
};
},
elementSize: function (element) {
return {
width: element.box.width(),
height: element.box.height()
};
},
createVisual: noop
});
var TextBox = BoxElement.extend({
ROWS_SPLIT_REGEX: /\n|\\n/m,
init: function (content, options) {
var textbox = this;
textbox.content = content;
BoxElement.fn.init.call(textbox, options);
textbox._initContainer();
textbox.reflow(Box2D());
},
_initContainer: function () {
var textbox = this;
var options = textbox.options;
var rows = (textbox.content + '').split(textbox.ROWS_SPLIT_REGEX);
var floatElement = new FloatElement({
vertical: true,
align: options.align,
wrap: false
});
var textOptions = deepExtend({}, options, {
opacity: 1,
animation: null
});
var text;
var rowIdx;
textbox.container = floatElement;
textbox.append(floatElement);
for (rowIdx = 0; rowIdx < rows.length; rowIdx++) {
text = new Text(trim(rows[rowIdx]), textOptions);
floatElement.append(text);
}
},
reflow: function (targetBox) {
var options = this.options;
var visualFn = options.visual;
this.container.options.align = options.align;
if (visualFn && !this._boxReflow) {
if (!targetBox.hasSize()) {
this._boxReflow = true;
this.reflow(targetBox);
this._boxReflow = false;
targetBox = this.box;
}
this.visual = visualFn(this.visualContext(targetBox));
var visualBox = targetBox;
if (this.visual) {
visualBox = rectToBox(this.visual.clippedBBox() || new geom.Rect());
this.visual.options.zIndex = options.zIndex;
this.visual.options.noclip = options.noclip;
}
this.box = this.contentBox = this.paddingBox = visualBox;
} else {
BoxElement.fn.reflow.call(this, targetBox);
if (options.rotation) {
var margin = getSpacing(options.margin);
var box = this.box.unpad(margin);
this.targetBox = targetBox;
this.normalBox = box.clone();
box = this.rotate();
box.translate(margin.left - margin.right, margin.top - margin.bottom);
this.rotatedBox = box.clone();
box.pad(margin);
}
}
},
createVisual: function () {
var options = this.options;
if (!options.visible) {
return;
}
this.visual = new dataviz.drawing.Group({
transform: this.rotationTransform(),
zIndex: options.zIndex,
noclip: options.noclip
});
if (this.hasBox()) {
var box = draw.Path.fromRect(this.paddingBox.toRect(), this.visualStyle());
this.visual.append(box);
}
},
renderVisual: function () {
if (this.options.visual) {
this.addVisual();
this.createAnimation();
} else {
BoxElement.fn.renderVisual.call(this);
}
},
visualOptions: function () {
var options = this.options;
return {
background: options.background,
border: options.border,
color: options.color,
font: options.font,
margin: options.margin,
padding: options.padding,
visible: options.visible
};
},
visualContext: function (targetBox) {
var textbox = this;
return {
text: textbox.content,
rect: targetBox.toRect(),
sender: this.getChart(),
options: textbox.visualOptions(),
createVisual: function () {
textbox._boxReflow = true;
textbox.reflow(targetBox);
textbox._boxReflow = false;
return textbox.getDefaultVisual();
}
};
},
getDefaultVisual: function () {
this.createVisual();
this.renderChildren();
var visual = this.visual;
delete this.visual;
return visual;
},
rotate: function () {
var options = this.options;
this.box.rotate(options.rotation);
this.align(this.targetBox, X, options.align);
this.align(this.targetBox, Y, options.vAlign);
return this.box;
},
rotationTransform: function () {
var rotation = this.options.rotation;
if (!rotation) {
return null;
}
var center = this.normalBox.center();
var cx = center.x;
var cy = center.y;
var boxCenter = this.rotatedBox.center();
return geom.transform().translate(boxCenter.x - cx, boxCenter.y - cy).rotate(rotation, [
cx,
cy
]);
}
});
var Title = ChartElement.extend({
init: function (options) {
var title = this;
ChartElement.fn.init.call(title, options);
options = title.options;
title.append(new TextBox(options.text, deepExtend({}, options, { vAlign: options.position })));
},
options: {
color: BLACK,
position: TOP,
align: CENTER,
margin: getSpacing(5),
padding: getSpacing(5)
},
reflow: function (targetBox) {
var title = this;
ChartElement.fn.reflow.call(title, targetBox);
title.box.snapTo(targetBox, X);
}
});
Title.buildTitle = function (options, parent, defaultOptions) {
var title;
if (typeof options === 'string') {
options = { text: options };
}
options = deepExtend({ visible: true }, defaultOptions, options);
if (options && options.visible && options.text) {
title = new Title(options);
parent.append(title);
}
return title;
};
var AxisLabel = TextBox.extend({
init: function (value, text, index, dataItem, options) {
var label = this;
label.text = text;
label.value = value;
label.index = index;
label.dataItem = dataItem;
TextBox.fn.init.call(label, text, options);
},
visualContext: function (targetBox) {
var context = TextBox.fn.visualContext.call(this, targetBox);
context.value = this.value;
context.dataItem = this.dataItem;
context.format = this.options.format;
context.culture = this.options.culture;
return context;
},
click: function (widget, e) {
var label = this;
widget.trigger(AXIS_LABEL_CLICK, {
element: $(e.target),
value: label.value,
text: label.text,
index: label.index,
dataItem: label.dataItem,
axis: label.parent.options
});
},
rotate: function () {
if (this.options.alignRotation != CENTER) {
var box = this.normalBox.toRect();
var transform = this.rotationTransform();
this.box = rectToBox(box.bbox(transform.matrix()));
} else {
TextBox.fn.rotate.call(this);
}
return this.box;
},
rotationTransform: function () {
var options = this.options;
var rotation = options.rotation;
if (!rotation) {
return null;
}
if (options.alignRotation == CENTER) {
return TextBox.fn.rotationTransform.call(this);
}
var rotationMatrix = geom.transform().rotate(rotation).matrix();
var box = this.normalBox.toRect();
var rect = this.targetBox.toRect();
var rotationOrigin = options.rotationOrigin || TOP;
var alignAxis = rotationOrigin == TOP || rotationOrigin == BOTTOM ? X : Y;
var distanceAxis = rotationOrigin == TOP || rotationOrigin == BOTTOM ? Y : X;
var axisAnchor = rotationOrigin == TOP || rotationOrigin == LEFT ? rect.origin : rect.bottomRight();
var topLeft = box.topLeft().transformCopy(rotationMatrix);
var topRight = box.topRight().transformCopy(rotationMatrix);
var bottomRight = box.bottomRight().transformCopy(rotationMatrix);
var bottomLeft = box.bottomLeft().transformCopy(rotationMatrix);
var rotatedBox = geom.Rect.fromPoints(topLeft, topRight, bottomRight, bottomLeft);
var translate = {};
translate[distanceAxis] = rect.origin[distanceAxis] - rotatedBox.origin[distanceAxis];
var distanceLeft = math.abs(topLeft[distanceAxis] + translate[distanceAxis] - axisAnchor[distanceAxis]);
var distanceRight = math.abs(topRight[distanceAxis] + translate[distanceAxis] - axisAnchor[distanceAxis]);
var alignStart;
var alignEnd;
if (round(distanceLeft, DEFAULT_PRECISION) === round(distanceRight, DEFAULT_PRECISION)) {
alignStart = topLeft;
alignEnd = topRight;
} else if (distanceRight < distanceLeft) {
alignStart = topRight;
alignEnd = bottomRight;
} else {
alignStart = topLeft;
alignEnd = bottomLeft;
}
var alignCenter = alignStart[alignAxis] + (alignEnd[alignAxis] - alignStart[alignAxis]) / 2;
translate[alignAxis] = rect.center()[alignAxis] - alignCenter;
return geom.transform().translate(translate.x, translate.y).rotate(rotation);
}
});
function createAxisTick(options, tickOptions) {
var tickX = options.tickX, tickY = options.tickY, position = options.position;
var tick = new draw.Path({
stroke: {
width: tickOptions.width,
color: tickOptions.color
}
});
if (options.vertical) {
tick.moveTo(tickX, position).lineTo(tickX + tickOptions.size, position);
} else {
tick.moveTo(position, tickY).lineTo(position, tickY + tickOptions.size);
}
alignPathToPixel(tick);
return tick;
}
function createAxisGridLine(options, gridLine) {
var lineStart = options.lineStart, lineEnd = options.lineEnd, position = options.position;
var line = new draw.Path({
stroke: {
width: gridLine.width,
color: gridLine.color,
dashType: gridLine.dashType
}
});
if (options.vertical) {
line.moveTo(lineStart, position).lineTo(lineEnd, position);
} else {
line.moveTo(position, lineStart).lineTo(position, lineEnd);
}
alignPathToPixel(line);
return line;
}
var Axis = ChartElement.extend({
init: function (options) {
var axis = this;
ChartElement.fn.init.call(axis, options);
if (!axis.options.visible) {
axis.options = deepExtend({}, axis.options, {
labels: { visible: false },
line: { visible: false },
margin: 0,
majorTickSize: 0,
minorTickSize: 0
});
}
axis.options.minorTicks = deepExtend({}, {
color: axis.options.line.color,
width: axis.options.line.width,
visible: axis.options.minorTickType != NONE
}, axis.options.minorTicks, {
size: axis.options.minorTickSize,
align: axis.options.minorTickType
});
axis.options.majorTicks = deepExtend({}, {
color: axis.options.line.color,
width: axis.options.line.width,
visible: axis.options.majorTickType != NONE
}, axis.options.majorTicks, {
size: axis.options.majorTickSize,
align: axis.options.majorTickType
});
if (!this.options._deferLabels) {
axis.createLabels();
}
axis.createTitle();
axis.createNotes();
},
options: {
labels: {
visible: true,
rotation: 0,
mirror: false,
step: 1,
skip: 0
},
line: {
width: 1,
color: BLACK,
visible: true
},
title: {
visible: true,
position: CENTER
},
majorTicks: {
align: OUTSIDE,
size: 4,
skip: 0,
step: 1
},
minorTicks: {
align: OUTSIDE,
size: 3,
skip: 0,
step: 1
},
axisCrossingValue: 0,
majorTickType: OUTSIDE,
minorTickType: NONE,
majorGridLines: {
skip: 0,
step: 1
},
minorGridLines: {
visible: false,
width: 1,
color: BLACK,
skip: 0,
step: 1
},
margin: 5,
visible: true,
reverse: false,
justified: true,
notes: { label: { text: '' } },
_alignLines: true,
_deferLabels: false
},
labelsRange: function () {
return {
min: this.options.labels.skip,
max: this.labelsCount()
};
},
createLabels: function () {
var axis = this, options = axis.options, align = options.vertical ? RIGHT : CENTER, labelOptions = deepExtend({}, options.labels, {
align: align,
zIndex: options.zIndex
}), step = math.max(1, labelOptions.step);
axis.children = $.grep(axis.children, function (child) {
return !(child instanceof AxisLabel);
});
axis.labels = [];
if (labelOptions.visible) {
var range = axis.labelsRange(), rotation = labelOptions.rotation, label, i;
if (isPlainObject(rotation)) {
labelOptions.alignRotation = rotation.align;
labelOptions.rotation = rotation.angle;
}
if (labelOptions.rotation == 'auto') {
labelOptions.rotation = 0;
options.autoRotateLabels = true;
}
for (i = range.min; i < range.max; i += step) {
label = axis.createAxisLabel(i, labelOptions);
if (label) {
axis.append(label);
axis.labels.push(label);
}
}
}
},
lineBox: function () {
var axis = this, options = axis.options, box = axis.box, vertical = options.vertical, mirror = options.labels.mirror, axisX = mirror ? box.x1 : box.x2, axisY = mirror ? box.y2 : box.y1, lineWidth = options.line.width || 0;
return vertical ? Box2D(axisX, box.y1, axisX, box.y2 - lineWidth) : Box2D(box.x1, axisY, box.x2 - lineWidth, axisY);
},
createTitle: function () {
var axis = this, options = axis.options, titleOptions = deepExtend({
rotation: options.vertical ? -90 : 0,
text: '',
zIndex: 1,
visualSize: true
}, options.title), title;
if (titleOptions.visible && titleOptions.text) {
title = new TextBox(titleOptions.text, titleOptions);
axis.append(title);
axis.title = title;
}
},
createNotes: function () {
var axis = this, options = axis.options, notes = options.notes, items = notes.data || [], i, item, note;
axis.notes = [];
for (i = 0; i < items.length; i++) {
item = deepExtend({}, notes, items[i]);
item.value = axis.parseNoteValue(item.value);
note = new Note(item.value, item.label.text, null, null, null, item);
if (note.options.visible) {
if (defined(note.options.position)) {
if (options.vertical && !inArray(note.options.position, [
LEFT,
RIGHT
])) {
note.options.position = options.reverse ? LEFT : RIGHT;
} else if (!options.vertical && !inArray(note.options.position, [
TOP,
BOTTOM
])) {
note.options.position = options.reverse ? BOTTOM : TOP;
}
} else {
if (options.vertical) {
note.options.position = options.reverse ? LEFT : RIGHT;
} else {
note.options.position = options.reverse ? BOTTOM : TOP;
}
}
axis.append(note);
axis.notes.push(note);
}
}
},
parseNoteValue: function (value) {
return value;
},
renderVisual: function () {
ChartElement.fn.renderVisual.call(this);
this.createPlotBands();
},
createVisual: function () {
ChartElement.fn.createVisual.call(this);
this.createBackground();
this.createLine();
},
gridLinesVisual: function () {
var gridLines = this._gridLines;
if (!gridLines) {
gridLines = this._gridLines = new draw.Group({ zIndex: -2 });
this.appendVisual(this._gridLines);
}
return gridLines;
},
createTicks: function (lineGroup) {
var axis = this, options = axis.options, lineBox = axis.lineBox(), mirror = options.labels.mirror, majorUnit = options.majorTicks.visible ? options.majorUnit : 0, tickLineOptions = { vertical: options.vertical };
function render(tickPositions, tickOptions, skipUnit) {
var i, count = tickPositions.length;
if (tickOptions.visible) {
for (i = tickOptions.skip; i < count; i += tickOptions.step) {
if (defined(skipUnit) && i % skipUnit === 0) {
continue;
}
tickLineOptions.tickX = mirror ? lineBox.x2 : lineBox.x2 - tickOptions.size;
tickLineOptions.tickY = mirror ? lineBox.y1 - tickOptions.size : lineBox.y1;
tickLineOptions.position = tickPositions[i];
lineGroup.append(createAxisTick(tickLineOptions, tickOptions));
}
}
}
render(axis.getMajorTickPositions(), options.majorTicks);
render(axis.getMinorTickPositions(), options.minorTicks, majorUnit / options.minorUnit);
},
createLine: function () {
var axis = this, options = axis.options, line = options.line, lineBox = axis.lineBox();
if (line.width > 0 && line.visible) {
var path = new draw.Path({
stroke: {
width: line.width,
color: line.color,
dashType: line.dashType
}
});
path.moveTo(lineBox.x1, lineBox.y1).lineTo(lineBox.x2, lineBox.y2);
if (options._alignLines) {
alignPathToPixel(path);
}
var group = this._lineGroup = new draw.Group();
group.append(path);
this.visual.append(group);
this.createTicks(group);
}
},
getActualTickSize: function () {
var axis = this, options = axis.options, tickSize = 0;
if (options.majorTicks.visible && options.minorTicks.visible) {
tickSize = math.max(options.majorTicks.size, options.minorTicks.size);
} else if (options.majorTicks.visible) {
tickSize = options.majorTicks.size;
} else if (options.minorTicks.visible) {
tickSize = options.minorTicks.size;
}
return tickSize;
},
createBackground: function () {
var axis = this, options = axis.options, background = options.background, box = axis.box;
if (background) {
axis._backgroundPath = draw.Path.fromRect(box.toRect(), {
fill: { color: background },
stroke: null
});
this.visual.append(axis._backgroundPath);
}
},
createPlotBands: function () {
var axis = this, options = axis.options, plotBands = options.plotBands || [], vertical = options.vertical, plotArea = axis.plotArea, slotX, slotY, from, to;
if (plotBands.length === 0) {
return;
}
var group = this._plotbandGroup = new draw.Group({ zIndex: -1 });
var altAxis = $.grep(axis.pane.axes, function (a) {
return a.options.vertical !== axis.options.vertical;
})[0];
$.each(plotBands, function (i, item) {
from = valueOrDefault(item.from, MIN_VALUE);
to = valueOrDefault(item.to, MAX_VALUE);
if (vertical) {
slotX = (altAxis || plotArea.axisX).lineBox();
slotY = axis.getSlot(item.from, item.to, true);
} else {
slotX = axis.getSlot(item.from, item.to, true);
slotY = (altAxis || plotArea.axisY).lineBox();
}
if (slotX.width() !== 0 && slotY.height() !== 0) {
var bandRect = new geom.Rect([
slotX.x1,
slotY.y1
], [
slotX.width(),
slotY.height()
]);
var path = draw.Path.fromRect(bandRect, {
fill: {
color: item.color,
opacity: item.opacity
},
stroke: null
});
group.append(path);
}
});
axis.appendVisual(group);
},
createGridLines: function (altAxis) {
var axis = this, options = axis.options, axisLineVisible = altAxis.options.line.visible, majorGridLines = options.majorGridLines, majorUnit = majorGridLines.visible ? options.majorUnit : 0, vertical = options.vertical, lineBox = altAxis.lineBox(), linePos = lineBox[vertical ? 'y1' : 'x1'], lineOptions = {
lineStart: lineBox[vertical ? 'x1' : 'y1'],
lineEnd: lineBox[vertical ? 'x2' : 'y2'],
vertical: vertical
}, pos, majorTicks = [];
var container = this.gridLinesVisual();
function render(tickPositions, gridLine, skipUnit) {
var count = tickPositions.length, i;
if (gridLine.visible) {
for (i = gridLine.skip; i < count; i += gridLine.step) {
pos = round(tickPositions[i]);
if (!inArray(pos, majorTicks)) {
if (i % skipUnit !== 0 && (!axisLineVisible || linePos !== pos)) {
lineOptions.position = pos;
container.append(createAxisGridLine(lineOptions, gridLine));
majorTicks.push(pos);
}
}
}
}
}
render(axis.getMajorTickPositions(), options.majorGridLines);
render(axis.getMinorTickPositions(), options.minorGridLines, majorUnit / options.minorUnit);
return container.children;
},
reflow: function (box) {
var axis = this, options = axis.options, vertical = options.vertical, labels = axis.labels, count = labels.length, title = axis.title, sizeFn = vertical ? WIDTH : HEIGHT, titleSize = title ? title.box[sizeFn]() : 0, space = axis.getActualTickSize() + options.margin + titleSize, maxLabelSize = 0, rootBox = (this.getRoot() || {}).box || box, boxSize = rootBox[sizeFn](), labelSize, i;
for (i = 0; i < count; i++) {
labelSize = labels[i].box[sizeFn]();
if (labelSize + space <= boxSize) {
maxLabelSize = math.max(maxLabelSize, labelSize);
}
}
if (vertical) {
axis.box = Box2D(box.x1, box.y1, box.x1 + maxLabelSize + space, box.y2);
} else {
axis.box = Box2D(box.x1, box.y1, box.x2, box.y1 + maxLabelSize + space);
}
axis.arrangeTitle();
axis.arrangeLabels();
axis.arrangeNotes();
},
getLabelsTickPositions: function () {
return this.getMajorTickPositions();
},
labelTickIndex: function (label) {
return label.index;
},
arrangeLabels: function () {
var axis = this, options = axis.options, labels = axis.labels, labelsBetweenTicks = !options.justified, vertical = options.vertical, lineBox = axis.lineBox(), mirror = options.labels.mirror, tickPositions = axis.getLabelsTickPositions(), labelOffset = axis.getActualTickSize() + options.margin, labelBox, labelY, i;
for (i = 0; i < labels.length; i++) {
var label = labels[i], tickIx = axis.labelTickIndex(label), labelSize = vertical ? label.box.height() : label.box.width(), labelPos = tickPositions[tickIx] - labelSize / 2, firstTickPosition, nextTickPosition, middle, labelX;
if (vertical) {
if (labelsBetweenTicks) {
firstTickPosition = tickPositions[tickIx];
nextTickPosition = tickPositions[tickIx + 1];
middle = firstTickPosition + (nextTickPosition - firstTickPosition) / 2;
labelPos = middle - labelSize / 2;
}
labelX = lineBox.x2;
if (mirror) {
labelX += labelOffset;
label.options.rotationOrigin = LEFT;
} else {
labelX -= labelOffset + label.box.width();
label.options.rotationOrigin = RIGHT;
}
labelBox = label.box.move(labelX, labelPos);
} else {
if (labelsBetweenTicks) {
firstTickPosition = tickPositions[tickIx];
nextTickPosition = tickPositions[tickIx + 1];
} else {
firstTickPosition = labelPos;
nextTickPosition = labelPos + labelSize;
}
labelY = lineBox.y1;
if (mirror) {
labelY -= labelOffset + label.box.height();
label.options.rotationOrigin = BOTTOM;
} else {
labelY += labelOffset;
label.options.rotationOrigin = TOP;
}
labelBox = Box2D(firstTickPosition, labelY, nextTickPosition, labelY + label.box.height());
}
label.reflow(labelBox);
}
},
autoRotateLabels: function () {
if (this.options.autoRotateLabels && !this.options.vertical) {
var tickPositions = this.getMajorTickPositions();
var labels = this.labels;
var labelBox, angle, width, idx;
for (idx = 0; idx < labels.length; idx++) {
width = tickPositions[idx + 1] - tickPositions[idx];
labelBox = labels[idx].box;
if (labelBox.width() > width) {
if (labelBox.height() > width) {
angle = -90;
break;
}
angle = -45;
}
}
if (angle) {
for (idx = 0; idx < labels.length; idx++) {
labels[idx].options.rotation = angle;
labels[idx].reflow(Box2D());
}
return true;
}
}
},
arrangeTitle: function () {
var axis = this, options = axis.options, mirror = options.labels.mirror, vertical = options.vertical, title = axis.title;
if (title) {
if (vertical) {
title.options.align = mirror ? RIGHT : LEFT;
title.options.vAlign = title.options.position;
} else {
title.options.align = title.options.position;
title.options.vAlign = mirror ? TOP : BOTTOM;
}
title.reflow(axis.box);
}
},
arrangeNotes: function () {
var axis = this, i, item, slot, value;
for (i = 0; i < axis.notes.length; i++) {
item = axis.notes[i];
value = item.options.value;
if (defined(value)) {
if (axis.shouldRenderNote(value)) {
item.show();
} else {
item.hide();
}
slot = axis.getSlot(value);
} else {
item.hide();
}
item.reflow(slot || axis.lineBox());
}
},
alignTo: function (secondAxis) {
var axis = this, lineBox = secondAxis.lineBox(), vertical = axis.options.vertical, pos = vertical ? Y : X;
axis.box.snapTo(lineBox, pos);
if (vertical) {
axis.box.shrink(0, axis.lineBox().height() - lineBox.height());
} else {
axis.box.shrink(axis.lineBox().width() - lineBox.width(), 0);
}
axis.box[pos + 1] -= axis.lineBox()[pos + 1] - lineBox[pos + 1];
axis.box[pos + 2] -= axis.lineBox()[pos + 2] - lineBox[pos + 2];
},
axisLabelText: function (value, dataItem, options) {
var text = value;
if (options.template) {
var tmpl = template(options.template);
text = tmpl({
value: value,
dataItem: dataItem,
format: options.format,
culture: options.culture
});
} else if (options.format) {
if (options.format.match(FORMAT_REGEX)) {
text = kendo.format(options.format, value);
} else {
text = kendo.toString(value, options.format, options.culture);
}
}
return text;
},
slot: function (from, to, limit) {
var slot = this.getSlot(from, to, limit);
if (slot) {
return slot.toRect();
}
},
contentBox: function () {
var box = this.box.clone();
var labels = this.labels;
if (labels.length) {
if (labels[0].options.visible) {
box.wrap(labels[0].box);
}
if (last(labels).options.visible) {
box.wrap(last(labels).box);
}
}
return box;
},
limitRange: function (from, to, min, max, offset) {
var options = this.options;
if (from < min && offset < 0 && (!defined(options.min) || options.min <= min) || max < to && offset > 0 && (!defined(options.max) || max <= options.max)) {
return;
}
if (to < min && offset > 0 || max < from && offset < 0) {
return {
min: from,
max: to
};
}
var rangeSize = to - from;
if (from < min) {
from = util.limitValue(from, min, max);
to = util.limitValue(from + rangeSize, min + rangeSize, max);
} else if (to > max) {
to = util.limitValue(to, min, max);
from = util.limitValue(to - rangeSize, min, max - rangeSize);
}
return {
min: from,
max: to
};
}
});
var Note = BoxElement.extend({
init: function (value, text, dataItem, category, series, options) {
var note = this;
BoxElement.fn.init.call(note, options);
note.value = value;
note.text = text;
note.dataItem = dataItem;
note.category = category;
note.series = series;
note.render();
},
options: {
icon: {
visible: true,
type: CIRCLE
},
label: {
position: INSIDE,
visible: true,
align: CENTER,
vAlign: CENTER
},
line: { visible: true },
visible: true,
position: TOP,
zIndex: 2
},
hide: function () {
this.options.visible = false;
},
show: function () {
this.options.visible = true;
},
render: function () {
var note = this, options = note.options, label = options.label, text = note.text, icon = options.icon, size = icon.size, box = Box2D(), marker, width, height, noteTemplate;
if (options.visible) {
if (defined(label) && label.visible) {
if (label.template) {
noteTemplate = template(label.template);
text = noteTemplate({
dataItem: note.dataItem,
category: note.category,
value: note.value,
text: text,
series: note.series
});
} else if (label.format) {
text = autoFormat(label.format, text);
}
note.label = new TextBox(text, deepExtend({}, label));
if (label.position === INSIDE && !defined(size)) {
if (icon.type === CIRCLE) {
size = math.max(note.label.box.width(), note.label.box.height());
} else {
width = note.label.box.width();
height = note.label.box.height();
}
box.wrap(note.label.box);
}
}
icon.width = width || size || DEFAULT_ICON_SIZE;
icon.height = height || size || DEFAULT_ICON_SIZE;
marker = new ShapeElement(deepExtend({}, icon));
note.marker = marker;
note.append(marker);
if (note.label) {
note.append(note.label);
}
marker.reflow(Box2D());
note.wrapperBox = box.wrap(marker.box);
}
},
reflow: function (targetBox) {
var note = this, options = note.options, center = targetBox.center(), wrapperBox = note.wrapperBox, length = options.line.length, position = options.position, label = note.label, marker = note.marker, lineStart, box, contentBox;
if (options.visible) {
if (inArray(position, [
LEFT,
RIGHT
])) {
if (position === LEFT) {
contentBox = wrapperBox.alignTo(targetBox, position).translate(-length, targetBox.center().y - wrapperBox.center().y);
if (options.line.visible) {
lineStart = [
targetBox.x1,
center.y
];
note.linePoints = [
lineStart,
[
contentBox.x2,
center.y
]
];
box = contentBox.clone().wrapPoint(lineStart);
}
} else {
contentBox = wrapperBox.alignTo(targetBox, position).translate(length, targetBox.center().y - wrapperBox.center().y);
if (options.line.visible) {
lineStart = [
targetBox.x2,
center.y
];
note.linePoints = [
lineStart,
[
contentBox.x1,
center.y
]
];
box = contentBox.clone().wrapPoint(lineStart);
}
}
} else {
if (position === BOTTOM) {
contentBox = wrapperBox.alignTo(targetBox, position).translate(targetBox.center().x - wrapperBox.center().x, length);
if (options.line.visible) {
lineStart = [
center.x,
targetBox.y2
];
note.linePoints = [
lineStart,
[
center.x,
contentBox.y1
]
];
box = contentBox.clone().wrapPoint(lineStart);
}
} else {
contentBox = wrapperBox.alignTo(targetBox, position).translate(targetBox.center().x - wrapperBox.center().x, -length);
if (options.line.visible) {
lineStart = [
center.x,
targetBox.y1
];
note.linePoints = [
lineStart,
[
center.x,
contentBox.y2
]
];
box = contentBox.clone().wrapPoint(lineStart);
}
}
}
if (marker) {
marker.reflow(contentBox);
}
if (label) {
label.reflow(contentBox);
if (marker) {
if (options.label.position === OUTSIDE) {
label.box.alignTo(marker.box, position);
}
label.reflow(label.box);
}
}
note.contentBox = contentBox;
note.targetBox = targetBox;
note.box = box || contentBox;
}
},
createVisual: function () {
BoxElement.fn.createVisual.call(this);
if (this.options.visible) {
this.createLine();
}
},
renderVisual: function () {
var that = this;
var options = that.options;
var customVisual = options.visual;
if (options.visible && customVisual) {
that.visual = customVisual({
dataItem: that.dataItem,
category: that.category,
value: that.value,
text: that.text,
sender: that.getChart(),
series: that.series,
rect: that.targetBox.toRect(),
options: {
background: options.background,
border: options.background,
icon: options.icon,
label: options.label,
line: options.line,
position: options.position,
visible: options.visible
},
createVisual: function () {
that.createVisual();
that.renderChildren();
var defaultVisual = that.visual;
delete that.visual;
return defaultVisual;
}
});
that.addVisual();
} else {
BoxElement.fn.renderVisual.call(that);
}
},
createLine: function () {
var options = this.options.line;
if (this.linePoints) {
var path = draw.Path.fromPoints(this.linePoints, {
stroke: {
color: options.color,
width: options.width,
dashType: options.dashType
}
});
alignPathToPixel(path);
this.visual.append(path);
}
},
click: function (widget, e) {
var args = this.eventArgs(e);
if (!widget.trigger(NOTE_CLICK, args)) {
e.preventDefault();
}
},
hover: function (widget, e) {
var args = this.eventArgs(e);
if (!widget.trigger(NOTE_HOVER, args)) {
e.preventDefault();
}
},
leave: function (widget) {
widget._unsetActivePoint();
},
eventArgs: function (e) {
var note = this, options = note.options;
return {
element: $(e.target),
text: defined(options.label) ? options.label.text : '',
dataItem: note.dataItem,
series: note.series,
value: note.value,
category: note.category,
visual: note.visual
};
}
});
var ShapeElement = BoxElement.extend({
init: function (options, pointData) {
this.pointData = pointData;
BoxElement.fn.init.call(this, options);
},
options: {
type: CIRCLE,
align: CENTER,
vAlign: CENTER
},
getElement: function () {
var marker = this, options = marker.options, type = options.type, rotation = options.rotation, box = marker.paddingBox, element, center = box.center(), halfWidth = box.width() / 2;
if (!options.visible || !marker.hasBox()) {
return;
}
var style = marker.visualStyle();
if (type === CIRCLE) {
element = new draw.Circle(new geom.Circle([
round(box.x1 + halfWidth, COORD_PRECISION),
round(box.y1 + box.height() / 2, COORD_PRECISION)
], halfWidth), style);
} else if (type === TRIANGLE) {
element = draw.Path.fromPoints([
[
box.x1 + halfWidth,
box.y1
],
[
box.x1,
box.y2
],
[
box.x2,
box.y2
]
], style).close();
} else if (type === CROSS) {
element = new draw.MultiPath(style);
element.moveTo(box.x1, box.y1).lineTo(box.x2, box.y2);
element.moveTo(box.x1, box.y2).lineTo(box.x2, box.y1);
} else {
element = draw.Path.fromRect(box.toRect(), style);
}
if (rotation) {
element.transform(geom.transform().rotate(-rotation, [
center.x,
center.y
]));
}
element.options.zIndex = this.options.zIndex;
return element;
},
createElement: function () {
var that = this;
var customVisual = that.options.visual;
var pointData = that.pointData || {};
var visual;
if (customVisual) {
visual = customVisual({
value: pointData.value,
dataItem: pointData.dataItem,
sender: that.getChart(),
series: pointData.series,
category: pointData.category,
rect: that.paddingBox.toRect(),
options: that.visualOptions(),
createVisual: function () {
return that.getElement();
}
});
} else {
visual = that.getElement();
}
return visual;
},
visualOptions: function () {
var options = this.options;
return {
background: options.background,
border: options.border,
margin: options.margin,
padding: options.padding,
type: options.type,
size: options.width,
visible: options.visible
};
},
createVisual: function () {
this.visual = this.createElement();
}
});
var NumericAxis = Axis.extend({
init: function (seriesMin, seriesMax, options) {
var axis = this, defaultOptions = axis.initDefaults(seriesMin, seriesMax, options);
Axis.fn.init.call(axis, defaultOptions);
},
startValue: function () {
return 0;
},
options: {
type: 'numeric',
min: 0,
max: 1,
vertical: true,
majorGridLines: {
visible: true,
width: 1,
color: BLACK
},
labels: { format: '#.####################' },
zIndex: 1
},
initDefaults: function (seriesMin, seriesMax, options) {
var axis = this, narrowRange = options.narrowRange, autoMin = axis.autoAxisMin(seriesMin, seriesMax, narrowRange), autoMax = axis.autoAxisMax(seriesMin, seriesMax, narrowRange), majorUnit = autoMajorUnit(autoMin, autoMax), autoOptions = { majorUnit: majorUnit }, userSetLimits;
if (options.roundToMajorUnit !== false) {
if (autoMin < 0 && remainderClose(autoMin, majorUnit, 1 / 3)) {
autoMin -= majorUnit;
}
if (autoMax > 0 && remainderClose(autoMax, majorUnit, 1 / 3)) {
autoMax += majorUnit;
}
}
autoOptions.min = floor(autoMin, majorUnit);
autoOptions.max = ceil(autoMax, majorUnit);
this.totalMin = defined(options.min) ? math.min(autoOptions.min, options.min) : autoOptions.min;
this.totalMax = defined(options.max) ? math.max(autoOptions.max, options.max) : autoOptions.max;
this.totalMajorUnit = majorUnit;
if (options) {
userSetLimits = defined(options.min) || defined(options.max);
if (userSetLimits) {
if (options.min === options.max) {
if (options.min > 0) {
options.min = 0;
} else {
options.max = 1;
}
}
}
if (options.majorUnit) {
autoOptions.min = floor(autoOptions.min, options.majorUnit);
autoOptions.max = ceil(autoOptions.max, options.majorUnit);
} else if (userSetLimits) {
options = deepExtend(autoOptions, options);
autoOptions.majorUnit = autoMajorUnit(options.min, options.max);
}
}
autoOptions.minorUnit = (options.majorUnit || autoOptions.majorUnit) / 5;
return deepExtend(autoOptions, options);
},
range: function () {
var options = this.options;
return {
min: options.min,
max: options.max
};
},
autoAxisMax: function (min, max, narrow) {
var axisMax, diff;
if (!min && !max) {
return 1;
}
if (min <= 0 && max <= 0) {
max = min == max ? 0 : max;
diff = math.abs((max - min) / max);
if (narrow === false || !narrow && diff > ZERO_THRESHOLD) {
return 0;
}
axisMax = math.min(0, max - (min - max) / 2);
} else {
min = min == max ? 0 : min;
axisMax = max;
}
return axisMax;
},
autoAxisMin: function (min, max, narrow) {
var axisMin, diff;
if (!min && !max) {
return 0;
}
if (min >= 0 && max >= 0) {
min = min == max ? 0 : min;
diff = (max - min) / max;
if (narrow === false || !narrow && diff > ZERO_THRESHOLD) {
return 0;
}
axisMin = math.max(0, min - (max - min) / 2);
} else {
max = min == max ? 0 : max;
axisMin = min;
}
return axisMin;
},
getDivisions: function (stepValue) {
if (stepValue === 0) {
return 1;
}
var options = this.options, range = options.max - options.min;
return math.floor(round(range / stepValue, COORD_PRECISION)) + 1;
},
getTickPositions: function (unit, skipUnit) {
var axis = this, options = axis.options, vertical = options.vertical, reverse = options.reverse, lineBox = axis.lineBox(), lineSize = vertical ? lineBox.height() : lineBox.width(), range = options.max - options.min, scale = lineSize / range, step = unit * scale, skipStep = 0, divisions = axis.getDivisions(unit), dir = (vertical ? -1 : 1) * (reverse ? -1 : 1), startEdge = dir === 1 ? 1 : 2, pos = lineBox[(vertical ? Y : X) + startEdge], positions = [], i;
if (skipUnit) {
skipStep = skipUnit / unit;
}
for (i = 0; i < divisions; i++) {
if (i % skipStep !== 0) {
positions.push(round(pos, COORD_PRECISION));
}
pos = pos + step * dir;
}
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) {
var axis = this, options = axis.options, reverse = options.reverse, vertical = options.vertical, valueAxis = vertical ? Y : X, lineBox = axis.lineBox(), lineStart = lineBox[valueAxis + (reverse ? 2 : 1)], lineSize = vertical ? lineBox.height() : lineBox.width(), dir = reverse ? -1 : 1, step = dir * (lineSize / (options.max - options.min)), p1, p2, slotBox = new Box2D(lineBox.x1, lineBox.y1, lineBox.x1, lineBox.y1);
if (!defined(a)) {
a = b || 0;
}
if (!defined(b)) {
b = a || 0;
}
if (limit) {
a = math.max(math.min(a, options.max), options.min);
b = math.max(math.min(b, options.max), options.min);
}
if (vertical) {
p1 = options.max - math.max(a, b);
p2 = options.max - math.min(a, b);
} else {
p1 = math.min(a, b) - options.min;
p2 = math.max(a, b) - options.min;
}
slotBox[valueAxis + 1] = math.max(math.min(lineStart + step * (reverse ? p2 : p1), COORDINATE_LIMIT), -COORDINATE_LIMIT);
slotBox[valueAxis + 2] = math.max(math.min(lineStart + step * (reverse ? p1 : p2), COORDINATE_LIMIT), -COORDINATE_LIMIT);
return slotBox;
},
getValue: function (point) {
var axis = this, options = axis.options, reverse = options.reverse, vertical = options.vertical, max = options.max * 1, min = options.min * 1, valueAxis = vertical ? Y : X, lineBox = axis.lineBox(), lineStart = lineBox[valueAxis + (reverse ? 2 : 1)], lineSize = vertical ? lineBox.height() : lineBox.width(), dir = reverse ? -1 : 1, offset = dir * (point[valueAxis] - lineStart), step = (max - min) / lineSize, valueOffset = offset * step, value;
if (offset < 0 || offset > lineSize) {
return null;
}
value = vertical ? max - valueOffset : min + valueOffset;
return round(value, DEFAULT_PRECISION);
},
translateRange: function (delta) {
var axis = this, options = axis.options, lineBox = axis.lineBox(), vertical = options.vertical, reverse = options.reverse, size = vertical ? lineBox.height() : lineBox.width(), range = options.max - options.min, scale = size / range, offset = round(delta / scale, DEFAULT_PRECISION);
if ((vertical || reverse) && !(vertical && reverse)) {
offset = -offset;
}
return {
min: options.min + offset,
max: options.max + offset
};
},
scaleRange: function (delta) {
var axis = this, options = axis.options, offset = -delta * options.majorUnit;
return {
min: options.min - offset,
max: options.max + offset
};
},
labelsCount: function () {
return this.getDivisions(this.options.majorUnit);
},
createAxisLabel: function (index, labelOptions) {
var axis = this, options = axis.options, value = round(options.min + index * options.majorUnit, DEFAULT_PRECISION), text = axis.axisLabelText(value, null, labelOptions);
return new AxisLabel(value, text, index, null, labelOptions);
},
shouldRenderNote: function (value) {
var range = this.range();
return range.min <= value && value <= range.max;
},
pan: function (delta) {
var range = this.translateRange(delta);
return this.limitRange(range.min, range.max, this.totalMin, this.totalMax);
},
pointsRange: function (start, end) {
var startValue = this.getValue(start);
var endValue = this.getValue(end);
var min = math.min(startValue, endValue);
var max = math.max(startValue, endValue);
return {
min: min,
max: max
};
},
zoomRange: function (delta) {
var newRange = this.scaleRange(delta);
var totalMax = this.totalMax;
var totalMin = this.totalMin;
var min = util.limitValue(newRange.min, totalMin, totalMax);
var max = util.limitValue(newRange.max, totalMin, totalMax);
var optionsRange = this.options.max - this.options.min;
if (optionsRange < this.totalMajorUnit || max - min >= this.totalMajorUnit) {
return {
min: min,
max: max
};
}
}
});
var LogarithmicAxis = Axis.extend({
init: function (seriesMin, seriesMax, options) {
this.options = this._initOptions(seriesMin, seriesMax, options);
Axis.fn.init.call(this, options);
},
startValue: function () {
return this.options.min;
},
options: {
type: 'log',
majorUnit: 10,
minorUnit: 1,
axisCrossingValue: 1,
vertical: true,
majorGridLines: {
visible: true,
width: 1,
color: BLACK
},
zIndex: 1
},
getSlot: function (a, b, limit) {
var axis = this, options = axis.options, reverse = options.reverse, vertical = options.vertical, valueAxis = vertical ? Y : X, lineBox = axis.lineBox(), lineStart = lineBox[valueAxis + (reverse ? 2 : 1)], lineSize = vertical ? lineBox.height() : lineBox.width(), dir = reverse ? -1 : 1, base = options.majorUnit, logMin = axis.logMin, logMax = axis.logMax, step = dir * (lineSize / (logMax - logMin)), p1, p2, slotBox = new Box2D(lineBox.x1, lineBox.y1, lineBox.x1, lineBox.y1);
if (!defined(a)) {
a = b || 1;
}
if (!defined(b)) {
b = a || 1;
}
if (a <= 0 || b <= 0) {
return;
}
if (limit) {
a = math.max(math.min(a, options.max), options.min);
b = math.max(math.min(b, options.max), options.min);
}
a = log(a, base);
b = log(b, base);
if (vertical) {
p1 = logMax - math.max(a, b);
p2 = logMax - math.min(a, b);
} else {
p1 = math.min(a, b) - logMin;
p2 = math.max(a, b) - logMin;
}
slotBox[valueAxis + 1] = lineStart + step * (reverse ? p2 : p1);
slotBox[valueAxis + 2] = lineStart + step * (reverse ? p1 : p2);
return slotBox;
},
getValue: function (point) {
var axis = this, options = axis.options, reverse = options.reverse, vertical = options.vertical, lineBox = axis.lineBox(), base = options.majorUnit, logMin = axis.logMin, logMax = axis.logMax, dir = vertical === reverse ? 1 : -1, startEdge = dir === 1 ? 1 : 2, lineSize = vertical ? lineBox.height() : lineBox.width(), step = (logMax - logMin) / lineSize, valueAxis = vertical ? Y : X, lineStart = lineBox[valueAxis + startEdge], offset = dir * (point[valueAxis] - lineStart), valueOffset = offset * step, value;
if (offset < 0 || offset > lineSize) {
return null;
}
value = logMin + valueOffset;
return round(math.pow(base, value), DEFAULT_PRECISION);
},
range: function () {
var options = this.options;
return {
min: options.min,
max: options.max
};
},
scaleRange: function (delta) {
var axis = this, options = axis.options, base = options.majorUnit, offset = -delta;
return {
min: math.pow(base, axis.logMin - offset),
max: math.pow(base, axis.logMax + offset)
};
},
translateRange: function (delta) {
var axis = this, options = axis.options, base = options.majorUnit, lineBox = axis.lineBox(), vertical = options.vertical, reverse = options.reverse, size = vertical ? lineBox.height() : lineBox.width(), scale = size / (axis.logMax - axis.logMin), offset = round(delta / scale, DEFAULT_PRECISION);
if ((vertical || reverse) && !(vertical && reverse)) {
offset = -offset;
}
return {
min: math.pow(base, axis.logMin + offset),
max: math.pow(base, axis.logMax + offset)
};
},
labelsCount: function () {
var axis = this, floorMax = math.floor(axis.logMax), count = math.floor(floorMax - axis.logMin) + 1;
return count;
},
getMajorTickPositions: function () {
var axis = this, ticks = [];
axis.traverseMajorTicksPositions(function (position) {
ticks.push(position);
}, {
step: 1,
skip: 0
});
return ticks;
},
createTicks: function (lineGroup) {
var axis = this, ticks = [], options = axis.options, lineBox = axis.lineBox(), mirror = options.labels.mirror, majorTicks = options.majorTicks, minorTicks = options.minorTicks, tickLineOptions = { vertical: options.vertical };
function render(tickPosition, tickOptions) {
tickLineOptions.tickX = mirror ? lineBox.x2 : lineBox.x2 - tickOptions.size;
tickLineOptions.tickY = mirror ? lineBox.y1 - tickOptions.size : lineBox.y1;
tickLineOptions.position = tickPosition;
lineGroup.append(createAxisTick(tickLineOptions, tickOptions));
}
if (majorTicks.visible) {
axis.traverseMajorTicksPositions(render, majorTicks);
}
if (minorTicks.visible) {
axis.traverseMinorTicksPositions(render, minorTicks);
}
return ticks;
},
createGridLines: function (altAxis) {
var axis = this, options = axis.options, majorGridLines = options.majorGridLines, minorGridLines = options.minorGridLines, vertical = options.vertical, lineBox = altAxis.lineBox(), lineOptions = {
lineStart: lineBox[vertical ? 'x1' : 'y1'],
lineEnd: lineBox[vertical ? 'x2' : 'y2'],
vertical: vertical
}, majorTicks = [];
var container = this.gridLinesVisual();
function render(tickPosition, gridLine) {
if (!inArray(tickPosition, majorTicks)) {
lineOptions.position = tickPosition;
container.append(createAxisGridLine(lineOptions, gridLine));
majorTicks.push(tickPosition);
}
}
if (majorGridLines.visible) {
axis.traverseMajorTicksPositions(render, majorGridLines);
}
if (minorGridLines.visible) {
axis.traverseMinorTicksPositions(render, minorGridLines);
}
return container.children;
},
traverseMajorTicksPositions: function (callback, tickOptions) {
var axis = this, lineOptions = axis._lineOptions(), lineStart = lineOptions.lineStart, step = lineOptions.step, logMin = axis.logMin, logMax = axis.logMax, power, position;
for (power = math.ceil(logMin) + tickOptions.skip; power <= logMax; power += tickOptions.step) {
position = round(lineStart + step * (power - logMin), DEFAULT_PRECISION);
callback(position, tickOptions);
}
},
traverseMinorTicksPositions: function (callback, tickOptions) {
var axis = this, options = axis.options, lineOptions = axis._lineOptions(), lineStart = lineOptions.lineStart, lineStep = lineOptions.step, base = options.majorUnit, logMin = axis.logMin, logMax = axis.logMax, start = math.floor(logMin), max = options.max, min = options.min, minorUnit = options.minorUnit, power, value, position, minorOptions;
for (power = start; power < logMax; power++) {
minorOptions = axis._minorIntervalOptions(power);
for (var idx = tickOptions.skip; idx < minorUnit; idx += tickOptions.step) {
value = minorOptions.value + idx * minorOptions.minorStep;
if (value > max) {
break;
}
if (value >= min) {
position = round(lineStart + lineStep * (log(value, base) - logMin), DEFAULT_PRECISION);
callback(position, tickOptions);
}
}
}
},
createAxisLabel: function (index, labelOptions) {
var axis = this, options = axis.options, power = math.ceil(axis.logMin + index), value = Math.pow(options.majorUnit, power), text = axis.axisLabelText(value, null, labelOptions);
return new AxisLabel(value, text, index, null, labelOptions);
},
shouldRenderNote: function (value) {
var range = this.range();
return range.min <= value && value <= range.max;
},
_throwNegativeValuesError: function () {
throw new Error('Non positive values cannot be used for a logarithmic axis');
},
_initOptions: function (seriesMin, seriesMax, options) {
var axis = this, axisOptions = deepExtend({}, axis.options, {
min: seriesMin,
max: seriesMax
}, options), min = axisOptions.min, max = axisOptions.max, base = axisOptions.majorUnit, autoMax = this._autoMax(seriesMax, base), autoMin = this._autoMin(seriesMin, seriesMax, axisOptions);
if (axisOptions.axisCrossingValue <= 0) {
axis._throwNegativeValuesError();
}
if (!defined(options.max)) {
max = autoMax;
} else if (options.max <= 0) {
axis._throwNegativeValuesError();
}
if (!defined(options.min)) {
min = autoMin;
} else if (options.min <= 0) {
axis._throwNegativeValuesError();
}
this.totalMin = defined(options.min) ? math.min(autoMin, options.min) : autoMin;
this.totalMax = defined(options.max) ? math.max(autoMax, options.max) : autoMax;
axis.logMin = round(log(min, base), DEFAULT_PRECISION);
axis.logMax = round(log(max, base), DEFAULT_PRECISION);
axisOptions.max = max;
axisOptions.min = min;
axisOptions.minorUnit = options.minorUnit || round(base - 1, DEFAULT_PRECISION);
return axisOptions;
},
_autoMin: function (min, max, options) {
var autoMin = min;
var base = options.majorUnit;
if (min <= 0) {
autoMin = max <= 1 ? math.pow(base, -2) : 1;
} else if (!options.narrowRange) {
autoMin = math.pow(base, math.floor(log(min, base)));
}
return autoMin;
},
_autoMax: function (max, base) {
var logMaxRemainder = round(log(max, base), DEFAULT_PRECISION) % 1;
var autoMax;
if (max <= 0) {
autoMax = base;
} else if (logMaxRemainder !== 0 && (logMaxRemainder < 0.3 || logMaxRemainder > 0.9)) {
autoMax = math.pow(base, log(max, base) + 0.2);
} else {
autoMax = math.pow(base, math.ceil(log(max, base)));
}
return autoMax;
},
pan: function (delta) {
var range = this.translateRange(delta);
return this.limitRange(range.min, range.max, this.totalMin, this.totalMax, -delta);
},
pointsRange: function (start, end) {
var startValue = this.getValue(start);
var endValue = this.getValue(end);
var min = math.min(startValue, endValue);
var max = math.max(startValue, endValue);
return {
min: min,
max: max
};
},
zoomRange: function (delta) {
var options = this.options;
var newRange = this.scaleRange(delta);
var totalMax = this.totalMax;
var totalMin = this.totalMin;
var min = util.limitValue(newRange.min, totalMin, totalMax);
var max = util.limitValue(newRange.max, totalMin, totalMax);
var base = options.majorUnit;
var acceptOptionsRange = max > min && options.min && options.max && round(log(options.max, base) - log(options.min, base), DEFAULT_PRECISION) < 1;
var acceptNewRange = !(options.min === totalMin && options.max === totalMax) && round(log(max, base) - log(min, base), DEFAULT_PRECISION) >= 1;
if (acceptOptionsRange || acceptNewRange) {
return {
min: min,
max: max
};
}
},
_minorIntervalOptions: function (power) {
var base = this.options.majorUnit, value = math.pow(base, power), nextValue = math.pow(base, power + 1), difference = nextValue - value, minorStep = difference / this.options.minorUnit;
return {
value: value,
minorStep: minorStep
};
},
_lineOptions: function () {
var axis = this, options = axis.options, reverse = options.reverse, vertical = options.vertical, valueAxis = vertical ? Y : X, lineBox = axis.lineBox(), dir = vertical === reverse ? 1 : -1, startEdge = dir === 1 ? 1 : 2, lineSize = vertical ? lineBox.height() : lineBox.width(), step = dir * (lineSize / (axis.logMax - axis.logMin)), lineStart = lineBox[valueAxis + startEdge];
return {
step: step,
lineStart: lineStart,
lineBox: lineBox
};
}
});
dataviz.Gradients = {
glass: {
type: LINEAR,
rotation: 0,
stops: [
{
offset: 0,
color: WHITE,
opacity: 0
},
{
offset: 0.25,
color: WHITE,
opacity: 0.3
},
{
offset: 1,
color: WHITE,
opacity: 0
}
]
},
sharpBevel: {
type: RADIAL,
stops: [
{
offset: 0,
color: WHITE,
opacity: 0.55
},
{
offset: 0.65,
color: WHITE,
opacity: 0
},
{
offset: 0.95,
color: WHITE,
opacity: 0.25
}
]
},
roundedBevel: {
type: RADIAL,
stops: [
{
offset: 0.33,
color: WHITE,
opacity: 0.06
},
{
offset: 0.83,
color: WHITE,
opacity: 0.2
},
{
offset: 0.95,
color: WHITE,
opacity: 0
}
]
},
roundedGlass: {
type: RADIAL,
supportVML: false,
stops: [
{
offset: 0,
color: WHITE,
opacity: 0
},
{
offset: 0.5,
color: WHITE,
opacity: 0.3
},
{
offset: 0.99,
color: WHITE,
opacity: 0
}
]
},
sharpGlass: {
type: RADIAL,
supportVML: false,
stops: [
{
offset: 0,
color: WHITE,
opacity: 0.2
},
{
offset: 0.15,
color: WHITE,
opacity: 0.15
},
{
offset: 0.17,
color: WHITE,
opacity: 0.35
},
{
offset: 0.85,
color: WHITE,
opacity: 0.05
},
{
offset: 0.87,
color: WHITE,
opacity: 0.15
},
{
offset: 0.99,
color: WHITE,
opacity: 0
}
]
}
};
var ExportMixin = {
extend: function (proto, skipLegacy) {
if (!proto.exportVisual) {
throw new Error('Mixin target has no exportVisual method defined.');
}
proto.exportSVG = this.exportSVG;
proto.exportImage = this.exportImage;
proto.exportPDF = this.exportPDF;
if (!skipLegacy) {
proto.svg = this.svg;
proto.imageDataURL = this.imageDataURL;
}
},
exportSVG: function (options) {
return draw.exportSVG(this.exportVisual(), options);
},
exportImage: function (options) {
return draw.exportImage(this.exportVisual(options), options);
},
exportPDF: function (options) {
return draw.exportPDF(this.exportVisual(), options);
},
svg: function () {
if (draw.svg.Surface) {
return draw.svg._exportGroup(this.exportVisual());
} else {
throw new Error('SVG Export failed. Unable to export instantiate kendo.drawing.svg.Surface');
}
},
imageDataURL: function () {
if (!kendo.support.canvas) {
return null;
}
if (draw.canvas.Surface) {
var container = $('
').css({
display: 'none',
width: this.element.width(),
height: this.element.height()
}).appendTo(document.body);
var surface = new draw.canvas.Surface(container);
surface.draw(this.exportVisual());
var image = surface._rootElement.toDataURL();
surface.destroy();
container.remove();
return image;
} else {
throw new Error('Image Export failed. Unable to export instantiate kendo.drawing.canvas.Surface');
}
}
};
function autoMajorUnit(min, max) {
var diff = round(max - min, DEFAULT_PRECISION - 1);
if (diff === 0) {
if (max === 0) {
return 0.1;
}
diff = math.abs(max);
}
var scale = math.pow(10, math.floor(math.log(diff) / math.log(10))), relativeValue = round(diff / scale, DEFAULT_PRECISION), scaleMultiplier = 1;
if (relativeValue < 1.904762) {
scaleMultiplier = 0.2;
} else if (relativeValue < 4.761904) {
scaleMultiplier = 0.5;
} else if (relativeValue < 9.523809) {
scaleMultiplier = 1;
} else {
scaleMultiplier = 2;
}
return round(scale * scaleMultiplier, DEFAULT_PRECISION);
}
function rotatePoint(x, y, cx, cy, angle) {
var theta = angle * DEG_TO_RAD;
return new Point2D(cx + (x - cx) * math.cos(theta) + (y - cy) * math.sin(theta), cy - (x - cx) * math.sin(theta) + (y - cy) * math.cos(theta));
}
function boxDiff(r, s) {
if (r.x1 == s.x1 && r.y1 == s.y1 && r.x2 == s.x2 && r.y2 == s.y2) {
return s;
}
var a = math.min(r.x1, s.x1), b = math.max(r.x1, s.x1), c = math.min(r.x2, s.x2), d = math.max(r.x2, s.x2), e = math.min(r.y1, s.y1), f = math.max(r.y1, s.y1), g = math.min(r.y2, s.y2), h = math.max(r.y2, s.y2), result = [];
result[0] = Box2D(b, e, c, f);
result[1] = Box2D(a, f, b, g);
result[2] = Box2D(c, f, d, g);
result[3] = Box2D(b, g, c, h);
if (r.x1 == a && r.y1 == e || s.x1 == a && s.y1 == e) {
result[4] = Box2D(a, e, b, f);
result[5] = Box2D(c, g, d, h);
} else {
result[4] = Box2D(c, e, d, f);
result[5] = Box2D(a, g, b, h);
}
return $.grep(result, function (box) {
return box.height() > 0 && box.width() > 0;
})[0];
}
function inArray(value, array) {
return indexOf(value, array) != -1;
}
function ceil(value, step) {
return round(math.ceil(value / step) * step, DEFAULT_PRECISION);
}
function floor(value, step) {
return round(math.floor(value / step) * step, DEFAULT_PRECISION);
}
function round(value, precision) {
var power = math.pow(10, precision || 0);
return math.round(value * power) / power;
}
function log(y, x) {
return math.log(y) / math.log(x);
}
function remainderClose(value, divisor, ratio) {
var remainder = round(math.abs(value % divisor), DEFAULT_PRECISION), threshold = divisor * (1 - ratio);
return remainder === 0 || remainder > threshold;
}
function interpolateValue(start, end, progress) {
return round(start + (end - start) * progress, COORD_PRECISION);
}
function numericComparer(a, b) {
return a - b;
}
function autoFormat(format, value) {
if (format.match(FORMAT_REGEX)) {
return kendo.format.apply(this, arguments);
}
return kendo.toString(value, format);
}
function clockwise(v1, v2) {
return -v1.x * v2.y + v1.y * v2.x < 0;
}
function dateComparer(a, b) {
if (a && b) {
return a.getTime() - b.getTime();
}
return -1;
}
var CurveProcessor = function (closed) {
this.closed = closed;
};
CurveProcessor.prototype = CurveProcessor.fn = {
WEIGHT: 0.333,
EXTREMUM_ALLOWED_DEVIATION: 0.01,
process: function (dataPoints) {
var that = this, closed = that.closed, points = dataPoints.slice(0), length = points.length, segments = [], p0, p1, p2, controlPoints, initialControlPoint, lastControlPoint, tangent;
if (length > 2) {
that.removeDuplicates(0, points);
length = points.length;
}
if (length < 2 || length == 2 && points[0].equals(points[1])) {
return segments;
}
p0 = points[0];
p1 = points[1];
p2 = points[2];
segments.push(new draw.Segment(p0));
while (p0.equals(points[length - 1])) {
closed = true;
points.pop();
length--;
}
if (length == 2) {
tangent = that.tangent(p0, p1, X, Y);
last(segments).controlOut(that.firstControlPoint(tangent, p0, p1, X, Y));
segments.push(new draw.Segment(p1, that.secondControlPoint(tangent, p0, p1, X, Y)));
return segments;
}
if (closed) {
p0 = points[length - 1];
p1 = points[0];
p2 = points[1];
controlPoints = that.controlPoints(p0, p1, p2);
initialControlPoint = controlPoints[1];
lastControlPoint = controlPoints[0];
} else {
tangent = that.tangent(p0, p1, X, Y);
initialControlPoint = that.firstControlPoint(tangent, p0, p1, X, Y);
}
var cp0 = initialControlPoint;
for (var idx = 0; idx <= length - 3; idx++) {
that.removeDuplicates(idx, points);
length = points.length;
if (idx + 3 <= length) {
p0 = points[idx];
p1 = points[idx + 1];
p2 = points[idx + 2];
controlPoints = that.controlPoints(p0, p1, p2);
last(segments).controlOut(cp0);
cp0 = controlPoints[1];
var cp1 = controlPoints[0];
segments.push(new draw.Segment(p1, cp1));
}
}
if (closed) {
p0 = points[length - 2];
p1 = points[length - 1];
p2 = points[0];
controlPoints = that.controlPoints(p0, p1, p2);
last(segments).controlOut(cp0);
segments.push(new draw.Segment(p1, controlPoints[0]));
last(segments).controlOut(controlPoints[1]);
segments.push(new draw.Segment(p2, lastControlPoint));
} else {
tangent = that.tangent(p1, p2, X, Y);
last(segments).controlOut(cp0);
segments.push(new draw.Segment(p2, that.secondControlPoint(tangent, p1, p2, X, Y)));
}
return segments;
},
removeDuplicates: function (idx, points) {
while (points[idx].equals(points[idx + 1]) || points[idx + 1].equals(points[idx + 2])) {
points.splice(idx + 1, 1);
}
},
invertAxis: function (p0, p1, p2) {
var that = this, fn, y2, invertAxis = false;
if (p0.x === p1.x) {
invertAxis = true;
} else if (p1.x === p2.x) {
if (p1.y < p2.y && p0.y <= p1.y || p2.y < p1.y && p1.y <= p0.y) {
invertAxis = true;
}
} else {
fn = that.lineFunction(p0, p1);
y2 = that.calculateFunction(fn, p2.x);
if (!(p0.y <= p1.y && p2.y <= y2) && !(p1.y <= p0.y && p2.y >= y2)) {
invertAxis = true;
}
}
return invertAxis;
},
isLine: function (p0, p1, p2) {
var that = this, fn = that.lineFunction(p0, p1), y2 = that.calculateFunction(fn, p2.x);
return p0.x == p1.x && p1.x == p2.x || round(y2, 1) === round(p2.y, 1);
},
lineFunction: function (p1, p2) {
var a = (p2.y - p1.y) / (p2.x - p1.x), b = p1.y - a * p1.x;
return [
b,
a
];
},
controlPoints: function (p0, p1, p2) {
var that = this, xField = X, yField = Y, restrict = false, switchOrientation = false, tangent, monotonic, firstControlPoint, secondControlPoint, allowedDeviation = that.EXTREMUM_ALLOWED_DEVIATION;
if (that.isLine(p0, p1, p2)) {
tangent = that.tangent(p0, p1, X, Y);
} else {
monotonic = {
x: that.isMonotonicByField(p0, p1, p2, X),
y: that.isMonotonicByField(p0, p1, p2, Y)
};
if (monotonic.x && monotonic.y) {
tangent = that.tangent(p0, p2, X, Y);
restrict = true;
} else {
if (that.invertAxis(p0, p1, p2)) {
xField = Y;
yField = X;
}
if (monotonic[xField]) {
tangent = 0;
} else {
var sign;
if (p2[yField] < p0[yField] && p0[yField] <= p1[yField] || p0[yField] < p2[yField] && p1[yField] <= p0[yField]) {
sign = that.sign((p2[yField] - p0[yField]) * (p1[xField] - p0[xField]));
} else {
sign = -that.sign((p2[xField] - p0[xField]) * (p1[yField] - p0[yField]));
}
tangent = allowedDeviation * sign;
switchOrientation = true;
}
}
}
secondControlPoint = that.secondControlPoint(tangent, p0, p1, xField, yField);
if (switchOrientation) {
var oldXField = xField;
xField = yField;
yField = oldXField;
}
firstControlPoint = that.firstControlPoint(tangent, p1, p2, xField, yField);
if (restrict) {
that.restrictControlPoint(p0, p1, secondControlPoint, tangent);
that.restrictControlPoint(p1, p2, firstControlPoint, tangent);
}
return [
secondControlPoint,
firstControlPoint
];
},
sign: function (x) {
return x <= 0 ? -1 : 1;
},
restrictControlPoint: function (p1, p2, cp, tangent) {
if (p1.y < p2.y) {
if (p2.y < cp.y) {
cp.x = p1.x + (p2.y - p1.y) / tangent;
cp.y = p2.y;
} else if (cp.y < p1.y) {
cp.x = p2.x - (p2.y - p1.y) / tangent;
cp.y = p1.y;
}
} else {
if (cp.y < p2.y) {
cp.x = p1.x - (p1.y - p2.y) / tangent;
cp.y = p2.y;
} else if (p1.y < cp.y) {
cp.x = p2.x + (p1.y - p2.y) / tangent;
cp.y = p1.y;
}
}
},
tangent: function (p0, p1, xField, yField) {
var tangent, x = p1[xField] - p0[xField], y = p1[yField] - p0[yField];
if (x === 0) {
tangent = 0;
} else {
tangent = y / x;
}
return tangent;
},
isMonotonicByField: function (p0, p1, p2, field) {
return p2[field] > p1[field] && p1[field] > p0[field] || p2[field] < p1[field] && p1[field] < p0[field];
},
firstControlPoint: function (tangent, p0, p3, xField, yField) {
var that = this, t1 = p0[xField], t2 = p3[xField], distance = (t2 - t1) * that.WEIGHT;
return that.point(t1 + distance, p0[yField] + distance * tangent, xField, yField);
},
secondControlPoint: function (tangent, p0, p3, xField, yField) {
var that = this, t1 = p0[xField], t2 = p3[xField], distance = (t2 - t1) * that.WEIGHT;
return that.point(t2 - distance, p3[yField] - distance * tangent, xField, yField);
},
point: function (xValue, yValue, xField, yField) {
var controlPoint = new geom.Point();
controlPoint[xField] = xValue;
controlPoint[yField] = yValue;
return controlPoint;
},
calculateFunction: function (fn, x) {
var result = 0, length = fn.length;
for (var i = 0; i < length; i++) {
result += Math.pow(x, i) * fn[i];
}
return result;
}
};
function mwDelta(e) {
var origEvent = e.originalEvent, delta = 0;
if (origEvent.wheelDelta) {
delta = -origEvent.wheelDelta / 120;
delta = delta > 0 ? math.ceil(delta) : math.floor(delta);
}
if (origEvent.detail) {
delta = round(origEvent.detail / 3);
}
return delta;
}
function decodeEntities(text) {
if (!text || !text.indexOf || text.indexOf('&') < 0) {
return text;
} else {
var element = decodeEntities._element;
element.innerHTML = text;
return element.textContent || element.innerText;
}
}
function alignPathToPixel(path) {
if (!kendo.support.vml) {
var offset = 0.5;
if (path.options.stroke && defined(path.options.stroke.width)) {
if (path.options.stroke.width % 2 === 0) {
offset = 0;
}
}
for (var i = 0; i < path.segments.length; i++) {
path.segments[i].anchor().round(0).translate(offset, offset);
}
}
return path;
}
function innerRadialStops(options) {
var stops = options.stops, usedSpace = options.innerRadius / options.radius * 100, i, length = stops.length, currentStop, currentStops = [];
for (i = 0; i < length; i++) {
currentStop = deepExtend({}, stops[i]);
currentStop.offset = (currentStop.offset * (100 - usedSpace) + usedSpace) / 100;
currentStops.push(currentStop);
}
return currentStops;
}
function rectToBox(rect) {
var origin = rect.origin;
var bottomRight = rect.bottomRight();
return new Box2D(origin.x, origin.y, bottomRight.x, bottomRight.y);
}
decodeEntities._element = document.createElement('span');
deepExtend(kendo.dataviz, {
AXIS_LABEL_CLICK: AXIS_LABEL_CLICK,
COORD_PRECISION: COORD_PRECISION,
DEFAULT_PRECISION: DEFAULT_PRECISION,
DEFAULT_WIDTH: DEFAULT_WIDTH,
DEFAULT_HEIGHT: DEFAULT_HEIGHT,
DEFAULT_FONT: DEFAULT_FONT,
INITIAL_ANIMATION_DURATION: INITIAL_ANIMATION_DURATION,
NOTE_CLICK: NOTE_CLICK,
NOTE_HOVER: NOTE_HOVER,
CLIP: CLIP,
Axis: Axis,
AxisLabel: AxisLabel,
Box2D: Box2D,
BoxElement: BoxElement,
ChartElement: ChartElement,
CurveProcessor: CurveProcessor,
ExportMixin: ExportMixin,
FloatElement: FloatElement,
LogarithmicAxis: LogarithmicAxis,
Note: Note,
NumericAxis: NumericAxis,
Point2D: Point2D,
Ring: Ring,
RootElement: RootElement,
Sector: Sector,
ShapeBuilder: ShapeBuilder,
ShapeElement: ShapeElement,
Text: Text,
TextBox: TextBox,
Title: Title,
alignPathToPixel: alignPathToPixel,
autoFormat: autoFormat,
autoMajorUnit: autoMajorUnit,
boxDiff: boxDiff,
dateComparer: dateComparer,
decodeEntities: decodeEntities,
getSpacing: getSpacing,
inArray: inArray,
interpolateValue: interpolateValue,
mwDelta: mwDelta,
rectToBox: rectToBox,
rotatePoint: rotatePoint,
round: round,
ceil: ceil,
floor: floor
});
}(window.kendo.jQuery));
return window.kendo;
}, typeof define == 'function' && define.amd ? define : function (a1, a2, a3) {
(a3 || a2)();
}));
(function (f, define) {
define('kendo.dataviz.themes', ['kendo.dataviz.core'], f);
}(function () {
var __meta__ = {
id: 'dataviz.themes',
name: 'Themes',
description: 'Built-in themes for the DataViz widgets',
category: 'dataviz',
depends: ['dataviz.core'],
hidden: true
};
(function ($) {
var kendo = window.kendo, ui = kendo.dataviz.ui, deepExtend = kendo.deepExtend;
var BAR_GAP = 1.5, BAR_SPACING = 0.4, BLACK = '#000', SANS = 'Arial,Helvetica,sans-serif', SANS11 = '11px ' + SANS, SANS12 = '12px ' + SANS, SANS16 = '16px ' + SANS, WHITE = '#fff';
var chartBaseTheme = {
title: { font: SANS16 },
legend: { labels: { font: SANS12 } },
seriesDefaults: {
visible: true,
labels: { font: SANS11 },
donut: { margin: 1 },
line: { width: 2 },
vericalLine: { width: 2 },
scatterLine: { width: 1 },
area: {
opacity: 0.4,
markers: {
visible: false,
size: 6
},
highlight: {
markers: {
border: {
color: '#fff',
opacity: 1,
width: 1
}
}
},
line: {
opacity: 1,
width: 0
}
},
verticalArea: {
opacity: 0.4,
markers: {
visible: false,
size: 6
},
line: {
opacity: 1,
width: 0
}
},
radarLine: {
width: 2,
markers: { visible: false }
},
radarArea: {
opacity: 0.5,
markers: {
visible: false,
size: 6
},
line: {
opacity: 1,
width: 0
}
},
candlestick: {
line: {
width: 1,
color: BLACK
},
border: {
width: 1,
_brightness: 0.8
},
gap: 1,
spacing: 0.3,
downColor: WHITE,
highlight: {
line: { width: 2 },
border: {
width: 2,
opacity: 1
}
}
},
ohlc: {
line: { width: 1 },
gap: 1,
spacing: 0.3,
highlight: {
line: {
width: 3,
opacity: 1
}
}
},
bubble: {
opacity: 0.6,
border: { width: 0 },
labels: { background: 'transparent' }
},
bar: {
gap: BAR_GAP,
spacing: BAR_SPACING
},
column: {
gap: BAR_GAP,
spacing: BAR_SPACING
},
rangeColumn: {
gap: BAR_GAP,
spacing: BAR_SPACING
},
rangeBar: {
gap: BAR_GAP,
spacing: BAR_SPACING
},
waterfall: {
gap: 0.5,
spacing: BAR_SPACING,
line: {
width: 1,
color: BLACK
}
},
horizontalWaterfall: {
gap: 0.5,
spacing: BAR_SPACING,
line: {
width: 1,
color: BLACK
}
},
bullet: {
gap: BAR_GAP,
spacing: BAR_SPACING,
target: { color: '#ff0000' }
},
verticalBullet: {
gap: BAR_GAP,
spacing: BAR_SPACING,
target: { color: '#ff0000' }
},
boxPlot: {
outliersField: '',
meanField: '',
whiskers: {
width: 1,
color: BLACK
},
mean: {
width: 1,
color: BLACK
},
median: {
width: 1,
color: BLACK
},
border: {
width: 1,
_brightness: 0.8
},
gap: 1,
spacing: 0.3,
downColor: WHITE,
highlight: {
whiskers: { width: 2 },
border: {
width: 2,
opacity: 1
}
}
},
funnel: {
labels: {
color: '',
background: ''
}
},
notes: {
icon: { border: { width: 1 } },
label: {
padding: 3,
font: SANS12
},
line: {
length: 10,
width: 1
},
visible: true
}
},
categoryAxis: { majorGridLines: { visible: true } },
axisDefaults: {
labels: { font: SANS12 },
title: {
font: SANS16,
margin: 5
},
crosshair: { tooltip: { font: SANS12 } },
notes: {
icon: {
size: 7,
border: { width: 1 }
},
label: {
padding: 3,
font: SANS12
},
line: {
length: 10,
width: 1
},
visible: true
}
},
tooltip: { font: SANS12 },
navigator: {
pane: {
height: 90,
margin: { top: 10 }
}
}
};
var gaugeBaseTheme = { scale: { labels: { font: SANS12 } } };
var diagramBaseTheme = {
shapeDefaults: {
hover: { opacity: 0.2 },
stroke: { width: 0 }
},
editable: {
resize: {
handles: {
width: 7,
height: 7
}
}
},
selectable: {
stroke: {
width: 1,
dashType: 'dot'
}
},
connectionDefaults: {
stroke: { width: 2 },
selection: {
handles: {
width: 8,
height: 8
}
},
editable: {
tools: [
'edit',
'delete'
]
}
}
};
var themes = ui.themes, registerTheme = ui.registerTheme = function (themeName, options) {
var result = {};
result.chart = deepExtend({}, chartBaseTheme, options.chart);
result.gauge = deepExtend({}, gaugeBaseTheme, options.gauge);
result.diagram = deepExtend({}, diagramBaseTheme, options.diagram);
result.treeMap = deepExtend({}, options.treeMap);
var defaults = result.chart.seriesDefaults;
defaults.verticalLine = deepExtend({}, defaults.line);
defaults.verticalArea = deepExtend({}, defaults.area);
defaults.polarArea = deepExtend({}, defaults.radarArea);
defaults.polarLine = deepExtend({}, defaults.radarLine);
themes[themeName] = result;
};
registerTheme('black', {
chart: {
title: { color: WHITE },
legend: {
labels: { color: WHITE },
inactiveItems: {
labels: { color: '#919191' },
markers: { color: '#919191' }
}
},
seriesDefaults: {
labels: { color: WHITE },
errorBars: { color: WHITE },
notes: {
icon: {
background: '#3b3b3b',
border: { color: '#8e8e8e' }
},
label: { color: WHITE },
line: { color: '#8e8e8e' }
},
pie: { overlay: { gradient: 'sharpBevel' } },
donut: { overlay: { gradient: 'sharpGlass' } },
line: { markers: { background: '#3d3d3d' } },
scatter: { markers: { background: '#3d3d3d' } },
scatterLine: { markers: { background: '#3d3d3d' } },
waterfall: { line: { color: '#8e8e8e' } },
horizontalWaterfall: { line: { color: '#8e8e8e' } },
candlestick: {
downColor: '#555',
line: { color: WHITE },
border: {
_brightness: 1.5,
opacity: 1
},
highlight: {
border: {
color: WHITE,
opacity: 0.2
}
}
},
ohlc: { line: { color: WHITE } }
},
chartArea: { background: '#3d3d3d' },
seriesColors: [
'#0081da',
'#3aafff',
'#99c900',
'#ffeb3d',
'#b20753',
'#ff4195'
],
axisDefaults: {
line: { color: '#8e8e8e' },
labels: { color: WHITE },
majorGridLines: { color: '#545454' },
minorGridLines: { color: '#454545' },
title: { color: WHITE },
crosshair: { color: '#8e8e8e' },
notes: {
icon: {
background: '#3b3b3b',
border: { color: '#8e8e8e' }
},
label: { color: WHITE },
line: { color: '#8e8e8e' }
}
}
},
gauge: {
pointer: { color: '#0070e4' },
scale: {
rangePlaceholderColor: '#1d1d1d',
labels: { color: WHITE },
minorTicks: { color: WHITE },
majorTicks: { color: WHITE },
line: { color: WHITE }
}
},
diagram: {
shapeDefaults: {
fill: { color: '#0066cc' },
connectorDefaults: {
fill: { color: WHITE },
stroke: { color: '#384049' },
hover: {
fill: { color: '#3d3d3d' },
stroke: { color: '#efefef' }
}
},
content: { color: WHITE }
},
editable: {
resize: {
handles: {
fill: { color: '#3d3d3d' },
stroke: { color: WHITE },
hover: {
fill: { color: WHITE },
stroke: { color: WHITE }
}
}
},
rotate: {
thumb: {
stroke: { color: WHITE },
fill: { color: WHITE }
}
}
},
selectable: { stroke: { color: WHITE } },
connectionDefaults: {
stroke: { color: WHITE },
content: { color: WHITE },
selection: {
handles: {
fill: { color: '#3d3d3d' },
stroke: { color: '#efefef' }
}
}
}
},
treeMap: {
colors: [
[
'#0081da',
'#314b5c'
],
[
'#3aafff',
'#3c5464'
],
[
'#99c900',
'#4f5931'
],
[
'#ffeb3d',
'#64603d'
],
[
'#b20753',
'#543241'
],
[
'#ff4195',
'#643e4f'
]
]
}
});
registerTheme('blueopal', {
chart: {
title: { color: '#293135' },
legend: {
labels: { color: '#293135' },
inactiveItems: {
labels: { color: '#27A5BA' },
markers: { color: '#27A5BA' }
}
},
seriesDefaults: {
labels: {
color: BLACK,
background: WHITE,
opacity: 0.5
},
errorBars: { color: '#293135' },
candlestick: {
downColor: '#c4d0d5',
line: { color: '#9aabb2' }
},
waterfall: { line: { color: '#9aabb2' } },
horizontalWaterfall: { line: { color: '#9aabb2' } },
notes: {
icon: {
background: 'transparent',
border: { color: '#9aabb2' }
},
label: { color: '#293135' },
line: { color: '#9aabb2' }
}
},
seriesColors: [
'#0069a5',
'#0098ee',
'#7bd2f6',
'#ffb800',
'#ff8517',
'#e34a00'
],
axisDefaults: {
line: { color: '#9aabb2' },
labels: { color: '#293135' },
majorGridLines: { color: '#c4d0d5' },
minorGridLines: { color: '#edf1f2' },
title: { color: '#293135' },
crosshair: { color: '#9aabb2' },
notes: {
icon: {
background: 'transparent',
border: { color: '#9aabb2' }
},
label: { color: '#293135' },
line: { color: '#9aabb2' }
}
}
},
gauge: {
pointer: { color: '#005c83' },
scale: {
rangePlaceholderColor: '#daecf4',
labels: { color: '#293135' },
minorTicks: { color: '#293135' },
majorTicks: { color: '#293135' },
line: { color: '#293135' }
}
},
diagram: {
shapeDefaults: {
fill: { color: '#7ec6e3' },
connectorDefaults: {
fill: { color: '#003f59' },
stroke: { color: WHITE },
hover: {
fill: { color: WHITE },
stroke: { color: '#003f59' }
}
},
content: { color: '#293135' }
},
editable: {
resize: {
handles: {
fill: { color: WHITE },
stroke: { color: '#003f59' },
hover: {
fill: { color: '#003f59' },
stroke: { color: '#003f59' }
}
}
},
rotate: {
thumb: {
stroke: { color: '#003f59' },
fill: { color: '#003f59' }
}
}
},
selectable: { stroke: { color: '#003f59' } },
connectionDefaults: {
stroke: { color: '#003f59' },
content: { color: '#293135' },
selection: {
handles: {
fill: { color: '#3d3d3d' },
stroke: { color: '#efefef' }
}
}
}
},
treeMap: {
colors: [
[
'#0069a5',
'#bad7e7'
],
[
'#0098ee',
'#b9e0f5'
],
[
'#7bd2f6',
'#ceeaf6'
],
[
'#ffb800',
'#e6e3c4'
],
[
'#ff8517',
'#e4d8c8'
],
[
'#e34a00',
'#ddccc2'
]
]
}
});
registerTheme('highcontrast', {
chart: {
title: { color: '#ffffff' },
legend: {
labels: { color: '#ffffff' },
inactiveItems: {
labels: { color: '#66465B' },
markers: { color: '#66465B' }
}
},
seriesDefaults: {
labels: { color: '#ffffff' },
errorBars: { color: '#ffffff' },
notes: {
icon: {
background: 'transparent',
border: { color: '#ffffff' }
},
label: { color: '#ffffff' },
line: { color: '#ffffff' }
},
pie: { overlay: { gradient: 'sharpGlass' } },
donut: { overlay: { gradient: 'sharpGlass' } },
line: { markers: { background: '#2c232b' } },
scatter: { markers: { background: '#2c232b' } },
scatterLine: { markers: { background: '#2c232b' } },
area: { opacity: 0.5 },
waterfall: { line: { color: '#ffffff' } },
horizontalWaterfall: { line: { color: '#ffffff' } },
candlestick: {
downColor: '#664e62',
line: { color: '#ffffff' },
border: {
_brightness: 1.5,
opacity: 1
},
highlight: {
border: {
color: '#ffffff',
opacity: 1
}
}
},
ohlc: { line: { color: '#ffffff' } }
},
chartArea: { background: '#2c232b' },
seriesColors: [
'#a7008f',
'#ffb800',
'#3aafff',
'#99c900',
'#b20753',
'#ff4195'
],
axisDefaults: {
line: { color: '#ffffff' },
labels: { color: '#ffffff' },
majorGridLines: { color: '#664e62' },
minorGridLines: { color: '#4f394b' },
title: { color: '#ffffff' },
crosshair: { color: '#ffffff' },
notes: {
icon: {
background: 'transparent',
border: { color: '#ffffff' }
},
label: { color: '#ffffff' },
line: { color: '#ffffff' }
}
}
},
gauge: {
pointer: { color: '#a7008f' },
scale: {
rangePlaceholderColor: '#2c232b',
labels: { color: '#ffffff' },
minorTicks: { color: '#2c232b' },
majorTicks: { color: '#664e62' },
line: { color: '#ffffff' }
}
},
diagram: {
shapeDefaults: {
fill: { color: '#a7018f' },
connectorDefaults: {
fill: { color: WHITE },
stroke: { color: '#2c232b' },
hover: {
fill: { color: '#2c232b' },
stroke: { color: WHITE }
}
},
content: { color: WHITE }
},
editable: {
resize: {
handles: {
fill: { color: '#2c232b' },
stroke: { color: WHITE },
hover: {
fill: { color: WHITE },
stroke: { color: WHITE }
}
}
},
rotate: {
thumb: {
stroke: { color: WHITE },
fill: { color: WHITE }
}
}
},
selectable: { stroke: { color: WHITE } },
connectionDefaults: {
stroke: { color: WHITE },
content: { color: WHITE },
selection: {
handles: {
fill: { color: '#2c232b' },
stroke: { color: WHITE }
}
}
}
},
treeMap: {
colors: [
[
'#a7008f',
'#451c3f'
],
[
'#ffb800',
'#564122'
],
[
'#3aafff',
'#2f3f55'
],
[
'#99c900',
'#424422'
],
[
'#b20753',
'#471d33'
],
[
'#ff4195',
'#562940'
]
]
}
});
registerTheme('default', {
chart: {
title: { color: '#8e8e8e' },
legend: {
labels: { color: '#232323' },
inactiveItems: {
labels: { color: '#919191' },
markers: { color: '#919191' }
}
},
seriesDefaults: {
labels: {
color: BLACK,
background: WHITE,
opacity: 0.5
},
errorBars: { color: '#232323' },
candlestick: {
downColor: '#dedede',
line: { color: '#8d8d8d' }
},
waterfall: { line: { color: '#8e8e8e' } },
horizontalWaterfall: { line: { color: '#8e8e8e' } },
notes: {
icon: {
background: 'transparent',
border: { color: '#8e8e8e' }
},
label: { color: '#232323' },
line: { color: '#8e8e8e' }
}
},
seriesColors: [
'#ff6800',
'#a0a700',
'#ff8d00',
'#678900',
'#ffb53c',
'#396000'
],
axisDefaults: {
line: { color: '#8e8e8e' },
labels: { color: '#232323' },
minorGridLines: { color: '#f0f0f0' },
majorGridLines: { color: '#dfdfdf' },
title: { color: '#232323' },
crosshair: { color: '#8e8e8e' },
notes: {
icon: {
background: 'transparent',
border: { color: '#8e8e8e' }
},
label: { color: '#232323' },
line: { color: '#8e8e8e' }
}
}
},
gauge: {
pointer: { color: '#ea7001' },
scale: {
rangePlaceholderColor: '#dedede',
labels: { color: '#2e2e2e' },
minorTicks: { color: '#2e2e2e' },
majorTicks: { color: '#2e2e2e' },
line: { color: '#2e2e2e' }
}
},
diagram: {
shapeDefaults: {
fill: { color: '#e15613' },
connectorDefaults: {
fill: { color: '#282828' },
stroke: { color: WHITE },
hover: {
fill: { color: WHITE },
stroke: { color: '#282828' }
}
},
content: { color: '#2e2e2e' }
},
editable: {
resize: {
handles: {
fill: { color: WHITE },
stroke: { color: '#282828' },
hover: {
fill: { color: '#282828' },
stroke: { color: '#282828' }
}
}
},
rotate: {
thumb: {
stroke: { color: '#282828' },
fill: { color: '#282828' }
}
}
},
selectable: { stroke: { color: '#a7018f' } },
connectionDefaults: {
stroke: { color: '#282828' },
content: { color: '#2e2e2e' },
selection: {
handles: {
fill: { color: WHITE },
stroke: { color: '#282828' }
}
}
}
},
treeMap: {
colors: [
[
'#ff6800',
'#edcfba'
],
[
'#a0a700',
'#dadcba'
],
[
'#ff8d00',
'#edd7ba'
],
[
'#678900',
'#cfd6ba'
],
[
'#ffb53c',
'#eddfc6'
],
[
'#396000',
'#c6ceba'
]
]
}
});
registerTheme('silver', {
chart: {
title: { color: '#4e5968' },
legend: {
labels: { color: '#4e5968' },
inactiveItems: {
labels: { color: '#B1BCC8' },
markers: { color: '#B1BCC8' }
}
},
seriesDefaults: {
labels: {
color: '#293135',
background: '#eaeaec',
opacity: 0.5
},
errorBars: { color: '#4e5968' },
notes: {
icon: {
background: 'transparent',
border: { color: '#4e5968' }
},
label: { color: '#4e5968' },
line: { color: '#4e5968' }
},
line: { markers: { background: '#eaeaec' } },
scatter: { markers: { background: '#eaeaec' } },
scatterLine: { markers: { background: '#eaeaec' } },
pie: { connectors: { color: '#A6B1C0' } },
donut: { connectors: { color: '#A6B1C0' } },
waterfall: { line: { color: '#a6b1c0' } },
horizontalWaterfall: { line: { color: '#a6b1c0' } },
candlestick: { downColor: '#a6afbe' }
},
chartArea: { background: '#eaeaec' },
seriesColors: [
'#007bc3',
'#76b800',
'#ffae00',
'#ef4c00',
'#a419b7',
'#430B62'
],
axisDefaults: {
line: { color: '#a6b1c0' },
labels: { color: '#4e5968' },
majorGridLines: { color: '#dcdcdf' },
minorGridLines: { color: '#eeeeef' },
title: { color: '#4e5968' },
crosshair: { color: '#a6b1c0' },
notes: {
icon: {
background: 'transparent',
border: { color: '#4e5968' }
},
label: { color: '#4e5968' },
line: { color: '#4e5968' }
}
}
},
gauge: {
pointer: { color: '#0879c0' },
scale: {
rangePlaceholderColor: '#f3f3f4',
labels: { color: '#515967' },
minorTicks: { color: '#515967' },
majorTicks: { color: '#515967' },
line: { color: '#515967' }
}
},
diagram: {
shapeDefaults: {
fill: { color: '#1c82c2' },
connectorDefaults: {
fill: { color: '#515967' },
stroke: { color: WHITE },
hover: {
fill: { color: WHITE },
stroke: { color: '#282828' }
}
},
content: { color: '#515967' }
},
editable: {
resize: {
handles: {
fill: { color: WHITE },
stroke: { color: '#515967' },
hover: {
fill: { color: '#515967' },
stroke: { color: '#515967' }
}
}
},
rotate: {
thumb: {
stroke: { color: '#515967' },
fill: { color: '#515967' }
}
}
},
selectable: { stroke: { color: '#515967' } },
connectionDefaults: {
stroke: { color: '#515967' },
content: { color: '#515967' },
selection: {
handles: {
fill: { color: WHITE },
stroke: { color: '#515967' }
}
}
}
},
treeMap: {
colors: [
[
'#007bc3',
'#c2dbea'
],
[
'#76b800',
'#dae7c3'
],
[
'#ffae00',
'#f5e5c3'
],
[
'#ef4c00',
'#f2d2c3'
],
[
'#a419b7',
'#e3c7e8'
],
[
'#430b62',
'#d0c5d7'
]
]
}
});
registerTheme('metro', {
chart: {
title: { color: '#777777' },
legend: {
labels: { color: '#777777' },
inactiveItems: {
labels: { color: '#CBCBCB' },
markers: { color: '#CBCBCB' }
}
},
seriesDefaults: {
labels: { color: BLACK },
errorBars: { color: '#777777' },
notes: {
icon: {
background: 'transparent',
border: { color: '#777777' }
},
label: { color: '#777777' },
line: { color: '#777777' }
},
candlestick: {
downColor: '#c7c7c7',
line: { color: '#787878' }
},
waterfall: { line: { color: '#c7c7c7' } },
horizontalWaterfall: { line: { color: '#c7c7c7' } },
overlay: { gradient: 'none' },
border: { _brightness: 1 }
},
seriesColors: [
'#8ebc00',
'#309b46',
'#25a0da',
'#ff6900',
'#e61e26',
'#d8e404',
'#16aba9',
'#7e51a1',
'#313131',
'#ed1691'
],
axisDefaults: {
line: { color: '#c7c7c7' },
labels: { color: '#777777' },
minorGridLines: { color: '#c7c7c7' },
majorGridLines: { color: '#c7c7c7' },
title: { color: '#777777' },
crosshair: { color: '#c7c7c7' },
notes: {
icon: {
background: 'transparent',
border: { color: '#777777' }
},
label: { color: '#777777' },
line: { color: '#777777' }
}
}
},
gauge: {
pointer: { color: '#8ebc00' },
scale: {
rangePlaceholderColor: '#e6e6e6',
labels: { color: '#777' },
minorTicks: { color: '#777' },
majorTicks: { color: '#777' },
line: { color: '#777' }
}
},
diagram: {
shapeDefaults: {
fill: { color: '#8ebc00' },
connectorDefaults: {
fill: { color: BLACK },
stroke: { color: WHITE },
hover: {
fill: { color: WHITE },
stroke: { color: BLACK }
}
},
content: { color: '#777' }
},
editable: {
resize: {
handles: {
fill: { color: WHITE },
stroke: { color: '#787878' },
hover: {
fill: { color: '#787878' },
stroke: { color: '#787878' }
}
}
},
rotate: {
thumb: {
stroke: { color: '#787878' },
fill: { color: '#787878' }
}
}
},
selectable: { stroke: { color: '#515967' } },
connectionDefaults: {
stroke: { color: '#787878' },
content: { color: '#777' },
selection: {
handles: {
fill: { color: WHITE },
stroke: { color: '#787878' }
}
}
}
},
treeMap: {
colors: [
[
'#8ebc00',
'#e8f2cc'
],
[
'#309b46',
'#d6ebda'
],
[
'#25a0da',
'#d3ecf8'
],
[
'#ff6900',
'#ffe1cc'
],
[
'#e61e26',
'#fad2d4'
],
[
'#d8e404',
'#f7facd'
],
[
'#16aba9',
'#d0eeee'
],
[
'#7e51a1',
'#e5dcec'
],
[
'#313131',
'#d6d6d6'
],
[
'#ed1691',
'#fbd0e9'
]
]
}
});
registerTheme('metroblack', {
chart: {
title: { color: '#ffffff' },
legend: {
labels: { color: '#ffffff' },
inactiveItems: {
labels: { color: '#797979' },
markers: { color: '#797979' }
}
},
seriesDefaults: {
border: { _brightness: 1 },
labels: { color: '#ffffff' },
errorBars: { color: '#ffffff' },
notes: {
icon: {
background: 'transparent',
border: { color: '#cecece' }
},
label: { color: '#ffffff' },
line: { color: '#cecece' }
},
line: { markers: { background: '#0e0e0e' } },
bubble: { opacity: 0.6 },
scatter: { markers: { background: '#0e0e0e' } },
scatterLine: { markers: { background: '#0e0e0e' } },
candlestick: {
downColor: '#828282',
line: { color: '#ffffff' }
},
waterfall: { line: { color: '#cecece' } },
horizontalWaterfall: { line: { color: '#cecece' } },
overlay: { gradient: 'none' }
},
chartArea: { background: '#0e0e0e' },
seriesColors: [
'#00aba9',
'#309b46',
'#8ebc00',
'#ff6900',
'#e61e26',
'#d8e404',
'#25a0da',
'#7e51a1',
'#313131',
'#ed1691'
],
axisDefaults: {
line: { color: '#cecece' },
labels: { color: '#ffffff' },
minorGridLines: { color: '#2d2d2d' },
majorGridLines: { color: '#333333' },
title: { color: '#ffffff' },
crosshair: { color: '#cecece' },
notes: {
icon: {
background: 'transparent',
border: { color: '#cecece' }
},
label: { color: '#ffffff' },
line: { color: '#cecece' }
}
}
},
gauge: {
pointer: { color: '#00aba9' },
scale: {
rangePlaceholderColor: '#2d2d2d',
labels: { color: '#ffffff' },
minorTicks: { color: '#333333' },
majorTicks: { color: '#cecece' },
line: { color: '#cecece' }
}
},
diagram: {
shapeDefaults: {
fill: { color: '#00aba9' },
connectorDefaults: {
fill: { color: WHITE },
stroke: { color: '#0e0e0e' },
hover: {
fill: { color: '#0e0e0e' },
stroke: { color: WHITE }
}
},
content: { color: WHITE }
},
editable: {
resize: {
handles: {
fill: { color: '#0e0e0e' },
stroke: { color: '#787878' },
hover: {
fill: { color: '#787878' },
stroke: { color: '#787878' }
}
}
},
rotate: {
thumb: {
stroke: { color: WHITE },
fill: { color: WHITE }
}
}
},
selectable: { stroke: { color: '#787878' } },
connectionDefaults: {
stroke: { color: WHITE },
content: { color: WHITE },
selection: {
handles: {
fill: { color: '#0e0e0e' },
stroke: { color: WHITE }
}
}
}
},
treeMap: {
colors: [
[
'#00aba9',
'#0b2d2d'
],
[
'#309b46',
'#152a19'
],
[
'#8ebc00',
'#28310b'
],
[
'#ff6900',
'#3e200b'
],
[
'#e61e26',
'#391113'
],
[
'#d8e404',
'#36390c'
],
[
'#25a0da',
'#132b37'
],
[
'#7e51a1',
'#241b2b'
],
[
'#313131',
'#151515'
],
[
'#ed1691',
'#3b1028'
]
]
}
});
registerTheme('moonlight', {
chart: {
title: { color: '#ffffff' },
legend: {
labels: { color: '#ffffff' },
inactiveItems: {
labels: { color: '#A1A7AB' },
markers: { color: '#A1A7AB' }
}
},
seriesDefaults: {
labels: { color: '#ffffff' },
errorBars: { color: '#ffffff' },
notes: {
icon: {
background: 'transparent',
border: { color: '#8c909e' }
},
label: { color: '#ffffff' },
line: { color: '#8c909e' }
},
pie: { overlay: { gradient: 'sharpBevel' } },
donut: { overlay: { gradient: 'sharpGlass' } },
line: { markers: { background: '#212a33' } },
bubble: { opacity: 0.6 },
scatter: { markers: { background: '#212a33' } },
scatterLine: { markers: { background: '#212a33' } },
area: { opacity: 0.3 },
candlestick: {
downColor: '#757d87',
line: { color: '#ea9d06' },
border: {
_brightness: 1.5,
opacity: 1
},
highlight: {
border: {
color: WHITE,
opacity: 0.2
}
}
},
waterfall: { line: { color: '#8c909e' } },
horizontalWaterfall: { line: { color: '#8c909e' } },
ohlc: { line: { color: '#ea9d06' } }
},
chartArea: { background: '#212a33' },
seriesColors: [
'#ffca08',
'#ff710f',
'#ed2e24',
'#ff9f03',
'#e13c02',
'#a00201'
],
axisDefaults: {
line: { color: '#8c909e' },
minorTicks: { color: '#8c909e' },
majorTicks: { color: '#8c909e' },
labels: { color: '#ffffff' },
majorGridLines: { color: '#3e424d' },
minorGridLines: { color: '#2f3640' },
title: { color: '#ffffff' },
crosshair: { color: '#8c909e' },
notes: {
icon: {
background: 'transparent',
border: { color: '#8c909e' }
},
label: { color: '#ffffff' },
line: { color: '#8c909e' }
}
}
},
gauge: {
pointer: { color: '#f4af03' },
scale: {
rangePlaceholderColor: '#2f3640',
labels: { color: WHITE },
minorTicks: { color: '#8c909e' },
majorTicks: { color: '#8c909e' },
line: { color: '#8c909e' }
}
},
diagram: {
shapeDefaults: {
fill: { color: '#f3ae03' },
connectorDefaults: {
fill: { color: WHITE },
stroke: { color: '#414550' },
hover: {
fill: { color: '#414550' },
stroke: { color: WHITE }
}
},
content: { color: WHITE }
},
editable: {
resize: {
handles: {
fill: { color: '#414550' },
stroke: { color: WHITE },
hover: {
fill: { color: WHITE },
stroke: { color: WHITE }
}
}
},
rotate: {
thumb: {
stroke: { color: WHITE },
fill: { color: WHITE }
}
}
},
selectable: { stroke: { color: WHITE } },
connectionDefaults: {
stroke: { color: WHITE },
content: { color: WHITE },
selection: {
handles: {
fill: { color: '#414550' },
stroke: { color: WHITE }
}
}
}
},
treeMap: {
colors: [
[
'#ffca08',
'#4e4b2b'
],
[
'#ff710f',
'#4e392d'
],
[
'#ed2e24',
'#4b2c31'
],
[
'#ff9f03',
'#4e422a'
],
[
'#e13c02',
'#482e2a'
],
[
'#a00201',
'#3b232a'
]
]
}
});
registerTheme('uniform', {
chart: {
title: { color: '#686868' },
legend: {
labels: { color: '#686868' },
inactiveItems: {
labels: { color: '#B6B6B6' },
markers: { color: '#B6B6B6' }
}
},
seriesDefaults: {
labels: { color: '#686868' },
errorBars: { color: '#686868' },
notes: {
icon: {
background: 'transparent',
border: { color: '#9e9e9e' }
},
label: { color: '#686868' },
line: { color: '#9e9e9e' }
},
pie: { overlay: { gradient: 'sharpBevel' } },
donut: { overlay: { gradient: 'sharpGlass' } },
line: { markers: { background: '#ffffff' } },
bubble: { opacity: 0.6 },
scatter: { markers: { background: '#ffffff' } },
scatterLine: { markers: { background: '#ffffff' } },
area: { opacity: 0.3 },
candlestick: {
downColor: '#cccccc',
line: { color: '#cccccc' },
border: {
_brightness: 1.5,
opacity: 1
},
highlight: {
border: {
color: '#cccccc',
opacity: 0.2
}
}
},
waterfall: { line: { color: '#9e9e9e' } },
horizontalWaterfall: { line: { color: '#9e9e9e' } },
ohlc: { line: { color: '#cccccc' } }
},
chartArea: { background: '#ffffff' },
seriesColors: [
'#527aa3',
'#6f91b3',
'#8ca7c2',
'#a8bdd1',
'#c5d3e0',
'#e2e9f0'
],
axisDefaults: {
line: { color: '#9e9e9e' },
minorTicks: { color: '#aaaaaa' },
majorTicks: { color: '#888888' },
labels: { color: '#686868' },
majorGridLines: { color: '#dadada' },
minorGridLines: { color: '#e7e7e7' },
title: { color: '#686868' },
crosshair: { color: '#9e9e9e' },
notes: {
icon: {
background: 'transparent',
border: { color: '#9e9e9e' }
},
label: { color: '#686868' },
line: { color: '#9e9e9e' }
}
}
},
gauge: {
pointer: { color: '#527aa3' },
scale: {
rangePlaceholderColor: '#e7e7e7',
labels: { color: '#686868' },
minorTicks: { color: '#aaaaaa' },
majorTicks: { color: '#888888' },
line: { color: '#9e9e9e' }
}
},
diagram: {
shapeDefaults: {
fill: { color: '#d1d1d1' },
connectorDefaults: {
fill: { color: '#686868' },
stroke: { color: WHITE },
hover: {
fill: { color: WHITE },
stroke: { color: '#686868' }
}
},
content: { color: '#686868' }
},
editable: {
resize: {
handles: {
fill: { color: WHITE },
stroke: { color: '#686868' },
hover: {
fill: { color: '#686868' },
stroke: { color: '#686868' }
}
}
},
rotate: {
thumb: {
stroke: { color: '#686868' },
fill: { color: '#686868' }
}
}
},
selectable: { stroke: { color: '#686868' } },
connectionDefaults: {
stroke: { color: '#686868' },
content: { color: '#686868' },
selection: {
handles: {
fill: { color: WHITE },
stroke: { color: '#686868' }
}
}
}
},
treeMap: {
colors: [
[
'#527aa3',
'#d0d8e1'
],
[
'#6f91b3',
'#d6dde4'
],
[
'#8ca7c2',
'#dce1e7'
],
[
'#a8bdd1',
'#e2e6ea'
],
[
'#c5d3e0',
'#e7eaed'
],
[
'#e2e9f0',
'#edeff0'
]
]
}
});
registerTheme('bootstrap', {
chart: {
title: { color: '#333333' },
legend: {
labels: { color: '#333333' },
inactiveItems: {
labels: { color: '#999999' },
markers: { color: '#9A9A9A' }
}
},
seriesDefaults: {
labels: { color: '#333333' },
overlay: { gradient: 'none' },
errorBars: { color: '#343434' },
notes: {
icon: {
background: '#000000',
border: { color: '#000000' }
},
label: { color: '#333333' },
line: { color: '#000000' }
},
pie: { overlay: { gradient: 'none' } },
donut: { overlay: { gradient: 'none' } },
line: { markers: { background: '#ffffff' } },
bubble: { opacity: 0.6 },
scatter: { markers: { background: '#ffffff' } },
scatterLine: { markers: { background: '#ffffff' } },
area: { opacity: 0.8 },
candlestick: {
downColor: '#d0d0d0',
line: { color: '#333333' },
border: {
_brightness: 1.5,
opacity: 1
},
highlight: {
border: {
color: '#b8b8b8',
opacity: 0.2
}
}
},
waterfall: { line: { color: '#cccccc' } },
horizontalWaterfall: { line: { color: '#cccccc' } },
ohlc: { line: { color: '#333333' } }
},
chartArea: { background: '#ffffff' },
seriesColors: [
'#428bca',
'#5bc0de',
'#5cb85c',
'#f2b661',
'#e67d4a',
'#da3b36'
],
axisDefaults: {
line: { color: '#cccccc' },
minorTicks: { color: '#ebebeb' },
majorTicks: { color: '#cccccc' },
labels: { color: '#333333' },
majorGridLines: { color: '#cccccc' },
minorGridLines: { color: '#ebebeb' },
title: { color: '#333333' },
crosshair: { color: '#000000' },
notes: {
icon: {
background: '#000000',
border: { color: '#000000' }
},
label: { color: '#ffffff' },
line: { color: '#000000' }
}
}
},
gauge: {
pointer: { color: '#428bca' },
scale: {
rangePlaceholderColor: '#cccccc',
labels: { color: '#333333' },
minorTicks: { color: '#ebebeb' },
majorTicks: { color: '#cccccc' },
line: { color: '#cccccc' }
}
},
diagram: {
shapeDefaults: {
fill: { color: '#428bca' },
connectorDefaults: {
fill: { color: '#333333' },
stroke: { color: WHITE },
hover: {
fill: { color: WHITE },
stroke: { color: '#333333' }
}
},
content: { color: '#333333' }
},
editable: {
resize: {
handles: {
fill: { color: WHITE },
stroke: { color: '#333333' },
hover: {
fill: { color: '#333333' },
stroke: { color: '#333333' }
}
}
},
rotate: {
thumb: {
stroke: { color: '#333333' },
fill: { color: '#333333' }
}
}
},
selectable: { stroke: { color: '#333333' } },
connectionDefaults: {
stroke: { color: '#c4c4c4' },
content: { color: '#333333' },
selection: {
handles: {
fill: { color: WHITE },
stroke: { color: '#333333' }
},
stroke: { color: '#333333' }
}
}
},
treeMap: {
colors: [
[
'#428bca',
'#d1e0ec'
],
[
'#5bc0de',
'#d6eaf0'
],
[
'#5cb85c',
'#d6e9d6'
],
[
'#5cb85c',
'#f4e8d7'
],
[
'#e67d4a',
'#f2ddd3'
],
[
'#da3b36',
'#f0d0cf'
]
]
}
});
registerTheme('flat', {
chart: {
title: { color: '#4c5356' },
legend: {
labels: { color: '#4c5356' },
inactiveItems: {
labels: { color: '#CBCBCB' },
markers: { color: '#CBCBCB' }
}
},
seriesDefaults: {
labels: { color: '#4c5356' },
errorBars: { color: '#4c5356' },
notes: {
icon: {
background: 'transparent',
border: { color: '#cdcdcd' }
},
label: { color: '#4c5356' },
line: { color: '#cdcdcd' }
},
candlestick: {
downColor: '#c7c7c7',
line: { color: '#787878' }
},
area: { opacity: 0.9 },
waterfall: { line: { color: '#cdcdcd' } },
horizontalWaterfall: { line: { color: '#cdcdcd' } },
overlay: { gradient: 'none' },
border: { _brightness: 1 }
},
seriesColors: [
'#10c4b2',
'#ff7663',
'#ffb74f',
'#a2df53',
'#1c9ec4',
'#ff63a5',
'#1cc47b'
],
axisDefaults: {
line: { color: '#cdcdcd' },
labels: { color: '#4c5356' },
minorGridLines: { color: '#cdcdcd' },
majorGridLines: { color: '#cdcdcd' },
title: { color: '#4c5356' },
crosshair: { color: '#cdcdcd' },
notes: {
icon: {
background: 'transparent',
border: { color: '#cdcdcd' }
},
label: { color: '#4c5356' },
line: { color: '#cdcdcd' }
}
}
},
gauge: {
pointer: { color: '#10c4b2' },
scale: {
rangePlaceholderColor: '#cdcdcd',
labels: { color: '#4c5356' },
minorTicks: { color: '#4c5356' },
majorTicks: { color: '#4c5356' },
line: { color: '#4c5356' }
}
},
diagram: {
shapeDefaults: {
fill: { color: '#10c4b2' },
connectorDefaults: {
fill: { color: '#363940' },
stroke: { color: WHITE },
hover: {
fill: { color: WHITE },
stroke: { color: '#363940' }
}
},
content: { color: '#4c5356' }
},
editable: {
resize: {
handles: {
fill: { color: WHITE },
stroke: { color: '#363940' },
hover: {
fill: { color: '#363940' },
stroke: { color: '#363940' }
}
}
},
rotate: {
thumb: {
stroke: { color: '#363940' },
fill: { color: '#363940' }
}
}
},
selectable: { stroke: { color: '#363940' } },
connectionDefaults: {
stroke: { color: '#cdcdcd' },
content: { color: '#4c5356' },
selection: {
handles: {
fill: { color: WHITE },
stroke: { color: '#363940' }
},
stroke: { color: '#363940' }
}
}
},
treeMap: {
colors: [
[
'#10c4b2',
'#cff3f0'
],
[
'#ff7663',
'#ffe4e0'
],
[
'#ffb74f',
'#fff1dc'
],
[
'#a2df53',
'#ecf9dd'
],
[
'#1c9ec4',
'#d2ecf3'
],
[
'#ff63a5',
'#ffe0ed'
],
[
'#1cc47b',
'#d2f3e5'
]
]
}
});
registerTheme('material', {
chart: {
title: { color: '#444444' },
legend: {
labels: { color: '#444444' },
inactiveItems: {
labels: { color: '#CBCBCB' },
markers: { color: '#CBCBCB' }
}
},
seriesDefaults: {
labels: { color: '#444444' },
errorBars: { color: '#444444' },
notes: {
icon: {
background: 'transparent',
border: { color: '#e5e5e5' }
},
label: { color: '#444444' },
line: { color: '#e5e5e5' }
},
candlestick: {
downColor: '#c7c7c7',
line: { color: '#787878' }
},
area: { opacity: 0.9 },
waterfall: { line: { color: '#e5e5e5' } },
horizontalWaterfall: { line: { color: '#e5e5e5' } },
overlay: { gradient: 'none' },
border: { _brightness: 1 }
},
seriesColors: [
'#3f51b5',
'#03a9f4',
'#4caf50',
'#f9ce1d',
'#ff9800',
'#ff5722'
],
axisDefaults: {
line: { color: '#e5e5e5' },
labels: { color: '#444444' },
minorGridLines: { color: '#e5e5e5' },
majorGridLines: { color: '#e5e5e5' },
title: { color: '#444444' },
crosshair: { color: '#7f7f7f' },
notes: {
icon: {
background: 'transparent',
border: { color: '#e5e5e5' }
},
label: { color: '#444444' },
line: { color: '#e5e5e5' }
}
}
},
gauge: {
pointer: { color: '#3f51b5' },
scale: {
rangePlaceholderColor: '#e5e5e5',
labels: { color: '#444444' },
minorTicks: { color: '#444444' },
majorTicks: { color: '#444444' },
line: { color: '#444444' }
}
},
diagram: {
shapeDefaults: {
fill: { color: '#3f51b5' },
connectorDefaults: {
fill: { color: '#7f7f7f' },
stroke: { color: WHITE },
hover: {
fill: { color: WHITE },
stroke: { color: '#7f7f7f' }
}
},
content: { color: '#444444' }
},
editable: {
resize: {
handles: {
fill: { color: WHITE },
stroke: { color: '#444444' },
hover: {
fill: { color: '#444444' },
stroke: { color: '#444444' }
}
}
},
rotate: {
thumb: {
stroke: { color: '#444444' },
fill: { color: '#444444' }
}
}
},
selectable: { stroke: { color: '#444444' } },
connectionDefaults: {
stroke: { color: '#7f7f7f' },
content: { color: '#444444' },
selection: {
handles: {
fill: { color: WHITE },
stroke: { color: '#444444' }
},
stroke: { color: '#444444' }
}
}
},
treeMap: {
colors: [
[
'#3f51b5',
'#cff3f0'
],
[
'#03a9f4',
'#e5f6fe'
],
[
'#4caf50',
'#edf7ed'
],
[
'#f9ce1d',
'#fefae8'
],
[
'#ff9800',
'#fff4e5'
],
[
'#ff5722',
'#ffeee8'
]
]
}
});
registerTheme('materialblack', {
chart: {
title: { color: '#fff' },
legend: {
labels: { color: '#fff' },
inactiveItems: {
labels: { color: '#CBCBCB' },
markers: { color: '#CBCBCB' }
}
},
seriesDefaults: {
labels: { color: '#fff' },
errorBars: { color: '#fff' },
notes: {
icon: {
background: 'transparent',
border: { color: '#e5e5e5' }
},
label: { color: '#fff' },
line: { color: '#e5e5e5' }
},
candlestick: {
downColor: '#c7c7c7',
line: { color: '#787878' }
},
area: { opacity: 0.9 },
waterfall: { line: { color: '#4d4d4d' } },
horizontalWaterfall: { line: { color: '#4d4d4d' } },
overlay: { gradient: 'none' },
border: { _brightness: 1 }
},
chartArea: { background: '#1c1c1c' },
seriesColors: [
'#3f51b5',
'#03a9f4',
'#4caf50',
'#f9ce1d',
'#ff9800',
'#ff5722'
],
axisDefaults: {
line: { color: '#4d4d4d' },
labels: { color: '#fff' },
minorGridLines: { color: '#4d4d4d' },
majorGridLines: { color: '#4d4d4d' },
title: { color: '#fff' },
crosshair: { color: '#7f7f7f' },
notes: {
icon: {
background: 'transparent',
border: { color: '#4d4d4d' }
},
label: { color: '#fff' },
line: { color: '#4d4d4d' }
}
}
},
gauge: {
pointer: { color: '#3f51b5' },
scale: {
rangePlaceholderColor: '#4d4d4d',
labels: { color: '#fff' },
minorTicks: { color: '#fff' },
majorTicks: { color: '#fff' },
line: { color: '#fff' }
}
},
diagram: {
shapeDefaults: {
fill: { color: '#3f51b5' },
connectorDefaults: {
fill: { color: '#7f7f7f' },
stroke: { color: WHITE },
hover: {
fill: { color: WHITE },
stroke: { color: '#7f7f7f' }
}
},
content: { color: '#fff' }
},
editable: {
resize: {
handles: {
fill: { color: WHITE },
stroke: { color: '#fff' },
hover: {
fill: { color: '#fff' },
stroke: { color: '#fff' }
}
}
},
rotate: {
thumb: {
stroke: { color: '#fff' },
fill: { color: '#fff' }
}
}
},
selectable: { stroke: { color: '#fff' } },
connectionDefaults: {
stroke: { color: '#7f7f7f' },
content: { color: '#fff' },
selection: {
handles: {
fill: { color: WHITE },
stroke: { color: '#fff' }
},
stroke: { color: '#fff' }
}
}
},
treeMap: {
colors: [
[
'#3f51b5',
'#cff3f0'
],
[
'#03a9f4',
'#e5f6fe'
],
[
'#4caf50',
'#edf7ed'
],
[
'#f9ce1d',
'#fefae8'
],
[
'#ff9800',
'#fff4e5'
],
[
'#ff5722',
'#ffeee8'
]
]
}
});
(function () {
var TEXT = '#333333';
var INACTIVE = '#7f7f7f';
var INACTIVE_SHAPE = '#bdbdbd';
var AXIS = '#c8c8c8';
var AXIS_MINOR = '#dddddd';
var SERIES = [
'#008fd3',
'#99d101',
'#f39b02',
'#f05662',
'#c03c53',
'#acacac'
];
var SERIES_LIGHT = [
'#cbe8f5',
'#eaf5cb',
'#fceacc',
'#fbdcdf',
'#f2d7dc',
'#eeeeee'
];
var PRIMARY = SERIES[0];
var DIAGRAM_HOVER = WHITE;
function noteStyle() {
return {
icon: {
background: '#007cc0',
border: { color: '#007cc0' }
},
label: { color: '#ffffff' },
line: { color: AXIS }
};
}
registerTheme('fiori', {
chart: {
title: { color: TEXT },
legend: {
labels: { color: TEXT },
inactiveItems: {
labels: { color: INACTIVE },
markers: { color: INACTIVE }
}
},
seriesDefaults: {
labels: { color: TEXT },
errorBars: { color: TEXT },
notes: noteStyle(),
candlestick: {
downColor: AXIS,
line: { color: INACTIVE_SHAPE }
},
area: { opacity: 0.8 },
waterfall: { line: { color: AXIS } },
horizontalWaterfall: { line: { color: AXIS } },
overlay: { gradient: 'none' },
border: { _brightness: 1 }
},
seriesColors: SERIES,
axisDefaults: {
line: { color: AXIS },
labels: { color: TEXT },
minorGridLines: { color: AXIS_MINOR },
majorGridLines: { color: AXIS },
title: { color: TEXT },
crosshair: { color: INACTIVE },
notes: noteStyle()
}
},
gauge: {
pointer: { color: PRIMARY },
scale: {
rangePlaceholderColor: AXIS,
labels: { color: TEXT },
minorTicks: { color: TEXT },
majorTicks: { color: TEXT },
line: { color: TEXT }
}
},
diagram: {
shapeDefaults: {
fill: { color: PRIMARY },
connectorDefaults: {
fill: { color: TEXT },
stroke: { color: DIAGRAM_HOVER },
hover: {
fill: { color: DIAGRAM_HOVER },
stroke: { color: TEXT }
}
},
content: { color: TEXT }
},
editable: {
resize: {
handles: {
fill: { color: DIAGRAM_HOVER },
stroke: { color: INACTIVE_SHAPE },
hover: {
fill: { color: INACTIVE_SHAPE },
stroke: { color: INACTIVE_SHAPE }
}
}
},
rotate: {
thumb: {
stroke: { color: INACTIVE_SHAPE },
fill: { color: INACTIVE_SHAPE }
}
}
},
selectable: { stroke: { color: INACTIVE_SHAPE } },
connectionDefaults: {
stroke: { color: INACTIVE_SHAPE },
content: { color: INACTIVE_SHAPE },
selection: {
handles: {
fill: { color: DIAGRAM_HOVER },
stroke: { color: INACTIVE_SHAPE }
},
stroke: { color: INACTIVE_SHAPE }
}
}
},
treeMap: { colors: fuse(SERIES, SERIES_LIGHT) }
});
}());
(function () {
var TEXT = '#4e4e4e';
var INACTIVE = '#7f7f7f';
var INACTIVE_SHAPE = '#bdbdbd';
var AXIS = '#c8c8c8';
var AXIS_MINOR = '#e5e5e5';
var SERIES = [
'#0072c6',
'#5db2ff',
'#008a17',
'#82ba00',
'#ff8f32',
'#ac193d'
];
var SERIES_LIGHT = [
'#cbe2f3',
'#deeffe',
'#cbe7d0',
'#e5f0cb',
'#fee8d5',
'#eed0d7'
];
var PRIMARY = SERIES[0];
var DIAGRAM_HOVER = WHITE;
function noteStyle() {
return {
icon: {
background: '#00b0ff',
border: { color: '#00b0ff' }
},
label: { color: '#ffffff' },
line: { color: AXIS }
};
}
registerTheme('office365', {
chart: {
title: { color: TEXT },
legend: {
labels: { color: TEXT },
inactiveItems: {
labels: { color: INACTIVE },
markers: { color: INACTIVE }
}
},
seriesDefaults: {
labels: { color: TEXT },
errorBars: { color: TEXT },
notes: noteStyle(),
candlestick: {
downColor: AXIS,
line: { color: INACTIVE_SHAPE }
},
area: { opacity: 0.8 },
waterfall: { line: { color: AXIS } },
horizontalWaterfall: { line: { color: AXIS } },
overlay: { gradient: 'none' },
border: { _brightness: 1 }
},
seriesColors: SERIES,
axisDefaults: {
line: { color: AXIS },
labels: { color: TEXT },
minorGridLines: { color: AXIS_MINOR },
majorGridLines: { color: AXIS },
title: { color: TEXT },
crosshair: { color: INACTIVE },
notes: noteStyle()
}
},
gauge: {
pointer: { color: PRIMARY },
scale: {
rangePlaceholderColor: AXIS,
labels: { color: TEXT },
minorTicks: { color: TEXT },
majorTicks: { color: TEXT },
line: { color: TEXT }
}
},
diagram: {
shapeDefaults: {
fill: { color: PRIMARY },
connectorDefaults: {
fill: { color: TEXT },
stroke: { color: DIAGRAM_HOVER },
hover: {
fill: { color: DIAGRAM_HOVER },
stroke: { color: TEXT }
}
},
content: { color: TEXT }
},
editable: {
resize: {
handles: {
fill: { color: DIAGRAM_HOVER },
stroke: { color: INACTIVE_SHAPE },
hover: {
fill: { color: INACTIVE_SHAPE },
stroke: { color: INACTIVE_SHAPE }
}
}
},
rotate: {
thumb: {
stroke: { color: INACTIVE_SHAPE },
fill: { color: INACTIVE_SHAPE }
}
}
},
selectable: { stroke: { color: INACTIVE_SHAPE } },
connectionDefaults: {
stroke: { color: INACTIVE_SHAPE },
content: { color: INACTIVE_SHAPE },
selection: {
handles: {
fill: { color: DIAGRAM_HOVER },
stroke: { color: INACTIVE_SHAPE }
},
stroke: { color: INACTIVE_SHAPE }
}
}
},
treeMap: { colors: fuse(SERIES, SERIES_LIGHT) }
});
}());
(function () {
var TEXT = '#32364c';
var INACTIVE = '#7f7f7f';
var INACTIVE_SHAPE = '#bdbdbd';
var AXIS = '#dfe0e1';
var AXIS_MINOR = '#dfe0e1';
var SERIES = [
'#ff4350',
'#ff9ea5',
'#00acc1',
'#80deea',
'#ffbf46',
'#ffd78c'
];
var SERIES_LIGHT = [
'#ffd9dc',
'#ffeced',
'#cceef3',
'#e6f8fb',
'#fff2da',
'#fff7e8'
];
var PRIMARY = SERIES[0];
var DIAGRAM_HOVER = WHITE;
function noteStyle() {
return {
icon: {
background: '#007cc0',
border: { color: '#007cc0' }
},
label: { color: '#ffffff' },
line: { color: AXIS }
};
}
registerTheme('nova', {
chart: {
title: { color: TEXT },
legend: {
labels: { color: TEXT },
inactiveItems: {
labels: { color: INACTIVE },
markers: { color: INACTIVE }
}
},
seriesDefaults: {
labels: { color: TEXT },
errorBars: { color: TEXT },
notes: noteStyle(),
candlestick: {
downColor: AXIS,
line: { color: INACTIVE_SHAPE }
},
area: { opacity: 0.8 },
waterfall: { line: { color: AXIS } },
horizontalWaterfall: { line: { color: AXIS } },
overlay: { gradient: 'none' },
border: { _brightness: 1 }
},
seriesColors: SERIES,
axisDefaults: {
line: { color: AXIS },
labels: { color: TEXT },
minorGridLines: { color: AXIS_MINOR },
majorGridLines: { color: AXIS },
title: { color: TEXT },
crosshair: { color: TEXT },
notes: noteStyle()
}
},
gauge: {
pointer: { color: PRIMARY },
scale: {
rangePlaceholderColor: AXIS,
labels: { color: TEXT },
minorTicks: { color: TEXT },
majorTicks: { color: TEXT },
line: { color: TEXT }
}
},
diagram: {
shapeDefaults: {
fill: { color: PRIMARY },
connectorDefaults: {
fill: { color: TEXT },
stroke: { color: DIAGRAM_HOVER },
hover: {
fill: { color: DIAGRAM_HOVER },
stroke: { color: TEXT }
}
},
content: { color: TEXT }
},
editable: {
resize: {
handles: {
fill: { color: DIAGRAM_HOVER },
stroke: { color: INACTIVE_SHAPE },
hover: {
fill: { color: INACTIVE_SHAPE },
stroke: { color: INACTIVE_SHAPE }
}
}
},
rotate: {
thumb: {
stroke: { color: INACTIVE_SHAPE },
fill: { color: INACTIVE_SHAPE }
}
}
},
selectable: { stroke: { color: INACTIVE_SHAPE } },
connectionDefaults: {
stroke: { color: INACTIVE_SHAPE },
content: { color: INACTIVE_SHAPE },
selection: {
handles: {
fill: { color: DIAGRAM_HOVER },
stroke: { color: INACTIVE_SHAPE }
},
stroke: { color: INACTIVE_SHAPE }
}
}
},
treeMap: { colors: fuse(SERIES, SERIES_LIGHT) }
});
}());
function fuse(arr1, arr2) {
return $.map(arr1, function (item, index) {
return [[
item,
arr2[index]
]];
});
}
}(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) {
if (chart._zoomSelection.start(e)) {
this.trigger(ZOOM_START, {
axisRanges: axisRanges(this._plotArea.axes),
originalEvent: 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();
this.trigger(ZOOM_END, {
axisRanges: ranges,
originalEvent: e
});
}
}
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) {
var args = {
delta: delta,
axisRanges: axisRanges(this._plotArea.axes),
originalEvent: e
};
if (!chart.trigger(ZOOM_START, args)) {
e.preventDefault();
args.axisRanges = ranges = mousewheelZoom.updateRanges(delta);
if (ranges && !chart.trigger(ZOOM, args)) {
mousewheelZoom.zoom();
chart.trigger(ZOOM_END, args);
}
}
} 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 * step, 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 = math.min(box.x2, clipBox.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 = math.max(box.y1, clipBox.y1) - tooltipHeight - TOOLTIP_OFFSET;
} else {
x = aboveAxis ? x2 + TOOLTIP_OFFSET : x1 - tooltipWidth - TOOLTIP_OFFSET;
y = math.max(box.y1, clipBox.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];
if (!categoryPts) {
continue;
}
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}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: Bar.fn.tooltipAnchor,
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 (!(options.markers.visible && options.markers.size)) {
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: '
' + '{4:d} | ' + 'Open: | {0:C} |
' + 'High: | {1:C} |
' + 'Low: | {2:C} |
' + 'Close: | {3:C} |
' + '
' },
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: '
' + '{6:d} | ' + 'Lower: | {0:C} |
' + 'Q1: | {1:C} |
' + 'Median: | {2:C} |
' + 'Mean: | {5:C} |
' + 'Q3: | {3:C} |
' + 'Upper: | {4:C} |
' + '
' },
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('
' + '
');
}
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: '
' + '#= categoryText # | ' + '# for(var i = 0; i < points.length; i++) { #' + '# var point = points[i]; #' + '' + '# if(point.series.name) { # ' + ' #= point.series.name #: | ' + '# } #' + '#= content(point) # | ' + '
' + '# } #' + '
',
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('
');
}
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 = $('
');
},
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 && zoomPane.clipBox()) {
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
});
return true;
}
}
return false;
},
_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, limit) {
if (!defined(limit)) {
limit = true;
}
return this._axis.slot(from, to, limit);
},
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 if (baseUnit == DAYS || baseUnit == WEEKS) {
index = timeIndex(date, startDate, baseUnit);
} else {
index = dateDiff(date, start) / TIME_PER_UNIT[baseUnit];
}
return index / baseUnitStep;
}
function timeIndex(date, start, baseUnit) {
return absoluteDateDiff(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,
ZoomSelection: ZoomSelection,
Pannable: Pannable,
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)();
}));
(function (f, define) {
define('kendo.dataviz.chart.polar', [
'kendo.dataviz.chart',
'kendo.drawing'
], f);
}(function () {
var __meta__ = {
id: 'dataviz.chart.polar',
name: 'Polar Charts',
category: 'dataviz',
depends: ['dataviz.chart'],
hidden: true
};
(function ($, undefined) {
var math = Math, kendo = window.kendo, deepExtend = kendo.deepExtend, util = kendo.util, append = util.append, draw = kendo.drawing, geom = kendo.geometry, dataviz = kendo.dataviz, AreaSegment = dataviz.AreaSegment, Axis = dataviz.Axis, AxisGroupRangeTracker = dataviz.AxisGroupRangeTracker, BarChart = dataviz.BarChart, Box2D = dataviz.Box2D, CategoryAxis = dataviz.CategoryAxis, CategoricalChart = dataviz.CategoricalChart, CategoricalPlotArea = dataviz.CategoricalPlotArea, ChartElement = dataviz.ChartElement, CurveProcessor = dataviz.CurveProcessor, DonutSegment = dataviz.DonutSegment, LineChart = dataviz.LineChart, LineSegment = dataviz.LineSegment, LogarithmicAxis = dataviz.LogarithmicAxis, NumericAxis = dataviz.NumericAxis, PlotAreaBase = dataviz.PlotAreaBase, PlotAreaFactory = dataviz.PlotAreaFactory, Point2D = dataviz.Point2D, Ring = dataviz.Ring, ScatterChart = dataviz.ScatterChart, ScatterLineChart = dataviz.ScatterLineChart, SeriesBinder = dataviz.SeriesBinder, ShapeBuilder = dataviz.ShapeBuilder, SplineSegment = dataviz.SplineSegment, SplineAreaSegment = dataviz.SplineAreaSegment, getSpacing = dataviz.getSpacing, filterSeriesByType = dataviz.filterSeriesByType, limitValue = util.limitValue, round = dataviz.round;
var ARC = 'arc', BLACK = '#000', COORD_PRECISION = dataviz.COORD_PRECISION, DEFAULT_PADDING = 0.15, DEG_TO_RAD = math.PI / 180, GAP = 'gap', INTERPOLATE = 'interpolate', LOGARITHMIC = 'log', PLOT_AREA_CLICK = 'plotAreaClick', POLAR_AREA = 'polarArea', POLAR_LINE = 'polarLine', POLAR_SCATTER = 'polarScatter', RADAR_AREA = 'radarArea', RADAR_COLUMN = 'radarColumn', RADAR_LINE = 'radarLine', SMOOTH = 'smooth', X = 'x', Y = 'y', ZERO = 'zero', POLAR_CHARTS = [
POLAR_AREA,
POLAR_LINE,
POLAR_SCATTER
], RADAR_CHARTS = [
RADAR_AREA,
RADAR_COLUMN,
RADAR_LINE
];
var GridLinesMixin = {
createGridLines: function (altAxis) {
var axis = this, options = axis.options, radius = math.abs(axis.box.center().y - altAxis.lineBox().y1), majorAngles, minorAngles, skipMajor = false, gridLines = [];
if (options.majorGridLines.visible) {
majorAngles = axis.majorGridLineAngles(altAxis);
skipMajor = true;
gridLines = axis.renderMajorGridLines(majorAngles, radius, options.majorGridLines);
}
if (options.minorGridLines.visible) {
minorAngles = axis.minorGridLineAngles(altAxis, skipMajor);
append(gridLines, axis.renderMinorGridLines(minorAngles, radius, options.minorGridLines, altAxis, skipMajor));
}
return gridLines;
},
renderMajorGridLines: function (angles, radius, options) {
return this.renderGridLines(angles, radius, options);
},
renderMinorGridLines: function (angles, radius, options, altAxis, skipMajor) {
var radiusCallback = this.radiusCallback && this.radiusCallback(radius, altAxis, skipMajor);
return this.renderGridLines(angles, radius, options, radiusCallback);
},
renderGridLines: function (angles, radius, options, radiusCallback) {
var style = {
stroke: {
width: options.width,
color: options.color,
dashType: options.dashType
}
};
var center = this.box.center();
var circle = new geom.Circle([
center.x,
center.y
], radius);
var container = this.gridLinesVisual();
for (var i = 0; i < angles.length; i++) {
var line = new draw.Path(style);
if (radiusCallback) {
circle.radius = radiusCallback(angles[i]);
}
line.moveTo(circle.center).lineTo(circle.pointAt(angles[i]));
container.append(line);
}
return container.children;
},
gridLineAngles: function (altAxis, size, skip, step, skipAngles) {
var axis = this, divs = axis.intervals(size, skip, step, skipAngles), options = altAxis.options, altAxisVisible = options.visible && (options.line || {}).visible !== false;
return $.map(divs, function (d) {
var alpha = axis.intervalAngle(d);
if (!altAxisVisible || alpha !== 90) {
return alpha;
}
});
}
};
var RadarCategoryAxis = CategoryAxis.extend({
options: {
startAngle: 90,
labels: { margin: getSpacing(10) },
majorGridLines: { visible: true },
justified: true
},
range: function () {
return {
min: 0,
max: this.options.categories.length
};
},
reflow: function (box) {
this.box = box;
this.reflowLabels();
},
lineBox: function () {
return this.box;
},
reflowLabels: function () {
var axis = this, labelOptions = axis.options.labels, skip = labelOptions.skip || 0, step = labelOptions.step || 1, measureBox = new Box2D(), labels = axis.labels, labelBox, i;
for (i = 0; i < labels.length; i++) {
labels[i].reflow(measureBox);
labelBox = labels[i].box;
labels[i].reflow(axis.getSlot(skip + i * step).adjacentBox(0, labelBox.width(), labelBox.height()));
}
},
intervals: function (size, skip, step, skipAngles) {
var axis = this, options = axis.options, categories = options.categories.length, angle = 0, divCount = categories / size || 1, divAngle = 360 / divCount, divs = [], i;
skip = skip || 0;
step = step || 1;
for (i = skip; i < divCount; i += step) {
if (options.reverse) {
angle = 360 - i * divAngle;
} else {
angle = i * divAngle;
}
angle = round(angle, COORD_PRECISION) % 360;
if (!(skipAngles && dataviz.inArray(angle, skipAngles))) {
divs.push(angle);
}
}
return divs;
},
majorIntervals: function () {
return this.intervals(1);
},
minorIntervals: function () {
return this.intervals(0.5);
},
intervalAngle: function (interval) {
return (360 + interval + this.options.startAngle) % 360;
},
majorAngles: function () {
return $.map(this.majorIntervals(), $.proxy(this.intervalAngle, this));
},
createLine: function () {
return [];
},
majorGridLineAngles: function (altAxis) {
var majorGridLines = this.options.majorGridLines;
return this.gridLineAngles(altAxis, 1, majorGridLines.skip, majorGridLines.step);
},
minorGridLineAngles: function (altAxis, skipMajor) {
var options = this.options;
var minorGridLines = options.minorGridLines;
var majorGridLines = options.majorGridLines;
var majorGridLineAngles = skipMajor ? this.intervals(1, majorGridLines.skip, majorGridLines.step) : null;
return this.gridLineAngles(altAxis, 0.5, minorGridLines.skip, minorGridLines.step, majorGridLineAngles);
},
radiusCallback: function (radius, altAxis, skipMajor) {
if (altAxis.options.type !== ARC) {
var minorAngle = 360 / (this.options.categories.length * 2);
var minorRadius = math.cos(minorAngle * DEG_TO_RAD) * radius;
var majorAngles = this.majorAngles();
var radiusCallback = function (angle) {
if (!skipMajor && dataviz.inArray(angle, majorAngles)) {
return radius;
} else {
return minorRadius;
}
};
return radiusCallback;
}
},
createPlotBands: function () {
var axis = this, options = axis.options, plotBands = options.plotBands || [], i, band, slot, singleSlot, head, tail;
var group = this._plotbandGroup = new draw.Group({ zIndex: -1 });
for (i = 0; i < plotBands.length; i++) {
band = plotBands[i];
slot = axis.plotBandSlot(band);
singleSlot = axis.getSlot(band.from);
head = band.from - math.floor(band.from);
slot.startAngle += head * singleSlot.angle;
tail = math.ceil(band.to) - band.to;
slot.angle -= (tail + head) * singleSlot.angle;
var ring = ShapeBuilder.current.createRing(slot, {
fill: {
color: band.color,
opacity: band.opacity
},
stroke: { opacity: band.opacity }
});
group.append(ring);
}
axis.appendVisual(group);
},
plotBandSlot: function (band) {
return this.getSlot(band.from, band.to - 1);
},
getSlot: function (from, to) {
var axis = this, options = axis.options, justified = options.justified, box = axis.box, divs = axis.majorAngles(), totalDivs = divs.length, slots, slotAngle = 360 / totalDivs, slotStart, angle;
if (options.reverse && !justified) {
from = (from + 1) % totalDivs;
}
from = limitValue(math.floor(from), 0, totalDivs - 1);
slotStart = divs[from];
if (justified) {
slotStart = slotStart - slotAngle / 2;
if (slotStart < 0) {
slotStart += 360;
}
}
to = limitValue(math.ceil(to || from), from, totalDivs - 1);
slots = to - from + 1;
angle = slotAngle * slots;
return new Ring(box.center(), 0, box.height() / 2, slotStart, angle);
},
slot: function (from, to) {
var slot = this.getSlot(from, to);
var startAngle = slot.startAngle + 180;
var endAngle = startAngle + slot.angle;
return new geom.Arc([
slot.c.x,
slot.c.y
], {
startAngle: startAngle,
endAngle: endAngle,
radiusX: slot.r,
radiusY: slot.r
});
},
pointCategoryIndex: function (point) {
var axis = this, index = null, i, length = axis.options.categories.length, slot;
for (i = 0; i < length; i++) {
slot = axis.getSlot(i);
if (slot.containsPoint(point)) {
index = i;
break;
}
}
return index;
}
});
deepExtend(RadarCategoryAxis.fn, GridLinesMixin);
var RadarNumericAxisMixin = {
options: { majorGridLines: { visible: true } },
createPlotBands: function () {
var axis = this, options = axis.options, plotBands = options.plotBands || [], type = options.majorGridLines.type, altAxis = axis.plotArea.polarAxis, majorAngles = altAxis.majorAngles(), center = altAxis.box.center(), i, band, bandStyle, slot, ring;
var group = this._plotbandGroup = new draw.Group({ zIndex: -1 });
for (i = 0; i < plotBands.length; i++) {
band = plotBands[i];
bandStyle = {
fill: {
color: band.color,
opacity: band.opacity
},
stroke: { opacity: band.opacity }
};
slot = axis.getSlot(band.from, band.to, true);
ring = new Ring(center, center.y - slot.y2, center.y - slot.y1, 0, 360);
var shape;
if (type === ARC) {
shape = ShapeBuilder.current.createRing(ring, bandStyle);
} else {
shape = draw.Path.fromPoints(axis.plotBandPoints(ring, majorAngles), bandStyle).close();
}
group.append(shape);
}
axis.appendVisual(group);
},
plotBandPoints: function (ring, angles) {
var innerPoints = [], outerPoints = [];
var center = [
ring.c.x,
ring.c.y
];
var innerCircle = new geom.Circle(center, ring.ir);
var outerCircle = new geom.Circle(center, ring.r);
for (var i = 0; i < angles.length; i++) {
innerPoints.push(innerCircle.pointAt(angles[i]));
outerPoints.push(outerCircle.pointAt(angles[i]));
}
innerPoints.reverse();
innerPoints.push(innerPoints[0]);
outerPoints.push(outerPoints[0]);
return outerPoints.concat(innerPoints);
},
createGridLines: function (altAxis) {
var axis = this, options = axis.options, majorTicks = axis.radarMajorGridLinePositions(), majorAngles = altAxis.majorAngles(), minorTicks, center = altAxis.box.center(), gridLines = [];
if (options.majorGridLines.visible) {
gridLines = axis.renderGridLines(center, majorTicks, majorAngles, options.majorGridLines);
}
if (options.minorGridLines.visible) {
minorTicks = axis.radarMinorGridLinePositions();
append(gridLines, axis.renderGridLines(center, minorTicks, majorAngles, options.minorGridLines));
}
return gridLines;
},
renderGridLines: function (center, ticks, angles, options) {
var tickRadius, tickIx, angleIx;
var style = {
stroke: {
width: options.width,
color: options.color,
dashType: options.dashType
}
};
var container = this.gridLinesVisual();
for (tickIx = 0; tickIx < ticks.length; tickIx++) {
tickRadius = center.y - ticks[tickIx];
if (tickRadius > 0) {
var circle = new geom.Circle([
center.x,
center.y
], tickRadius);
if (options.type === ARC) {
container.append(new draw.Circle(circle, style));
} else {
var line = new draw.Path(style);
for (angleIx = 0; angleIx < angles.length; angleIx++) {
line.lineTo(circle.pointAt(angles[angleIx]));
}
line.close();
container.append(line);
}
}
}
return container.children;
},
getValue: function (point) {
var axis = this, options = axis.options, lineBox = axis.lineBox(), altAxis = axis.plotArea.polarAxis, majorAngles = altAxis.majorAngles(), center = altAxis.box.center(), r = point.distanceTo(center), distance = r;
if (options.majorGridLines.type !== ARC && majorAngles.length > 1) {
var dx = point.x - center.x, dy = point.y - center.y, theta = (math.atan2(dy, dx) / DEG_TO_RAD + 540) % 360;
majorAngles.sort(function (a, b) {
return angularDistance(a, theta) - angularDistance(b, theta);
});
var midAngle = angularDistance(majorAngles[0], majorAngles[1]) / 2, alpha = angularDistance(theta, majorAngles[0]), gamma = 90 - midAngle, beta = 180 - alpha - gamma;
distance = r * (math.sin(beta * DEG_TO_RAD) / math.sin(gamma * DEG_TO_RAD));
}
return axis.axisType().fn.getValue.call(axis, new Point2D(lineBox.x1, lineBox.y2 - distance));
}
};
var RadarNumericAxis = NumericAxis.extend({
radarMajorGridLinePositions: function () {
return this.getTickPositions(this.options.majorUnit);
},
radarMinorGridLinePositions: function () {
var axis = this, options = axis.options, minorSkipStep = 0;
if (options.majorGridLines.visible) {
minorSkipStep = options.majorUnit;
}
return axis.getTickPositions(options.minorUnit, minorSkipStep);
},
axisType: function () {
return NumericAxis;
}
});
deepExtend(RadarNumericAxis.fn, RadarNumericAxisMixin);
var RadarLogarithmicAxis = LogarithmicAxis.extend({
radarMajorGridLinePositions: function () {
var axis = this, positions = [];
axis.traverseMajorTicksPositions(function (position) {
positions.push(position);
}, axis.options.majorGridLines);
return positions;
},
radarMinorGridLinePositions: function () {
var axis = this, positions = [];
axis.traverseMinorTicksPositions(function (position) {
positions.push(position);
}, axis.options.minorGridLines);
return positions;
},
axisType: function () {
return LogarithmicAxis;
}
});
deepExtend(RadarLogarithmicAxis.fn, RadarNumericAxisMixin);
var PolarAxis = Axis.extend({
init: function (options) {
var axis = this;
Axis.fn.init.call(axis, options);
options = axis.options;
options.minorUnit = options.minorUnit || axis.options.majorUnit / 2;
},
options: {
type: 'polar',
startAngle: 0,
reverse: false,
majorUnit: 60,
min: 0,
max: 360,
labels: { margin: getSpacing(10) },
majorGridLines: {
color: BLACK,
visible: true,
width: 1
},
minorGridLines: { color: '#aaa' }
},
getDivisions: function (stepValue) {
return NumericAxis.fn.getDivisions.call(this, stepValue) - 1;
},
reflow: function (box) {
this.box = box;
this.reflowLabels();
},
reflowLabels: function () {
var axis = this, options = axis.options, labelOptions = options.labels, skip = labelOptions.skip || 0, step = labelOptions.step || 1, measureBox = new Box2D(), divs = axis.intervals(options.majorUnit, skip, step), labels = axis.labels, labelBox, i;
for (i = 0; i < labels.length; i++) {
labels[i].reflow(measureBox);
labelBox = labels[i].box;
labels[i].reflow(axis.getSlot(divs[i]).adjacentBox(0, labelBox.width(), labelBox.height()));
}
},
lineBox: function () {
return this.box;
},
intervals: function (size, skip, step, skipAngles) {
var axis = this, options = axis.options, divisions = axis.getDivisions(size), min = options.min, current, divs = [], i;
skip = skip || 0;
step = step || 1;
for (i = skip; i < divisions; i += step) {
current = (360 + min + i * size) % 360;
if (!(skipAngles && dataviz.inArray(current, skipAngles))) {
divs.push(current);
}
}
return divs;
},
majorIntervals: function () {
return this.intervals(this.options.majorUnit);
},
minorIntervals: function () {
return this.intervals(this.options.minorUnit);
},
intervalAngle: function (i) {
return (540 - i - this.options.startAngle) % 360;
},
majorAngles: RadarCategoryAxis.fn.majorAngles,
createLine: function () {
return [];
},
majorGridLineAngles: function (altAxis) {
var majorGridLines = this.options.majorGridLines;
return this.gridLineAngles(altAxis, this.options.majorUnit, majorGridLines.skip, majorGridLines.step);
},
minorGridLineAngles: function (altAxis, skipMajor) {
var options = this.options;
var minorGridLines = options.minorGridLines;
var majorGridLines = options.majorGridLines;
var majorGridLineAngles = skipMajor ? this.intervals(options.majorUnit, majorGridLines.skip, majorGridLines.step) : null;
return this.gridLineAngles(altAxis, this.options.minorUnit, minorGridLines.skip, minorGridLines.step, majorGridLineAngles);
},
createPlotBands: RadarCategoryAxis.fn.createPlotBands,
plotBandSlot: function (band) {
return this.getSlot(band.from, band.to);
},
getSlot: function (a, b) {
var axis = this, options = axis.options, start = options.startAngle, box = axis.box, tmp;
a = limitValue(a, options.min, options.max);
b = limitValue(b || a, a, options.max);
if (options.reverse) {
a *= -1;
b *= -1;
}
a = (540 - a - start) % 360;
b = (540 - b - start) % 360;
if (b < a) {
tmp = a;
a = b;
b = tmp;
}
return new Ring(box.center(), 0, box.height() / 2, a, b - a);
},
slot: function (from, to) {
var options = this.options;
var start = 360 - options.startAngle;
var slot = this.getSlot(from, to);
var startAngle;
var endAngle;
var min;
var max;
if (!dataviz.util.defined(to)) {
to = from;
}
min = math.min(from, to);
max = math.max(from, to);
if (options.reverse) {
startAngle = min;
endAngle = max;
} else {
startAngle = 360 - max;
endAngle = 360 - min;
}
startAngle = (startAngle + start) % 360;
endAngle = (endAngle + start) % 360;
return new geom.Arc([
slot.c.x,
slot.c.y
], {
startAngle: startAngle,
endAngle: endAngle,
radiusX: slot.r,
radiusY: slot.r
});
},
getValue: function (point) {
var axis = this, options = axis.options, center = axis.box.center(), dx = point.x - center.x, dy = point.y - center.y, theta = math.round(math.atan2(dy, dx) / DEG_TO_RAD), start = options.startAngle;
if (!options.reverse) {
theta *= -1;
start *= -1;
}
return (theta + start + 360) % 360;
},
range: NumericAxis.fn.range,
labelsCount: NumericAxis.fn.labelsCount,
createAxisLabel: NumericAxis.fn.createAxisLabel
});
deepExtend(PolarAxis.fn, GridLinesMixin);
var RadarClusterLayout = ChartElement.extend({
options: {
gap: 1,
spacing: 0
},
reflow: function (sector) {
var cluster = this, options = cluster.options, children = cluster.children, gap = options.gap, spacing = options.spacing, count = children.length, slots = count + gap + spacing * (count - 1), slotAngle = sector.angle / slots, slotSector, angle = sector.startAngle + slotAngle * (gap / 2), i;
for (i = 0; i < count; i++) {
slotSector = sector.clone();
slotSector.startAngle = angle;
slotSector.angle = slotAngle;
if (children[i].sector) {
slotSector.r = children[i].sector.r;
}
children[i].reflow(slotSector);
children[i].sector = slotSector;
angle += slotAngle + slotAngle * spacing;
}
}
});
var RadarStackLayout = ChartElement.extend({
reflow: function (sector) {
var stack = this, reverse = stack.options.isReversed, children = stack.children, childrenCount = children.length, childSector, i, first = reverse ? childrenCount - 1 : 0, step = reverse ? -1 : 1;
stack.box = new Box2D();
for (i = first; i >= 0 && i < childrenCount; i += step) {
childSector = children[i].sector;
childSector.startAngle = sector.startAngle;
childSector.angle = sector.angle;
}
}
});
var RadarSegment = DonutSegment.extend({
init: function (value, options) {
DonutSegment.fn.init.call(this, value, null, options);
},
options: {
overlay: { gradient: null },
labels: { distance: 10 }
}
});
var RadarBarChart = BarChart.extend({
pointType: function () {
return RadarSegment;
},
clusterType: function () {
return RadarClusterLayout;
},
stackType: function () {
return RadarStackLayout;
},
categorySlot: function (categoryAxis, categoryIx) {
return categoryAxis.getSlot(categoryIx);
},
pointSlot: function (categorySlot, valueSlot) {
var slot = categorySlot.clone(), y = categorySlot.c.y;
slot.r = y - valueSlot.y1;
slot.ir = y - valueSlot.y2;
return slot;
},
reflow: CategoricalChart.fn.reflow,
reflowPoint: function (point, pointSlot) {
point.sector = pointSlot;
point.reflow();
},
options: {
clip: false,
animation: { type: 'pie' }
},
createAnimation: function () {
this.options.animation.center = this.box.toRect().center();
BarChart.fn.createAnimation.call(this);
}
});
var RadarLineChart = LineChart.extend({
options: { clip: false },
pointSlot: function (categorySlot, valueSlot) {
var valueRadius = categorySlot.c.y - valueSlot.y1, slot = Point2D.onCircle(categorySlot.c, categorySlot.middle(), valueRadius);
return new Box2D(slot.x, slot.y, slot.x, slot.y);
},
createSegment: function (linePoints, currentSeries, seriesIx) {
var segment, pointType, style = currentSeries.style;
if (style == SMOOTH) {
pointType = SplineSegment;
} else {
pointType = LineSegment;
}
segment = new pointType(linePoints, currentSeries, seriesIx);
if (linePoints.length === currentSeries.data.length) {
segment.options.closed = true;
}
return segment;
}
});
var RadarAreaSegment = AreaSegment.extend({
points: function () {
return LineSegment.fn.points.call(this, this.stackPoints);
}
});
var SplineRadarAreaSegment = SplineAreaSegment.extend({ closeFill: $.noop });
var RadarAreaChart = RadarLineChart.extend({
createSegment: function (linePoints, currentSeries, seriesIx, prevSegment) {
var chart = this, options = chart.options, isStacked = options.isStacked, stackPoints, segment, style = (currentSeries.line || {}).style;
if (style === SMOOTH) {
segment = new SplineRadarAreaSegment(linePoints, prevSegment, isStacked, currentSeries, seriesIx);
segment.options.closed = true;
} else {
if (isStacked && seriesIx > 0 && prevSegment) {
stackPoints = prevSegment.linePoints.slice(0).reverse();
}
linePoints.push(linePoints[0]);
segment = new RadarAreaSegment(linePoints, stackPoints, currentSeries, seriesIx);
}
return segment;
},
seriesMissingValues: function (series) {
return series.missingValues || ZERO;
}
});
var PolarScatterChart = ScatterChart.extend({
pointSlot: function (slotX, slotY) {
var valueRadius = slotX.c.y - slotY.y1, slot = Point2D.onCircle(slotX.c, slotX.startAngle, valueRadius);
return new Box2D(slot.x, slot.y, slot.x, slot.y);
},
options: { clip: false }
});
var PolarLineChart = ScatterLineChart.extend({
pointSlot: PolarScatterChart.fn.pointSlot,
options: { clip: false }
});
var PolarAreaSegment = AreaSegment.extend({
points: function () {
var segment = this, chart = segment.parent, plotArea = chart.plotArea, polarAxis = plotArea.polarAxis, center = polarAxis.box.center(), stackPoints = segment.stackPoints, points = LineSegment.fn.points.call(segment, stackPoints);
points.unshift([
center.x,
center.y
]);
points.push([
center.x,
center.y
]);
return points;
}
});
var SplinePolarAreaSegment = SplineAreaSegment.extend({
closeFill: function (fillPath) {
var center = this._polarAxisCenter();
fillPath.lineTo(center.x, center.y);
},
_polarAxisCenter: function () {
var chart = this.parent, plotArea = chart.plotArea, polarAxis = plotArea.polarAxis, center = polarAxis.box.center();
return center;
},
strokeSegments: function () {
var segments = this._strokeSegments;
if (!segments) {
var center = this._polarAxisCenter(), curveProcessor = new CurveProcessor(false), linePoints = LineSegment.fn.points.call(this);
linePoints.push(center);
segments = this._strokeSegments = curveProcessor.process(linePoints);
segments.pop();
}
return segments;
}
});
var PolarAreaChart = PolarLineChart.extend({
createSegment: function (linePoints, currentSeries, seriesIx) {
var segment, style = (currentSeries.line || {}).style;
if (style == SMOOTH) {
segment = new SplinePolarAreaSegment(linePoints, null, false, currentSeries, seriesIx);
} else {
segment = new PolarAreaSegment(linePoints, [], currentSeries, seriesIx);
}
return segment;
},
createMissingValue: function (value, missingValues) {
var missingValue;
if (dataviz.hasValue(value.x) && missingValues != INTERPOLATE) {
missingValue = {
x: value.x,
y: value.y
};
if (missingValues == ZERO) {
missingValue.y = 0;
}
}
return missingValue;
},
seriesMissingValues: function (series) {
return series.missingValues || ZERO;
},
_hasMissingValuesGap: function () {
var series = this.options.series;
for (var idx = 0; idx < series.length; idx++) {
if (this.seriesMissingValues(series[idx]) === GAP) {
return true;
}
}
},
sortPoints: function (points) {
var value, point;
points.sort(xComparer);
if (this._hasMissingValuesGap()) {
for (var idx = 0; idx < points.length; idx++) {
point = points[idx];
if (point) {
value = point.value;
if (!dataviz.hasValue(value.y) && this.seriesMissingValues(point.series) === GAP) {
delete points[idx];
}
}
}
}
return points;
}
});
var PolarPlotAreaBase = PlotAreaBase.extend({
init: function (series, options) {
var plotArea = this;
plotArea.valueAxisRangeTracker = new AxisGroupRangeTracker();
PlotAreaBase.fn.init.call(plotArea, series, options);
},
render: function () {
var plotArea = this;
plotArea.addToLegend(plotArea.series);
plotArea.createPolarAxis();
plotArea.createCharts();
plotArea.createValueAxis();
},
alignAxes: function () {
var axis = this.valueAxis;
var range = axis.range();
var crossingValue = axis.options.reverse ? range.max : range.min;
var slot = axis.getSlot(crossingValue);
var center = this.polarAxis.getSlot(0).c;
var axisBox = axis.box.translate(center.x - slot.x1, center.y - slot.y1);
axis.reflow(axisBox);
},
createValueAxis: function () {
var plotArea = this, tracker = plotArea.valueAxisRangeTracker, defaultRange = tracker.query(), range, valueAxis, axisOptions = plotArea.valueAxisOptions({
roundToMajorUnit: false,
zIndex: -1
}), axisType, axisDefaultRange;
if (axisOptions.type === LOGARITHMIC) {
axisType = RadarLogarithmicAxis;
axisDefaultRange = {
min: 0.1,
max: 1
};
} else {
axisType = RadarNumericAxis;
axisDefaultRange = {
min: 0,
max: 1
};
}
range = tracker.query(name) || defaultRange || axisDefaultRange;
if (range && defaultRange) {
range.min = math.min(range.min, defaultRange.min);
range.max = math.max(range.max, defaultRange.max);
}
valueAxis = new axisType(range.min, range.max, axisOptions);
plotArea.valueAxis = valueAxis;
plotArea.appendAxis(valueAxis);
},
reflowAxes: function () {
var plotArea = this, options = plotArea.options.plotArea, valueAxis = plotArea.valueAxis, polarAxis = plotArea.polarAxis, box = plotArea.box, defaultPadding = math.min(box.width(), box.height()) * DEFAULT_PADDING, padding = getSpacing(options.padding || {}, defaultPadding), axisBox = box.clone().unpad(padding), valueAxisBox = axisBox.clone().shrink(0, axisBox.height() / 2);
polarAxis.reflow(axisBox);
valueAxis.reflow(valueAxisBox);
var heightDiff = valueAxis.lineBox().height() - valueAxis.box.height();
valueAxis.reflow(valueAxis.box.unpad({ top: heightDiff }));
plotArea.axisBox = axisBox;
plotArea.alignAxes(axisBox);
},
backgroundBox: function () {
return this.box;
}
});
var RadarPlotArea = PolarPlotAreaBase.extend({
options: {
categoryAxis: { categories: [] },
valueAxis: {}
},
createPolarAxis: function () {
var plotArea = this, categoryAxis;
categoryAxis = new RadarCategoryAxis(plotArea.options.categoryAxis);
plotArea.polarAxis = categoryAxis;
plotArea.categoryAxis = categoryAxis;
plotArea.appendAxis(categoryAxis);
plotArea.aggregateCategories();
},
valueAxisOptions: function (defaults) {
var plotArea = this;
if (plotArea._hasBarCharts) {
deepExtend(defaults, {
majorGridLines: { type: ARC },
minorGridLines: { type: ARC }
});
}
if (plotArea._isStacked100) {
deepExtend(defaults, {
roundToMajorUnit: false,
labels: { format: 'P0' }
});
}
return deepExtend(defaults, plotArea.options.valueAxis);
},
appendChart: CategoricalPlotArea.fn.appendChart,
aggregateSeries: CategoricalPlotArea.fn.aggregateSeries,
aggregateCategories: function () {
CategoricalPlotArea.fn.aggregateCategories.call(this, this.panes);
},
filterSeries: function (currentSeries) {
return currentSeries;
},
createCharts: function () {
var plotArea = this, series = plotArea.filterVisibleSeries(plotArea.series), pane = plotArea.panes[0];
plotArea.createAreaChart(filterSeriesByType(series, [RADAR_AREA]), pane);
plotArea.createLineChart(filterSeriesByType(series, [RADAR_LINE]), pane);
plotArea.createBarChart(filterSeriesByType(series, [RADAR_COLUMN]), pane);
},
chartOptions: function (series) {
var options = { series: series };
var firstSeries = series[0];
if (firstSeries) {
var filteredSeries = this.filterVisibleSeries(series);
var stack = firstSeries.stack;
options.isStacked = stack && filteredSeries.length > 1;
options.isStacked100 = stack && stack.type === '100%' && filteredSeries.length > 1;
if (options.isStacked100) {
this._isStacked100 = true;
}
}
return options;
},
createAreaChart: function (series, pane) {
if (series.length === 0) {
return;
}
var areaChart = new RadarAreaChart(this, this.chartOptions(series));
this.appendChart(areaChart, pane);
},
createLineChart: function (series, pane) {
if (series.length === 0) {
return;
}
var lineChart = new RadarLineChart(this, this.chartOptions(series));
this.appendChart(lineChart, pane);
},
createBarChart: function (series, pane) {
if (series.length === 0) {
return;
}
var firstSeries = series[0];
var options = this.chartOptions(series);
options.gap = firstSeries.gap;
options.spacing = firstSeries.spacing;
var barChart = new RadarBarChart(this, options);
this.appendChart(barChart, pane);
this._hasBarCharts = true;
},
seriesCategoryAxis: function () {
return this.categoryAxis;
},
click: function (chart, e) {
var plotArea = this, coords = chart._eventCoordinates(e), point = new Point2D(coords.x, coords.y), category, value;
category = plotArea.categoryAxis.getCategory(point);
value = plotArea.valueAxis.getValue(point);
if (category !== null && value !== null) {
chart.trigger(PLOT_AREA_CLICK, {
element: $(e.target),
category: category,
value: value
});
}
},
createCrosshairs: $.noop
});
var PolarPlotArea = PolarPlotAreaBase.extend({
options: {
xAxis: {},
yAxis: {}
},
createPolarAxis: function () {
var plotArea = this, polarAxis;
polarAxis = new PolarAxis(plotArea.options.xAxis);
plotArea.polarAxis = polarAxis;
plotArea.axisX = polarAxis;
plotArea.appendAxis(polarAxis);
},
valueAxisOptions: function (defaults) {
var plotArea = this;
return deepExtend(defaults, {
majorGridLines: { type: ARC },
minorGridLines: { type: ARC }
}, plotArea.options.yAxis);
},
createValueAxis: function () {
var plotArea = this;
PolarPlotAreaBase.fn.createValueAxis.call(plotArea);
plotArea.axisY = plotArea.valueAxis;
},
appendChart: function (chart, pane) {
var plotArea = this;
plotArea.valueAxisRangeTracker.update(chart.yAxisRanges);
PlotAreaBase.fn.appendChart.call(plotArea, chart, pane);
},
createCharts: function () {
var plotArea = this, series = plotArea.filterVisibleSeries(plotArea.series), pane = plotArea.panes[0];
plotArea.createLineChart(filterSeriesByType(series, [POLAR_LINE]), pane);
plotArea.createScatterChart(filterSeriesByType(series, [POLAR_SCATTER]), pane);
plotArea.createAreaChart(filterSeriesByType(series, [POLAR_AREA]), pane);
},
createLineChart: function (series, pane) {
if (series.length === 0) {
return;
}
var plotArea = this, lineChart = new PolarLineChart(plotArea, { series: series });
plotArea.appendChart(lineChart, pane);
},
createScatterChart: function (series, pane) {
if (series.length === 0) {
return;
}
var plotArea = this, scatterChart = new PolarScatterChart(plotArea, { series: series });
plotArea.appendChart(scatterChart, pane);
},
createAreaChart: function (series, pane) {
if (series.length === 0) {
return;
}
var plotArea = this, areaChart = new PolarAreaChart(plotArea, { series: series });
plotArea.appendChart(areaChart, pane);
},
click: function (chart, e) {
var plotArea = this, coords = chart._eventCoordinates(e), point = new Point2D(coords.x, coords.y), xValue, yValue;
xValue = plotArea.axisX.getValue(point);
yValue = plotArea.axisY.getValue(point);
if (xValue !== null && yValue !== null) {
chart.trigger(PLOT_AREA_CLICK, {
element: $(e.target),
x: xValue,
y: yValue
});
}
},
createCrosshairs: $.noop
});
function xComparer(a, b) {
return a.value.x - b.value.x;
}
function angularDistance(a, b) {
return 180 - math.abs(math.abs(a - b) - 180);
}
PlotAreaFactory.current.register(PolarPlotArea, POLAR_CHARTS);
PlotAreaFactory.current.register(RadarPlotArea, RADAR_CHARTS);
SeriesBinder.current.register(POLAR_CHARTS, [
X,
Y
], ['color']);
SeriesBinder.current.register(RADAR_CHARTS, ['value'], ['color']);
dataviz.DefaultAggregates.current.register(RADAR_CHARTS, {
value: 'max',
color: 'first'
});
deepExtend(dataviz, {
PolarAreaChart: PolarAreaChart,
PolarAxis: PolarAxis,
PolarLineChart: PolarLineChart,
PolarPlotArea: PolarPlotArea,
RadarAreaChart: RadarAreaChart,
RadarBarChart: RadarBarChart,
RadarCategoryAxis: RadarCategoryAxis,
RadarClusterLayout: RadarClusterLayout,
RadarLineChart: RadarLineChart,
RadarNumericAxis: RadarNumericAxis,
RadarPlotArea: RadarPlotArea,
SplinePolarAreaSegment: SplinePolarAreaSegment,
SplineRadarAreaSegment: SplineRadarAreaSegment,
RadarStackLayout: RadarStackLayout
});
}(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.funnel', [
'kendo.dataviz.chart',
'kendo.drawing'
], f);
}(function () {
var __meta__ = {
id: 'dataviz.chart.funnel',
name: 'Funnel Chart',
category: 'dataviz',
depends: ['dataviz.chart'],
hidden: true
};
(function ($, undefined) {
var kendo = window.kendo, deepExtend = kendo.deepExtend, extend = $.extend, isFn = kendo.isFunction, template = kendo.template, util = kendo.util, append = util.append, draw = kendo.drawing, geom = kendo.geometry, dataviz = kendo.dataviz, Color = kendo.drawing.Color, ChartElement = dataviz.ChartElement, PieChartMixin = dataviz.PieChartMixin, PlotAreaBase = dataviz.PlotAreaBase, PlotAreaFactory = dataviz.PlotAreaFactory, Point2D = dataviz.Point2D, Box2D = dataviz.Box2D, SeriesBinder = dataviz.SeriesBinder, TextBox = dataviz.TextBox, autoFormat = dataviz.autoFormat, evalOptions = dataviz.evalOptions, limitValue = util.limitValue, seriesTotal = dataviz.seriesTotal;
var CATEGORY = 'category', COLOR = 'color', FUNNEL = 'funnel', VALUE = 'value', BLACK = 'black', WHITE = 'white';
var FunnelPlotArea = PlotAreaBase.extend({
render: function () {
var plotArea = this, series = plotArea.series;
plotArea.createFunnelChart(series);
},
createFunnelChart: function (series) {
var plotArea = this, firstSeries = series[0], funnelChart = new FunnelChart(plotArea, {
series: series,
legend: plotArea.options.legend,
neckRatio: firstSeries.neckRatio,
dynamicHeight: firstSeries.dynamicHeight,
dynamicSlope: firstSeries.dynamicSlope,
segmentSpacing: firstSeries.segmentSpacing,
highlight: firstSeries.highlight
});
plotArea.appendChart(funnelChart);
},
appendChart: function (chart, pane) {
PlotAreaBase.fn.appendChart.call(this, chart, pane);
append(this.options.legend.items, chart.legendItems);
}
});
var FunnelChart = ChartElement.extend({
init: function (plotArea, options) {
var chart = this;
ChartElement.fn.init.call(chart, options);
chart.plotArea = plotArea;
chart.points = [];
chart.labels = [];
chart.legendItems = [];
chart.render();
},
options: {
neckRatio: 0.3,
width: 300,
dynamicSlope: false,
dynamicHeight: true,
segmentSpacing: 0,
labels: {
visible: false,
align: 'center',
position: 'center'
}
},
formatPointValue: function (point, format) {
return autoFormat(format, point.value);
},
render: function () {
var chart = this, options = chart.options, colors = chart.plotArea.options.seriesColors || [], colorsCount = colors.length, series = options.series[0], pointData, fields, data = series.data;
if (!data) {
return;
}
var total = seriesTotal(series), value, i;
for (i = 0; i < data.length; i++) {
pointData = SeriesBinder.current.bindPoint(series, i);
value = pointData.valueFields.value;
if (value === null || value === undefined) {
continue;
}
fields = pointData.fields;
if (!isFn(series.color)) {
series.color = fields.color || colors[i % colorsCount];
}
fields = deepExtend({
index: i,
owner: chart,
series: series,
category: fields.category,
dataItem: data[i],
percentage: Math.abs(value) / total,
visibleInLegend: fields.visibleInLegend,
visible: fields.visible
}, fields);
var segment = chart.createSegment(value, fields);
var label = chart.createLabel(value, fields);
if (segment && label) {
segment.append(label);
}
}
},
evalSegmentOptions: function (options, value, fields) {
var series = fields.series;
evalOptions(options, {
value: value,
series: series,
dataItem: fields.dataItem,
index: fields.index
}, {
defaults: series._defaults,
excluded: [
'data',
'toggle',
'visual'
]
});
},
createSegment: function (value, fields) {
var chart = this, segment;
var seriesOptions = deepExtend({}, fields.series);
chart.evalSegmentOptions(seriesOptions, value, fields);
chart.createLegendItem(value, seriesOptions, fields);
if (fields.visible !== false) {
segment = new FunnelSegment(value, seriesOptions, fields);
extend(segment, fields);
chart.append(segment);
chart.points.push(segment);
return segment;
}
},
createLabel: function (value, fields) {
var chart = this, series = fields.series, dataItem = fields.dataItem, labels = deepExtend({}, chart.options.labels, series.labels), text = value, textBox;
if (labels.visible) {
if (labels.template) {
var labelTemplate = template(labels.template);
text = labelTemplate({
dataItem: dataItem,
value: value,
percentage: fields.percentage,
category: fields.category,
series: series
});
} else if (labels.format) {
text = autoFormat(labels.format, text);
}
if (!labels.color && labels.align === 'center') {
var brightnessValue = new Color(series.color).percBrightness();
if (brightnessValue > 180) {
labels.color = BLACK;
} else {
labels.color = WHITE;
}
}
chart.evalSegmentOptions(labels, value, fields);
textBox = new TextBox(text, deepExtend({ vAlign: labels.position }, labels));
chart.labels.push(textBox);
return textBox;
}
},
labelPadding: function () {
var labels = this.labels, label, align, width, padding = {
left: 0,
right: 0
}, i;
for (i = 0; i < labels.length; i++) {
label = labels[i];
align = label.options.align;
if (align !== 'center') {
width = labels[i].box.width();
if (align === 'left') {
padding.left = Math.max(padding.left, width);
} else {
padding.right = Math.max(padding.right, width);
}
}
}
return padding;
},
reflow: function (chartBox) {
var chart = this, options = chart.options, segments = chart.points, count = segments.length, decreasingWidth = options.neckRatio <= 1, i, height, lastUpperSide, points, percentage, offset, box = chartBox.clone().unpad(chart.labelPadding()), width = box.width(), previousHeight = 0, previousOffset = decreasingWidth ? 0 : (width - width / options.neckRatio) / 2, segmentSpacing = options.segmentSpacing, dynamicSlope = options.dynamicSlope, totalHeight = box.height() - segmentSpacing * (count - 1), neckRatio = decreasingWidth ? options.neckRatio * width : width;
if (!count) {
return;
}
if (dynamicSlope) {
var firstSegment = segments[0], maxSegment = firstSegment, nextSegment, nextPercentage;
$.each(segments, function (idx, val) {
if (val.percentage > maxSegment.percentage) {
maxSegment = val;
}
});
lastUpperSide = firstSegment.percentage / maxSegment.percentage * width;
previousOffset = (width - lastUpperSide) / 2;
for (i = 0; i < count; i++) {
percentage = segments[i].percentage;
nextSegment = segments[i + 1];
nextPercentage = nextSegment ? nextSegment.percentage : percentage;
points = segments[i].points = [];
height = options.dynamicHeight ? totalHeight * percentage : totalHeight / count;
if (!percentage) {
offset = nextPercentage ? 0 : width / 2;
} else {
offset = (width - lastUpperSide * (nextPercentage / percentage)) / 2;
}
offset = limitValue(offset, 0, width);
points.push(new geom.Point(box.x1 + previousOffset, box.y1 + previousHeight));
points.push(new geom.Point(box.x1 + width - previousOffset, box.y1 + previousHeight));
points.push(new geom.Point(box.x1 + width - offset, box.y1 + height + previousHeight));
points.push(new geom.Point(box.x1 + offset, box.y1 + height + previousHeight));
previousOffset = offset;
previousHeight += height + segmentSpacing;
lastUpperSide = limitValue(width - 2 * offset, 0, width);
}
} else {
var topMostWidth = decreasingWidth ? width : width - previousOffset * 2, finalNarrow = (topMostWidth - neckRatio) / 2;
for (i = 0; i < count; i++) {
points = segments[i].points = [];
percentage = segments[i].percentage;
offset = options.dynamicHeight ? finalNarrow * percentage : finalNarrow / count;
height = options.dynamicHeight ? totalHeight * percentage : totalHeight / count;
points.push(new geom.Point(box.x1 + previousOffset, box.y1 + previousHeight));
points.push(new geom.Point(box.x1 + width - previousOffset, box.y1 + previousHeight));
points.push(new geom.Point(box.x1 + width - previousOffset - offset, box.y1 + height + previousHeight));
points.push(new geom.Point(box.x1 + previousOffset + offset, box.y1 + height + previousHeight));
previousOffset += offset;
previousHeight += height + segmentSpacing;
}
}
for (i = 0; i < count; i++) {
segments[i].reflow(chartBox);
}
}
});
deepExtend(FunnelChart.fn, PieChartMixin);
var FunnelSegment = ChartElement.extend({
init: function (value, options, segmentOptions) {
var segment = this;
ChartElement.fn.init.call(segment, options);
segment.value = value;
segment.options.index = segmentOptions.index;
},
options: {
color: WHITE,
border: { width: 1 }
},
reflow: function (chartBox) {
var segment = this, points = segment.points, label = segment.children[0];
segment.box = new Box2D(points[0].x, points[0].y, points[1].x, points[2].y);
if (label) {
label.reflow(new Box2D(chartBox.x1, points[0].y, chartBox.x2, points[2].y));
}
},
createVisual: function () {
var segment = this;
var options = segment.options;
var visual;
ChartElement.fn.createVisual.call(this);
if (options.visual) {
visual = options.visual({
category: segment.category,
dataItem: segment.dataItem,
value: segment.value,
series: segment.series,
percentage: segment.percentage,
points: segment.points,
options: options,
createVisual: function () {
return segment.createPath();
}
});
} else {
visual = segment.createPath();
}
if (visual) {
this.visual.append(visual);
}
},
createPath: function () {
var options = this.options;
var border = options.border;
var path = draw.Path.fromPoints(this.points, {
fill: {
color: options.color,
opacity: options.opacity
},
stroke: {
color: border.color,
opacity: border.opacity,
width: border.width
}
}).close();
return path;
},
createHighlight: function (style) {
return draw.Path.fromPoints(this.points, style);
},
highlightVisual: function () {
return this.visual.children[0];
},
highlightVisualArgs: function () {
var path = draw.Path.fromPoints(this.points).close();
return {
options: this.options,
path: path
};
},
highlightOverlay: function (view, opt) {
var options = this.options, hlOptions = options.highlight || {};
if (hlOptions.visible === false) {
return;
}
var border = hlOptions.border || {};
var calcOptions = extend({}, opt, {
fill: hlOptions.color,
stroke: border.color,
strokeOpacity: border.opacity,
strokeWidth: border.width,
fillOpacity: hlOptions.opacity
});
var element = view.createPolyline(this.points, true, calcOptions);
return element;
},
tooltipAnchor: function (tooltipWidth) {
var box = this.box;
return new Point2D(box.center().x - tooltipWidth / 2, box.y1);
},
formatValue: function (format) {
var point = this;
return point.owner.formatPointValue(point, format);
}
});
deepExtend(FunnelSegment.fn, dataviz.PointEventsMixin);
PlotAreaFactory.current.register(FunnelPlotArea, [FUNNEL]);
SeriesBinder.current.register([FUNNEL], [VALUE], [
CATEGORY,
COLOR,
'visibleInLegend',
'visible'
]);
deepExtend(dataviz, {
FunnelChart: FunnelChart,
FunnelSegment: FunnelSegment
});
}(window.kendo.jQuery));
return window.kendo;
}, typeof define == 'function' && define.amd ? define : function (a1, a2, a3) {
(a3 || a2)();
}));
(function (f, define) {
define('kendo.dataviz.gauge', [
'kendo.dataviz.core',
'kendo.drawing',
'kendo.dataviz.themes'
], f);
}(function () {
var __meta__ = {
id: 'dataviz.gauge',
name: 'Gauge',
category: 'dataviz',
description: 'Radial and Linear gauges.',
depends: [
'dataviz.core',
'dataviz.themes'
]
};
(function ($, undefined) {
var math = Math, kendo = window.kendo, util = kendo.util, Widget = kendo.ui.Widget, deepExtend = kendo.deepExtend, dataviz = kendo.dataviz, autoMajorUnit = dataviz.autoMajorUnit, ChartElement = dataviz.ChartElement, NumericAxis = dataviz.NumericAxis, Axis = dataviz.Axis, Box2D = dataviz.Box2D, Class = kendo.Class, defined = util.defined, isNumber = util.isNumber, interpolateValue = dataviz.interpolateValue, getSpacing = dataviz.getSpacing, round = dataviz.round, geo = dataviz.geometry, draw = dataviz.drawing, Point = geo.Point, Group = draw.Group, Path = draw.Path, Rect = geo.Rect, Text = draw.Text;
var ANGULAR_SPEED = 150, LINEAR_SPEED = 250, ARROW = 'arrow', ARROW_POINTER = 'arrowPointer', BAR_POINTER = 'barPointer', BLACK = '#000', CAP_SIZE = 0.05, COORD_PRECISION = dataviz.COORD_PRECISION, MAX_VALUE = Number.MAX_VALUE, MIN_VALUE = -Number.MAX_VALUE, DEFAULT_HEIGHT = 200, DEFAULT_LINE_WIDTH = 0.5, DEFAULT_WIDTH = 200, DEFAULT_MIN_WIDTH = 60, DEFAULT_MIN_HEIGHT = 60, DEFAULT_MARGIN = 5, DEGREE = math.PI / 180, GEO_ARC_ADJUST_ANGLE = 180, INSIDE = 'inside', LINEAR = 'linear', NEEDLE = 'needle', OUTSIDE = 'outside', RADIAL_POINTER = 'radialPointer', X = 'x', Y = 'y';
var Pointer = Class.extend({
init: function (scale, options) {
var pointer = this;
var scaleOptions = scale.options;
ChartElement.fn.init.call(pointer, options);
options = pointer.options;
options.fill = options.color;
pointer.scale = scale;
if (defined(options.value)) {
options.value = math.min(math.max(options.value, scaleOptions.min), scaleOptions.max);
} else {
options.value = scaleOptions.min;
}
},
options: { color: BLACK },
value: function (newValue) {
var that = this;
var options = that.options;
var value = options.value;
var scaleOptions = that.scale.options;
if (arguments.length === 0) {
return value;
}
options._oldValue = options._oldValue !== undefined ? options.value : scaleOptions.min;
options.value = math.min(math.max(newValue, scaleOptions.min), scaleOptions.max);
if (that.elements) {
that.repaint();
}
}
});
var RadialPointer = Pointer.extend({
options: {
shape: NEEDLE,
cap: { size: CAP_SIZE },
arrow: {
width: 16,
height: 14
},
animation: {
type: RADIAL_POINTER,
duration: ANGULAR_SPEED
}
},
setRadius: function (radius) {
var that = this;
if (radius) {
that.elements.clear();
that.render(that.parent, that.center, radius);
}
},
setAngle: function (angle) {
this.elements.transform(geo.transform().rotate(angle, this.center));
},
repaint: function () {
var that = this;
var scale = that.scale;
var options = that.options;
var oldAngle = scale.slotAngle(options._oldValue);
var newAngle = scale.slotAngle(options.value);
if (options.animation.transitions === false) {
that.setAngle(newAngle);
} else {
new RadialPointerAnimation(that.elements, deepExtend(options.animation, {
oldAngle: oldAngle,
newAngle: newAngle
})).play();
}
},
render: function () {
var that = this;
var scale = that.scale;
var center = scale.arc.center;
var options = that.options;
var elements = new Group();
if (options.animation !== false) {
deepExtend(options.animation, {
startAngle: 0,
center: center,
reverse: scale.options.reverse
});
}
if (options.shape === NEEDLE) {
elements.append(that._renderNeedle(), that._renderCap());
} else {
elements.append(that._renderArrow());
}
that.elements = elements;
that.setAngle(DEGREE);
return elements;
},
reflow: function (arc) {
var that = this;
var center = that.center = arc.center;
var radius = that.radius = arc.getRadiusX();
var capSize = that.capSize = Math.round(radius * that.options.cap.size);
that.bbox = Rect.fromPoints(new Point(center.x - capSize, center.y - capSize), new Point(center.x + capSize, center.y + capSize));
},
_renderNeedle: function () {
var that = this;
var options = that.options;
var minorTickSize = that.scale.options.minorTicks.size;
var center = that.center;
var needleColor = options.color;
var needlePath = new Path({
fill: { color: needleColor },
stroke: {
color: needleColor,
width: DEFAULT_LINE_WIDTH
}
});
needlePath.moveTo(center.x + that.radius - minorTickSize, center.y).lineTo(center.x, center.y - that.capSize / 2).lineTo(center.x, center.y + that.capSize / 2).close();
return needlePath;
},
_renderCap: function () {
var that = this;
var options = that.options;
var capColor = options.cap.color || options.color;
var circle = new geo.Circle(that.center, that.capSize);
var cap = new draw.Circle(circle, {
fill: { color: capColor },
stroke: { color: capColor }
});
return cap;
}
});
var RadialScale = NumericAxis.extend({
init: function (options) {
var scale = this;
scale.options = deepExtend({}, scale.options, options);
scale.options.majorUnit = scale.options.majorUnit || autoMajorUnit(scale.options.min, scale.options.max);
scale.options.minorUnit = scale.options.minorUnit || scale.options.majorUnit / 10;
Axis.fn.init.call(scale, scale.options);
},
options: {
min: 0,
max: 100,
majorTicks: {
size: 15,
align: INSIDE,
color: BLACK,
width: DEFAULT_LINE_WIDTH,
visible: true
},
minorTicks: {
size: 10,
align: INSIDE,
color: BLACK,
width: DEFAULT_LINE_WIDTH,
visible: true
},
startAngle: -30,
endAngle: 210,
labels: {
position: INSIDE,
padding: 2
}
},
render: function (center, radius) {
var that = this;
var arc = that.renderArc(center, radius);
that.bbox = arc.bbox();
that.labelElements = that.renderLabels();
that.ticks = that.renderTicks();
that.ranges = that.renderRanges();
},
reflow: function (bbox) {
var that = this;
var center = bbox.center();
var radius = math.min(bbox.height(), bbox.width()) / 2;
if (that.bbox !== undefined) {
that.bbox = that.arc.bbox();
that.radius(that.arc.getRadiusX());
that.repositionRanges();
that.renderLabels();
} else {
return that.render(center, radius);
}
},
slotAngle: function (value) {
var options = this.options;
var startAngle = options.startAngle;
var reverse = options.reverse;
var angle = options.endAngle - startAngle;
var min = options.min;
var max = options.max;
var result;
if (reverse) {
result = options.endAngle - (value - min) / (max - min) * angle;
} else {
result = (value - min) / (max - min) * angle + startAngle;
}
return result + GEO_ARC_ADJUST_ANGLE;
},
renderLabels: function () {
var that = this;
var options = that.options;
var majorTickSize = options.majorTicks.size;
var arc = that.arc.clone();
var radius = arc.getRadiusX();
var tickAngles = that.tickAngles(arc, options.majorUnit);
var labels = that.labels;
var count = labels.length;
var labelsOptions = options.labels;
var padding = labelsOptions.padding;
var rangeDistance = radius * 0.05;
var rangeSize = options.rangeSize = options.rangeSize || radius * 0.1;
var ranges = options.ranges || [];
var halfWidth, halfHeight, labelAngle;
var angle, label, lp, i, cx, cy, isInside;
var labelsGroup = new Group();
var lbl, labelPos, prevLabelPos, labelTransform;
if (that.options.rangeDistance !== undefined) {
rangeDistance = that.options.rangeDistance;
} else {
that.options.rangeDistance = rangeDistance;
}
if (labelsOptions.position === INSIDE) {
radius -= majorTickSize;
if (ranges.length && that.labelElements === undefined) {
radius -= rangeSize + rangeDistance;
}
arc.setRadiusX(radius).setRadiusY(radius);
}
for (i = 0; i < count; i++) {
label = labels[i];
halfWidth = label.box.width() / 2;
halfHeight = label.box.height() / 2;
angle = tickAngles[i];
labelAngle = (angle - GEO_ARC_ADJUST_ANGLE) * DEGREE;
isInside = labelsOptions.position === INSIDE;
lp = arc.pointAt(angle);
cx = lp.x + math.cos(labelAngle) * (halfWidth + padding) * (isInside ? 1 : -1);
cy = lp.y + math.sin(labelAngle) * (halfHeight + padding) * (isInside ? 1 : -1);
label.reflow(new dataviz.Box2D(cx - halfWidth, cy - halfHeight, cx + halfWidth, cy + halfHeight));
labelPos = new Point(label.box.x1, label.box.y1);
if (that.labelElements === undefined) {
lbl = _buildLabel(label, options.labels);
labelsGroup.append(lbl);
} else {
lbl = that.labelElements.children[i];
prevLabelPos = lbl.bbox().origin;
labelTransform = lbl.transform() || geo.transform();
labelTransform.translate(labelPos.x - prevLabelPos.x, labelPos.y - prevLabelPos.y);
lbl.transform(labelTransform);
}
that.bbox = Rect.union(that.bbox, lbl.bbox());
}
return labelsGroup;
},
repositionRanges: function () {
var that = this;
var ranges = that.ranges.children;
var rangeSize = that.options.rangeSize;
var rangeDistance = that.options.rangeDistance;
var rangeRadius, newRadius;
if (ranges.length > 0) {
rangeRadius = that.getRangeRadius();
if (that.options.labels.position === INSIDE) {
rangeRadius += rangeSize + rangeDistance;
}
newRadius = rangeRadius + rangeSize / 2;
for (var i = 0; i < ranges.length; i++) {
ranges[i]._geometry.setRadiusX(newRadius).setRadiusY(newRadius);
}
that.bbox = Rect.union(that.bbox, that.ranges.bbox());
}
},
renderRanges: function () {
var that = this;
var arc = that.arc;
var result = new Group();
var from, to;
var segments = that.rangeSegments();
var segmentsCount = segments.length;
var reverse = that.options.reverse;
var rangeSize = that.options.rangeSize;
var rangeDistance = that.options.rangeDistance;
var segment, rangeRadius, rangeGeom, i;
if (segmentsCount) {
rangeRadius = that.getRangeRadius();
that.radius(that.radius() - rangeSize - rangeDistance);
for (i = 0; i < segmentsCount; i++) {
segment = segments[i];
from = that.slotAngle(segment[reverse ? 'to' : 'from']);
to = that.slotAngle(segment[!reverse ? 'to' : 'from']);
if (to - from !== 0) {
rangeGeom = new geo.Arc(arc.center, {
radiusX: rangeRadius + rangeSize / 2,
radiusY: rangeRadius + rangeSize / 2,
startAngle: from,
endAngle: to
});
result.append(new draw.Arc(rangeGeom, {
stroke: {
width: rangeSize,
color: segment.color,
opacity: segment.opacity
}
}));
}
}
}
return result;
},
rangeSegments: function () {
var gauge = this;
var options = gauge.options;
var ranges = options.ranges || [];
var count = ranges.length;
var range;
var segmentsCount;
var defaultColor = options.rangePlaceholderColor;
var segments = [];
var segment;
var min = options.min;
var max = options.max;
var i, j;
function rangeSegment(from, to, color, opacity) {
return {
from: from,
to: to,
color: color,
opacity: opacity
};
}
if (count) {
segments.push(rangeSegment(min, max, defaultColor));
for (i = 0; i < count; i++) {
range = getRange(ranges[i], min, max);
segmentsCount = segments.length;
for (j = 0; j < segmentsCount; j++) {
segment = segments[j];
if (segment.from <= range.from && range.from <= segment.to) {
segments.push(rangeSegment(range.from, range.to, range.color, range.opacity));
if (segment.from <= range.to && range.to <= segment.to) {
segments.push(rangeSegment(range.to, segment.to, defaultColor, range.opacity));
}
segment.to = range.from;
break;
}
}
}
}
return segments;
},
getRangeRadius: function () {
var that = this;
var options = that.options;
var majorTickSize = options.majorTicks.size;
var rangeSize = options.rangeSize;
var rangeDistance = options.rangeDistance;
var arc = that.arc;
var r;
if (options.labels.position === OUTSIDE) {
r = arc.getRadiusX() - majorTickSize - rangeDistance - rangeSize;
} else {
r = arc.getRadiusX() - rangeSize;
}
return r;
},
renderArc: function (center, radius) {
var that = this;
var options = that.options;
var arc = that.arc = new geo.Arc(center, {
radiusX: radius,
radiusY: radius,
startAngle: options.startAngle + GEO_ARC_ADJUST_ANGLE,
endAngle: options.endAngle + GEO_ARC_ADJUST_ANGLE
});
return arc;
},
renderTicks: function () {
var that = this;
var arc = that.arc;
var options = that.options;
var labelsPosition = options.labels.position;
var allTicks = new Group();
var majorTickSize = options.majorTicks.size;
var minorTickSize = options.minorTicks.size;
var tickArc = arc.clone();
var radius = tickArc.getRadiusX();
function drawTicks(arc, tickAngles, unit, tickOptions) {
var ticks = new Group(), center = arc.center, radius = arc.getRadiusX(), i, tickStart, tickEnd, visible = tickOptions.visible;
if (visible) {
for (i = 0; i < tickAngles.length; i++) {
tickStart = arc.pointAt(tickAngles[i]);
tickEnd = new Point(center.x + radius - tickOptions.size, center.y).rotate(tickAngles[i], center);
ticks.append(new Path({
stroke: {
color: tickOptions.color,
width: tickOptions.width
}
}).moveTo(tickStart).lineTo(tickEnd));
}
}
return ticks;
}
that.majorTickAngles = that.tickAngles(arc, options.majorUnit);
that.majorTicks = drawTicks(tickArc, that.majorTickAngles, options.majorUnit, options.majorTicks);
allTicks.append(that.majorTicks);
that._tickDifference = majorTickSize - minorTickSize;
if (labelsPosition === OUTSIDE) {
tickArc.setRadiusX(radius - majorTickSize + minorTickSize).setRadiusY(radius - majorTickSize + minorTickSize);
}
that.minorTickAngles = that.normalizeTickAngles(that.tickAngles(arc, options.minorUnit));
that.minorTicks = drawTicks(tickArc, that.minorTickAngles, options.minorUnit, options.minorTicks, options.majorUnit);
allTicks.append(that.minorTicks);
return allTicks;
},
normalizeTickAngles: function (angles) {
var that = this;
var options = that.options;
var skip = options.majorUnit / options.minorUnit;
for (var i = angles.length - 1; i >= 0; i--) {
if (i % skip === 0) {
angles.splice(i, 1);
}
}
return angles;
},
tickAngles: function (ring, stepValue) {
var scale = this;
var options = scale.options;
var reverse = options.reverse;
var range = options.max - options.min;
var angle = ring.endAngle - ring.startAngle;
var pos = ring.startAngle;
var tickCount = range / stepValue;
var step = angle / tickCount;
var positions = [];
var i;
if (reverse) {
pos += angle;
step = -step;
}
for (i = 0; i < tickCount; i++) {
positions.push(round(pos, COORD_PRECISION));
pos += step;
}
if (round(pos) <= ring.endAngle) {
positions.push(pos);
}
return positions;
},
radius: function (radius) {
var that = this;
if (radius) {
that.arc.setRadiusX(radius).setRadiusY(radius);
that.repositionTicks(that.majorTicks.children, that.majorTickAngles);
that.repositionTicks(that.minorTicks.children, that.minorTickAngles, true);
} else {
return that.arc.getRadiusX();
}
},
repositionTicks: function (ticks, tickAngles, minor) {
var that = this;
var diff = minor ? that._tickDifference || 0 : 0;
var tickArc = that.arc;
var radius = tickArc.getRadiusX();
if (minor && that.options.labels.position === OUTSIDE && diff !== 0) {
tickArc = that.arc.clone();
tickArc.setRadiusX(radius - diff).setRadiusY(radius - diff);
}
for (var i = 0; i < ticks.length; i++) {
var newPoint = tickArc.pointAt(tickAngles[i]);
var segments = ticks[i].segments;
var xDiff = newPoint.x - segments[0].anchor().x;
var yDiff = newPoint.y - segments[0].anchor().y;
ticks[i].transform(new geo.Transformation().translate(xDiff, yDiff));
}
}
});
var Gauge = Widget.extend({
init: function (element, userOptions) {
var gauge = this;
var options;
var themeOptions;
var themeName;
var themes = dataviz.ui.themes || {};
var theme;
kendo.destroy(element);
$(element).empty();
Widget.fn.init.call(gauge, element);
gauge.wrapper = gauge.element;
gauge._originalOptions = deepExtend({}, userOptions);
options = deepExtend({}, gauge.options, userOptions);
themeName = options.theme;
theme = themes[themeName] || themes[themeName.toLowerCase()];
themeOptions = themeName && theme ? theme.gauge : {};
gauge.options = deepExtend({}, themeOptions, options);
if ($.isArray(options.pointer)) {
for (var i = 0; i < options.pointer.length; i++) {
gauge.options.pointer[i] = deepExtend({}, themeOptions.pointer, options.pointer[i]);
}
}
gauge.element.addClass('k-gauge');
gauge.surface = gauge._createSurface();
gauge.redraw();
},
options: {
plotArea: {},
theme: 'default',
renderAs: '',
pointer: {},
scale: {},
gaugeArea: {}
},
destroy: function () {
this.surface.destroy();
Widget.fn.destroy.call(this);
},
value: function (value) {
var that = this;
var pointer = that.pointers[0];
if (arguments.length === 0) {
return pointer.value();
}
pointer.value(value);
that._setValueOptions(value);
},
_draw: function () {
var surface = this.surface;
surface.clear();
surface.draw(this._visuals);
},
exportVisual: function () {
return this._visuals;
},
allValues: function (values) {
var that = this;
var pointers = that.pointers;
var allValues = [];
var i;
if (arguments.length === 0) {
for (i = 0; i < pointers.length; i++) {
allValues.push(pointers[i].value());
}
return allValues;
}
if ($.isArray(values)) {
for (i = 0; i < values.length; i++) {
if (isNumber(values[i])) {
pointers[i].value(values[i]);
}
}
}
that._setValueOptions(values);
},
_setValueOptions: function (values) {
var pointers = [].concat(this.options.pointer);
values = [].concat(values);
for (var i = 0; i < values.length; i++) {
pointers[i].value = values[i];
}
},
_resize: function () {
var that = this;
var t = that.options.transitions;
var i;
that.options.transitions = false;
for (i = 0; i < that.pointers.length; i++) {
that.pointers[i].options.animation.transitions = false;
}
that.redraw();
that.options.transitions = t;
for (i = 0; i < that.pointers.length; i++) {
that.pointers[i].options.animation.transitions = t;
}
},
redraw: function () {
var that = this;
var size = deepExtend(that._getSize(), that.options.gaugeArea);
var wrapper = new Rect([
0,
0
], [
size.width,
size.height
]);
var bbox;
that.surface.clear();
that.gaugeArea = that._createGaugeArea();
that.surface.element.css({
width: size.width,
height: size.height
});
that._createModel();
bbox = _unpad(wrapper.bbox(), that._gaugeAreaMargin);
that.reflow(bbox);
},
_createGaugeArea: function () {
var that = this;
var options = that.options.gaugeArea;
var size = that.surface.size();
var border = options.border || {};
var areaGeometry = new Rect([
0,
0
], [
size.width,
size.height
]);
that._gaugeAreaMargin = options.margin || DEFAULT_MARGIN;
if (border.width > 0) {
areaGeometry = _unpad(areaGeometry, border.width);
}
var gaugeArea = Path.fromRect(areaGeometry, {
stroke: {
color: border.width ? border.color : '',
width: border.width,
dashType: border.dashType,
lineJoin: 'round',
lineCap: 'round'
},
fill: { color: options.background }
});
return gaugeArea;
},
_createSurface: function () {
var that = this;
var options = that.options;
var size = that._getSize();
size = options.gaugeArea ? deepExtend(size, options.gaugeArea) : size;
var wrap = $('
').appendTo(that.element).css({
width: size.width,
height: size.height
});
return new draw.Surface.create(wrap, { type: options.renderAs });
},
getSize: function () {
return this._getSize();
},
_getSize: function () {
var that = this;
var element = that.element;
var width = element.width();
var height = element.height();
if (!width) {
width = DEFAULT_WIDTH;
}
if (!height) {
height = DEFAULT_HEIGHT;
}
return {
width: width,
height: height
};
}
});
var RadialGauge = Gauge.extend({
init: function (element, options) {
var radialGauge = this;
Gauge.fn.init.call(radialGauge, element, options);
kendo.notify(radialGauge, dataviz.ui);
},
options: {
name: 'RadialGauge',
transitions: true,
gaugeArea: { background: '' }
},
reflow: function (bbox) {
var that = this;
var pointers = that.pointers;
that.scale.reflow(bbox);
that._initialPlotArea = that.scale.bbox;
for (var i = 0; i < pointers.length; i++) {
pointers[i].reflow(that.scale.arc);
that._initialPlotArea = Rect.union(that._initialPlotArea, pointers[i].bbox);
}
that.fitScale(bbox);
that.alignScale(bbox);
that._buildVisual(that.gaugeArea, pointers, that.scale);
that._draw();
},
_buildVisual: function (gaugeArea, pointers, scale) {
var visuals = new Group();
var current;
visuals.append(gaugeArea);
visuals.append(scale.ticks);
visuals.append(scale.ranges);
for (var i = 0; i < pointers.length; i++) {
current = pointers[i];
current.render();
visuals.append(current.elements);
current.value(current.options.value);
}
visuals.append(scale.labelElements);
this._visuals = visuals;
},
fitScale: function (bbox) {
var that = this;
var scale = that.scale;
var arc = scale.arc;
var plotAreaBox = that._initialPlotArea;
var step = math.abs(that.getDiff(plotAreaBox, bbox));
var min = round(step, COORD_PRECISION);
var max = round(-step, COORD_PRECISION);
var minDiff, midDiff, maxDiff, mid, oldDiff;
var staleFlag = 0;
var i = 0;
while (i++ < 100) {
staleFlag = oldDiff === maxDiff ? staleFlag + 1 : 0;
if (staleFlag > 5) {
break;
}
if (min != mid) {
minDiff = that.getPlotBox(min, bbox, arc);
if (0 <= minDiff && minDiff <= 2) {
break;
}
}
if (max != mid) {
maxDiff = that.getPlotBox(max, bbox, arc);
if (0 <= maxDiff && maxDiff <= 2) {
break;
}
}
if (minDiff > 0 && maxDiff > 0) {
mid = min * 2;
} else if (minDiff < 0 && maxDiff < 0) {
mid = max * 2;
} else {
mid = round((min + max) / 2 || 1, COORD_PRECISION);
}
midDiff = that.getPlotBox(mid, bbox, arc);
if (0 <= midDiff && midDiff <= 2) {
break;
}
oldDiff = maxDiff;
if (midDiff > 0) {
max = mid;
maxDiff = midDiff;
} else {
min = mid;
minDiff = midDiff;
}
}
},
getPlotBox: function (step, bbox, arc) {
var that = this;
var scale = that.scale;
var pointers = that.pointers;
var radius = arc.getRadiusX();
arc = arc.clone();
arc.setRadiusX(radius + step).setRadiusY(radius + step);
scale.arc = arc;
scale.reflow(bbox);
that.plotBbox = scale.bbox;
for (var i = 0; i < pointers.length; i++) {
pointers[i].reflow(arc);
that.plotBbox = Rect.union(that.plotBbox, pointers[i].bbox);
}
return that.getDiff(that.plotBbox, bbox);
},
getDiff: function (plotBox, box) {
return math.min(box.width() - plotBox.width(), box.height() - plotBox.height());
},
alignScale: function (bbox) {
var that = this;
var plotBoxCenter = that.plotBbox.center();
var boxCenter = bbox.center();
var paddingX = plotBoxCenter.x - boxCenter.x;
var paddingY = plotBoxCenter.y - boxCenter.y;
var scale = that.scale;
var pointers = that.pointers;
scale.arc.center.x -= paddingX;
scale.arc.center.y -= paddingY;
scale.reflow(bbox);
for (var i = 0; i < pointers.length; i++) {
pointers[i].reflow(scale.arc);
that.plotBbox = Rect.union(scale.bbox, pointers[i].bbox);
}
},
_createModel: function () {
var that = this;
var options = that.options;
var pointers = options.pointer;
var scale = that.scale = new RadialScale(options.scale);
var current;
that.pointers = [];
pointers = $.isArray(pointers) ? pointers : [pointers];
for (var i = 0; i < pointers.length; i++) {
current = new RadialPointer(scale, deepExtend({}, pointers[i], { animation: { transitions: options.transitions } }));
that.pointers.push(current);
}
}
});
var LinearGauge = Gauge.extend({
init: function (element, options) {
var linearGauge = this;
Gauge.fn.init.call(linearGauge, element, options);
kendo.notify(linearGauge, dataviz.ui);
},
options: {
name: 'LinearGauge',
transitions: true,
gaugeArea: { background: '' },
scale: { vertical: true }
},
reflow: function (bbox) {
var that = this;
var pointers = that.pointers;
var bboxX = bbox.origin.x;
var bboxY = bbox.origin.y;
var bbox2D = new dataviz.Box2D(bboxX, bboxX, bboxX + bbox.width(), bboxY + bbox.height());
that.scale.reflow(bbox2D);
for (var i = 0; i < pointers.length; i++) {
pointers[i].reflow();
}
that.bbox = that._getBox(bbox2D);
that._alignElements();
that._shrinkElements();
that._buildVisual();
that._draw();
},
_buildVisual: function () {
var that = this;
var visuals = new Group();
var scaleElements = that.scale.render();
var pointers = that.pointers;
var current;
visuals.append(that.gaugeArea);
visuals.append(scaleElements);
for (var i = 0; i < pointers.length; i++) {
current = pointers[i];
visuals.append(current.render());
current.value(current.options.value);
}
that._visuals = visuals;
},
_createModel: function () {
var that = this;
var options = that.options;
var pointers = options.pointer;
var scale = that.scale = new LinearScale(options.scale);
var current, currentOptions;
that.pointers = [];
pointers = $.isArray(pointers) ? pointers : [pointers];
for (var i = 0; i < pointers.length; i++) {
currentOptions = deepExtend({}, pointers[i], { animation: { transitions: options.transitions } });
if (currentOptions.shape === ARROW) {
current = new ArrowLinearPointer(scale, currentOptions);
} else {
current = new BarLinearPointer(scale, currentOptions);
}
that.pointers.push(current);
}
},
_getSize: function () {
var gauge = this;
var element = gauge.element;
var width = element.width();
var height = element.height();
var vertical = gauge.options.scale.vertical;
if (!width) {
width = vertical ? DEFAULT_MIN_WIDTH : DEFAULT_WIDTH;
}
if (!height) {
height = vertical ? DEFAULT_HEIGHT : DEFAULT_MIN_HEIGHT;
}
return {
width: width,
height: height
};
},
_getBox: function (box) {
var that = this;
var scale = that.scale;
var pointers = that.pointers;
var boxCenter = box.center();
var plotAreaBox = pointers[0].box.clone().wrap(scale.box);
var size;
for (var i = 0; i < pointers.length; i++) {
plotAreaBox.wrap(pointers[i].box.clone());
}
if (scale.options.vertical) {
size = plotAreaBox.width() / 2;
plotAreaBox = new Box2D(boxCenter.x - size, box.y1, boxCenter.x + size, box.y2);
} else {
size = plotAreaBox.height() / 2;
plotAreaBox = new Box2D(box.x1, boxCenter.y - size, box.x2, boxCenter.y + size);
}
return plotAreaBox;
},
_alignElements: function () {
var that = this;
var scale = that.scale;
var pointers = that.pointers;
var scaleBox = scale.box;
var box = pointers[0].box.clone().wrap(scale.box);
var plotAreaBox = that.bbox;
var diff, i;
for (i = 0; i < pointers.length; i++) {
box.wrap(pointers[i].box.clone());
}
if (scale.options.vertical) {
diff = plotAreaBox.center().x - box.center().x;
scale.reflow(new Box2D(scaleBox.x1 + diff, plotAreaBox.y1, scaleBox.x2 + diff, plotAreaBox.y2));
} else {
diff = plotAreaBox.center().y - box.center().y;
scale.reflow(new Box2D(plotAreaBox.x1, scaleBox.y1 + diff, plotAreaBox.x2, scaleBox.y2 + diff));
}
for (i = 0; i < pointers.length; i++) {
pointers[i].reflow(that.bbox);
}
},
_shrinkElements: function () {
var that = this;
var scale = that.scale;
var pointers = that.pointers;
var scaleBox = scale.box.clone();
var pos = scale.options.vertical ? 'y' : 'x';
var pointerBox = pointers[0].box;
var i;
for (i = 0; i < pointers.length; i++) {
pointerBox.wrap(pointers[i].box.clone());
}
scaleBox[pos + 1] += math.max(scaleBox[pos + 1] - pointerBox[pos + 1], 0);
scaleBox[pos + 2] -= math.max(pointerBox[pos + 2] - scaleBox[pos + 2], 0);
scale.reflow(scaleBox);
for (i = 0; i < pointers.length; i++) {
pointers[i].reflow(that.bbox);
}
}
});
var LinearScale = NumericAxis.extend({
init: function (options) {
var scale = this;
scale.options = deepExtend({}, scale.options, options);
scale.options = deepExtend({}, scale.options, { labels: { mirror: scale.options.mirror } });
scale.options.majorUnit = scale.options.majorUnit || autoMajorUnit(scale.options.min, scale.options.max);
Axis.fn.init.call(scale, scale.options);
scale.options.minorUnit = scale.options.minorUnit || scale.options.majorUnit / 10;
},
options: {
min: 0,
max: 50,
majorTicks: {
size: 15,
align: INSIDE,
color: BLACK,
width: DEFAULT_LINE_WIDTH,
visible: true
},
minorTicks: {
size: 10,
align: INSIDE,
color: BLACK,
width: DEFAULT_LINE_WIDTH,
visible: true
},
line: { width: DEFAULT_LINE_WIDTH },
labels: {
position: INSIDE,
padding: 2
},
mirror: false,
_alignLines: false
},
render: function () {
var that = this;
var elements = that.elements = new Group();
var labels = that.renderLabels();
var scaleLine = that.renderLine();
var scaleTicks = that.renderTicks();
var ranges = that.renderRanges();
elements.append(scaleLine, labels, scaleTicks, ranges);
return elements;
},
renderRanges: function () {
var that = this;
var options = that.options;
var min = options.min;
var max = options.max;
var ranges = options.ranges || [];
var vertical = options.vertical;
var mirror = options.labels.mirror;
var elements = new Group();
var count = ranges.length;
var rangeSize = options.rangeSize || options.minorTicks.size / 2;
var range, slot, slotX, slotY, i;
if (count) {
for (i = 0; i < count; i++) {
range = getRange(ranges[i], min, max);
slot = that.getSlot(range.from, range.to);
slotX = vertical ? that.lineBox() : slot;
slotY = vertical ? slot : that.lineBox();
if (vertical) {
slotX.x1 -= rangeSize * (mirror ? -1 : 1);
} else {
slotY.y2 += rangeSize * (mirror ? -1 : 1);
}
elements.append(Path.fromRect(new Rect([
slotX.x1,
slotY.y1
], [
slotX.x2 - slotX.x1,
slotY.y2 - slotY.y1
]), {
fill: {
color: range.color,
opacity: range.opacity
},
stroke: {}
}));
}
}
return elements;
},
renderLabels: function () {
var that = this;
var options = that.options;
var labels = that.labels;
var elements = new Group();
for (var i = 0; i < labels.length; i++) {
elements.append(_buildLabel(labels[i], options.labels));
}
return elements;
},
renderLine: function () {
var that = this;
var options = that.options;
var line = options.line;
var lineBox = that.lineBox();
var linePath;
var elements = new Group();
if (line.width > 0 && line.visible) {
linePath = new Path({
stroke: {
color: line.color,
dashType: line.dashType,
width: line.width
}
});
linePath.moveTo(lineBox.x1, lineBox.y1).lineTo(lineBox.x2, lineBox.y2);
elements.append(linePath);
}
return elements;
},
renderTicks: function () {
var that = this;
var ticks = new Group();
var options = that.options;
var lineBox = that.lineBox();
var mirror = options.labels.mirror;
var majorUnit = options.majorTicks.visible ? options.majorUnit : 0;
var tickLineOptions = {
_alignLines: options._alignLines,
vertical: options.vertical
};
function render(tickPositions, tickOptions) {
var i, count = tickPositions.length;
if (tickOptions.visible) {
for (i = tickOptions.skip; i < count; i += tickOptions.step) {
if (i % tickOptions.skipUnit === 0) {
continue;
}
tickLineOptions.tickX = mirror ? lineBox.x2 : lineBox.x2 - tickOptions.size;
tickLineOptions.tickY = mirror ? lineBox.y1 - tickOptions.size : lineBox.y1;
tickLineOptions.position = tickPositions[i];
ticks.append(that.renderAxisTick(tickLineOptions, tickOptions));
}
}
}
render(that.getMajorTickPositions(), options.majorTicks);
render(that.getMinorTickPositions(), deepExtend({}, { skipUnit: majorUnit / options.minorUnit }, options.minorTicks));
return ticks;
},
renderAxisTick: function (options, tickOptions) {
var tickX = options.tickX;
var tickY = options.tickY;
var position = options.position;
var start, end, tickPath;
if (options.vertical) {
start = new Point(tickX, position);
end = new Point(tickX + tickOptions.size, position);
} else {
start = new Point(position, tickY);
end = new Point(position, tickY + tickOptions.size);
}
tickPath = new Path({
stroke: {
color: tickOptions.color,
width: tickOptions.width
}
}).moveTo(start).lineTo(end);
return tickPath;
}
});
var LinearPointer = Pointer.extend({
init: function (scale, options) {
var pointer = this;
Pointer.fn.init.call(pointer, scale, options);
pointer.options = deepExtend({ track: { visible: defined(options.track) } }, pointer.options);
},
options: {
shape: BAR_POINTER,
track: { border: { width: 1 } },
color: BLACK,
border: { width: 1 },
opacity: 1,
margin: getSpacing(3),
animation: { type: BAR_POINTER },
visible: true
},
reflow: function () {
var pointer = this;
var options = pointer.options;
var scale = pointer.scale;
var scaleLine = scale.lineBox();
var trackSize = options.track.size || options.size;
var pointerHalfSize = options.size / 2;
var mirror = scale.options.mirror;
var margin = getSpacing(options.margin);
var vertical = scale.options.vertical;
var space = vertical ? margin[mirror ? 'left' : 'right'] : margin[mirror ? 'bottom' : 'top'];
var pointerBox, pointerRangeBox, trackBox;
space = mirror ? -space : space;
if (vertical) {
trackBox = new Box2D(scaleLine.x1 + space, scaleLine.y1, scaleLine.x1 + space, scaleLine.y2);
if (mirror) {
trackBox.x1 -= trackSize;
} else {
trackBox.x2 += trackSize;
}
if (options.shape !== BAR_POINTER) {
pointerRangeBox = new Box2D(scaleLine.x2 + space, scaleLine.y1 - pointerHalfSize, scaleLine.x2 + space, scaleLine.y2 + pointerHalfSize);
pointerBox = pointerRangeBox;
}
} else {
trackBox = new Box2D(scaleLine.x1, scaleLine.y1 - space, scaleLine.x2, scaleLine.y1 - space);
if (mirror) {
trackBox.y2 += trackSize;
} else {
trackBox.y1 -= trackSize;
}
if (options.shape !== BAR_POINTER) {
pointerRangeBox = new Box2D(scaleLine.x1 - pointerHalfSize, scaleLine.y1 - space, scaleLine.x2 + pointerHalfSize, scaleLine.y1 - space);
pointerBox = pointerRangeBox;
}
}
pointer.trackBox = trackBox;
pointer.pointerRangeBox = pointerRangeBox;
pointer.box = pointerBox || trackBox.clone().pad(options.border.width);
},
getElementOptions: function () {
var options = this.options;
return {
fill: {
color: options.color,
opacity: options.opacity
},
stroke: defined(options.border) ? {
color: options.border.width ? options.border.color || options.color : '',
width: options.border.width,
dashType: options.border.dashType,
opacity: options.opacity
} : null
};
},
_margin: function () {
var pointer = this;
var options = pointer.options;
var scale = pointer.scale;
var mirror = scale.options.mirror;
var margin = getSpacing(options.margin);
var vertical = scale.options.vertical;
var space = vertical ? margin[mirror ? 'left' : 'right'] : margin[mirror ? 'bottom' : 'top'];
return space;
}
});
var ArrowLinearPointer = LinearPointer.extend({
init: function (scale, options) {
LinearPointer.fn.init.call(this, scale, options);
if (this.options.size === undefined) {
this.options.size = this.scale.options.majorTicks.size * 0.6;
}
},
pointerShape: function () {
var that = this;
var options = that.options;
var scale = that.scale;
var size = options.size;
var vertical = scale.options.vertical;
var halfSize = size / 2;
var sign = scale.options.mirror ? -1 : 1;
var reverse = scale.options.reverse;
var pos, shape;
if (vertical) {
pos = reverse ? 'y2' : 'y1';
shape = [
new Point(0, 0 - halfSize),
new Point(0 - sign * size, 0),
new Point(0, 0 + halfSize)
];
} else {
pos = reverse ? 'x1' : 'x2';
shape = [
new Point(0 - halfSize, 0),
new Point(0, 0 + sign * size),
new Point(0 + halfSize, 0)
];
}
return shape;
},
repaint: function () {
var that = this;
var scale = that.scale;
var options = that.options;
var animation = new ArrowLinearPointerAnimation(that.elements, deepExtend(options.animation, {
vertical: scale.options.vertical,
mirror: scale.options.mirror,
margin: that._margin(options.margin),
from: scale.getSlot(options._oldValue),
to: scale.getSlot(options.value)
}));
if (options.animation.transitions === false) {
animation.options.duration = 0;
}
animation.setup();
animation.play();
},
render: function () {
var that = this;
var options = that.options;
var elements = new Group();
var scale = that.scale;
var elementOptions = that.getElementOptions();
var shape = that.pointerShape(options.value);
options.animation.type = ARROW_POINTER;
elements = new Path({
stroke: elementOptions.stroke,
fill: elementOptions.fill
}).moveTo(shape[0]).lineTo(shape[1]).lineTo(shape[2]).close();
var slot = scale.getSlot(options.value);
elements.transform(geo.transform().translate(slot.x1, slot.y1));
that.elements = elements;
return elements;
}
});
var BarLinearPointer = LinearPointer.extend({
init: function (scale, options) {
LinearPointer.fn.init.call(this, scale, options);
if (this.options.size === undefined) {
this.options.size = this.scale.options.majorTicks.size * 0.3;
}
},
pointerShape: function (value) {
var that = this;
var options = that.options;
var scale = that.scale;
var vertical = scale.options.vertical;
var mirror = scale.options.mirror;
var dir = mirror == vertical ? -1 : 1;
var size = options.size * dir;
var minSlot = scale.getSlot(scale.options.min);
var slot = scale.getSlot(value);
var axis = vertical ? Y : X;
var sizeAxis = vertical ? X : Y;
var margin = that._margin() * dir;
var p1 = new Point();
p1[axis] = minSlot[axis + '1'];
p1[sizeAxis] = minSlot[sizeAxis + '1'];
var p2 = new Point();
p2[axis] = slot[axis + '1'];
p2[sizeAxis] = slot[sizeAxis + '1'];
if (vertical) {
p1.translate(margin, 0);
p2.translate(margin, 0);
} else {
p1.translate(0, margin);
p2.translate(0, margin);
}
var p3 = p2.clone();
var p4 = p1.clone();
if (vertical) {
p3.translate(size, 0);
p4.translate(size, 0);
} else {
p3.translate(0, size);
p4.translate(0, size);
}
return [
p1,
p2,
p3,
p4
];
},
repaint: function () {
var that = this;
var scale = that.scale;
var options = that.options;
var shape = that.pointerShape(options.value);
var pointerPath = that.elements.children[0];
var oldShape = that.pointerShape(options._oldValue);
pointerPath.moveTo(shape[0]).lineTo(shape[1]).lineTo(shape[2]).lineTo(shape[3]).close();
var animation = new BarLinearPointerAnimation(pointerPath, deepExtend(options.animation, {
reverse: scale.options.reverse,
vertical: scale.options.vertical,
oldPoints: [
oldShape[1],
oldShape[2]
],
newPoints: [
shape[1],
shape[2]
]
}));
if (options.animation.transitions === false) {
animation.options.duration = 0;
}
animation.setup();
animation.play();
},
render: function () {
var that = this;
var group = new Group();
var elementOptions = that.getElementOptions();
var pointer = new Path({
stroke: elementOptions.stroke,
fill: elementOptions.fill
});
group.append(pointer);
that.elements = group;
return group;
}
});
var RadialPointerAnimation = draw.Animation.extend({
init: function (element, options) {
draw.Animation.fn.init.call(this, element, options);
options = this.options;
options.duration = math.max(math.abs(options.newAngle - options.oldAngle) / options.duration * 1000, 1);
},
options: {
easing: LINEAR,
duration: ANGULAR_SPEED
},
step: function (pos) {
var anim = this;
var options = anim.options;
var angle = interpolateValue(options.oldAngle, options.newAngle, pos);
anim.element.transform(geo.transform().rotate(angle, options.center));
}
});
draw.AnimationFactory.current.register(RADIAL_POINTER, RadialPointerAnimation);
var ArrowLinearPointerAnimation = draw.Animation.extend({
options: {
easing: LINEAR,
duration: LINEAR_SPEED
},
setup: function () {
var options = this.options;
var margin = options.margin;
var from = options.from;
var to = options.to;
var axis = options.vertical ? 'x1' : 'y1';
if (options.mirror == options.vertical) {
from[axis] -= margin;
to[axis] -= margin;
} else {
from[axis] += margin;
to[axis] += margin;
}
var fromScale = this.fromScale = new Point(from.x1, from.y1);
var toScale = this.toScale = new Point(to.x1, to.y1);
if (options.duration !== 0) {
options.duration = math.max(fromScale.distanceTo(toScale) / options.duration * 1000, 1);
}
},
step: function (pos) {
var translateX = interpolateValue(this.fromScale.x, this.toScale.x, pos);
var translateY = interpolateValue(this.fromScale.y, this.toScale.y, pos);
this.element.transform(geo.transform().translate(translateX, translateY));
}
});
draw.AnimationFactory.current.register(ARROW_POINTER, ArrowLinearPointerAnimation);
var BarLinearPointerAnimation = draw.Animation.extend({
options: {
easing: LINEAR,
speed: LINEAR_SPEED
},
setup: function () {
var options = this.options;
var newPoints = options.newPoints;
var oldPoints = options.oldPoints;
var axis = this.axis = options.vertical ? Y : X;
var to = this.to = newPoints[0][axis];
var from = this.from = oldPoints[0][axis];
if (options.duration !== 0) {
options.duration = math.max(math.abs(to - from) / options.speed * 1000, 1);
}
this._set(from);
},
step: function (pos) {
var value = interpolateValue(this.from, this.to, pos);
this._set(value);
},
_set: function (value) {
var setter = 'set' + this.axis.toUpperCase();
var points = this.options.newPoints;
points[0][setter](value);
points[1][setter](value);
}
});
draw.AnimationFactory.current.register(BAR_POINTER, BarLinearPointerAnimation);
function _buildLabel(label, options) {
var labelBox = label.box;
var textBox = label.children[0].box;
var border = options.border || {};
var background = options.background || '';
var elements = new Group();
var styleBox, styleGeometry, wrapper;
wrapper = Path.fromRect(new Rect([
labelBox.x1,
labelBox.y1
], [
labelBox.width(),
labelBox.height()
]), { stroke: {} });
var text = new Text(label.text, new Point(textBox.x1, textBox.y1), {
font: options.font,
fill: { color: options.color }
});
styleGeometry = _pad(text.bbox().clone(), options.padding);
styleBox = Path.fromRect(styleGeometry, {
stroke: {
color: border.width ? border.color : '',
width: border.width,
dashType: border.dashType,
lineJoin: 'round',
lineCap: 'round'
},
fill: { color: background }
});
elements.append(wrapper);
elements.append(styleBox);
elements.append(text);
return elements;
}
function getRange(range, min, max) {
var from = defined(range.from) ? range.from : MIN_VALUE;
var to = defined(range.to) ? range.to : MAX_VALUE;
range.from = math.max(math.min(to, from), min);
range.to = math.min(math.max(to, from), max);
return range;
}
function _pad(bbox, value) {
var origin = bbox.getOrigin();
var size = bbox.getSize();
var spacing = getSpacing(value);
bbox.setOrigin([
origin.x - spacing.left,
origin.y - spacing.top
]);
bbox.setSize([
size.width + (spacing.left + spacing.right),
size.height + (spacing.top + spacing.bottom)
]);
return bbox;
}
function _unpad(bbox, value) {
var spacing = getSpacing(value);
spacing.left = -spacing.left;
spacing.top = -spacing.top;
spacing.right = -spacing.right;
spacing.bottom = -spacing.bottom;
return _pad(bbox, spacing);
}
dataviz.ui.plugin(RadialGauge);
dataviz.ui.plugin(LinearGauge);
dataviz.ExportMixin.extend(Gauge.fn);
deepExtend(dataviz, {
Gauge: Gauge,
RadialPointer: RadialPointer,
LinearPointer: LinearPointer,
ArrowLinearPointer: ArrowLinearPointer,
BarLinearPointer: BarLinearPointer,
LinearScale: LinearScale,
RadialScale: RadialScale,
LinearGauge: LinearGauge,
RadialGauge: RadialGauge
});
}(window.kendo.jQuery));
return window.kendo;
}, typeof define == 'function' && define.amd ? define : function (a1, a2, a3) {
(a3 || a2)();
}));
(function (f, define) {
define('kendo.dataviz.barcode', [
'kendo.dataviz.core',
'kendo.drawing'
], f);
}(function () {
var __meta__ = {
id: 'dataviz.barcode',
name: 'Barcode',
category: 'dataviz',
description: 'Barcode widget',
depends: ['dataviz.core']
};
(function ($, undefined) {
var kendo = window.kendo, Widget = kendo.ui.Widget, extend = $.extend, deepExtend = kendo.deepExtend, inArray = $.inArray, isPlainObject = $.isPlainObject, draw = kendo.drawing, geom = kendo.geometry, util = kendo.util, defined = util.defined, dataviz = kendo.dataviz, Box2D = dataviz.Box2D, TextBox = dataviz.TextBox, DEFAULT_WIDTH = 300, DEFAULT_HEIGHT = 100, DEFAULT_QUIETZONE_LENGTH = 10, numberRegex = /^\d+$/, alphanumericRegex = /^[a-z0-9]+$/i, InvalidCharacterErrorTemplate = 'Character \'{0}\' is not valid for symbology {1}';
function getNext(value, index, count) {
return value.substring(index, index + count);
}
var Encoding = kendo.Class.extend({
init: function (options) {
this.setOptions(options);
},
setOptions: function (options) {
var that = this;
that.options = extend({}, that.options, options);
that.quietZoneLength = that.options.addQuietZone ? 2 * that.options.quietZoneLength : 0;
},
encode: function (value, width, height) {
var that = this;
if (defined(value)) {
value += '';
}
that.initValue(value, width, height);
if (that.options.addQuietZone) {
that.addQuietZone();
}
that.addData();
if (that.options.addQuietZone) {
that.addQuietZone();
}
return {
baseUnit: that.baseUnit,
pattern: that.pattern
};
},
options: {
quietZoneLength: DEFAULT_QUIETZONE_LENGTH,
addQuietZone: true,
addCheckSum: true
},
initValue: function () {
},
addQuietZone: function () {
this.pattern.push(this.options.quietZoneLength || DEFAULT_QUIETZONE_LENGTH);
},
addData: function () {
},
invalidCharacterError: function (character) {
throw new Error(kendo.format(InvalidCharacterErrorTemplate, character, this.name));
}
});
var encodings = {};
var code39Base = Encoding.extend({
minBaseUnitLength: 0.7,
addData: function () {
var that = this, value = that.value;
that.addStart();
for (var idx = 0; idx < value.length; idx++) {
that.addCharacter(value.charAt(idx));
}
if (that.options.addCheckSum) {
that.pushCheckSum();
}
that.addStop();
that.prepareValues();
},
addCharacter: function (character) {
var that = this, charData = that.characterMap[character];
if (!charData) {
that.invalidCharacterError(character);
}
that.addBase(charData);
},
addBase: function () {
}
});
var code39ExtendedBase = {
addCharacter: function (character) {
var that = this;
if (that.characterMap[character]) {
that.addBase(that.characterMap[character]);
} else if (character.charCodeAt(0) > 127) {
that.invalidCharacterError(character);
} else {
that.addExtended(character.charCodeAt(0));
}
},
addExtended: function (code) {
var that = this, patterns;
for (var i = 0; i < that.extendedMappings.length; i++) {
if (patterns = that.extendedMappings[i].call(that, code)) {
for (var j = 0; j < patterns.length; j++) {
that.addBase(patterns[j]);
}
that.dataLength += patterns.length - 1;
return;
}
}
},
extendedMappings: [
function (code) {
if (97 <= code && code <= 122) {
var that = this;
return [
that.characterMap[that.shiftCharacters[0]],
that.characterMap[String.fromCharCode(code - 32)]
];
}
},
function (code) {
if (33 <= code && code <= 58) {
var that = this;
return [
that.characterMap[that.shiftCharacters[1]],
that.characterMap[String.fromCharCode(code + 32)]
];
}
},
function (code) {
if (1 <= code && code <= 26) {
var that = this;
return [
that.characterMap[that.shiftCharacters[2]],
that.characterMap[String.fromCharCode(code + 64)]
];
}
},
function (code) {
var that = this, result, dataCharacter;
if (!that.specialAsciiCodes[code]) {
dataCharacter = Math.floor(code / 32) * 6 + (code - 27) % 32 + 64;
result = [
that.characterMap[that.shiftCharacters[3]],
that.characterMap[String.fromCharCode(dataCharacter)]
];
} else {
result = [];
for (var i = 0; i < that.specialAsciiCodes[code].length; i++) {
result.push(that.characterMap[that.shiftCharacters[3]]);
result.push(that.characterMap[that.specialAsciiCodes[code][i]]);
}
}
return result;
}
],
specialAsciiCodes: {
'0': ['U'],
'64': ['V'],
'96': ['W'],
'127': [
'T',
'X',
'Y',
'Z'
]
},
shiftValuesAsciiCodes: {
'39': 36,
'40': 47,
'41': 43,
'42': 37
},
characterMap: {
'+': false,
'/': false,
'$': false,
'%': false
},
shiftCharacters: [
'SHIFT0',
'SHIFT1',
'SHIFT2',
'SHIFT3'
]
};
encodings.code39 = code39Base.extend({
name: 'Code 39',
checkSumMod: 43,
minRatio: 2.5,
maxRatio: 3,
gapWidth: 1,
splitCharacter: '|',
initValue: function (value, width, height) {
var that = this;
that.width = width;
that.height = height;
that.value = value;
that.dataLength = value.length;
that.pattern = [];
that.patternString = '';
},
prepareValues: function () {
var that = this, baseUnit, minBaseUnit = that.minBaseUnitLength, ratio = that.maxRatio, minRatio = that.minRatio, minHeight = Math.max(0.15 * that.width, 24);
if (that.height < minHeight) {
throw new Error('Insufficient Height. The minimum height for value: ' + that.value + ' is: ' + minHeight);
}
while ((baseUnit = that.getBaseUnit(ratio)) < minBaseUnit && ratio > minRatio) {
ratio = parseFloat((ratio - 0.1).toFixed(1));
}
if (baseUnit < minBaseUnit) {
var minWidth = Math.ceil(that.getBaseWidth(minRatio) * minBaseUnit);
throw new Error('Insufficient width. The minimum width for value: ' + that.value + ' is: ' + minWidth);
}
that.ratio = ratio;
that.baseUnit = baseUnit;
that.patternString = that.patternString.substring(0, that.patternString.length - 1);
that.pattern = that.pattern.concat(that.patternString.replace(/ratio/g, ratio).split(that.splitCharacter));
},
getBaseUnit: function (ratio) {
return this.width / this.getBaseWidth(ratio);
},
getBaseWidth: function (ratio) {
var that = this, characterLength = 3 * (ratio + 2);
return that.quietZoneLength + characterLength * (that.dataLength + 2) + that.gapWidth * (that.dataLength + 1);
},
addStart: function () {
var that = this;
that.addPattern(that.characterMap.START.pattern);
that.addCharacterGap();
},
addBase: function (character) {
this.addPattern(character.pattern);
this.addCharacterGap();
},
addStop: function () {
this.addPattern(this.characterMap.START.pattern);
},
addPattern: function (pattern) {
for (var i = 0; i < pattern.length; i++) {
this.patternString += this.patternMappings[pattern.charAt(i)];
}
},
addCharacterGap: function () {
var that = this;
that.patternString += that.gapWidth + that.splitCharacter;
},
patternMappings: {
'b': '1|',
'w': '1|',
'B': 'ratio|',
'W': 'ratio|'
},
characterMap: {
'0': {
'pattern': 'bwbWBwBwb',
'value': 0
},
'1': {
'pattern': 'BwbWbwbwB',
'value': 1
},
'2': {
'pattern': 'bwBWbwbwB',
'value': 2
},
'3': {
'pattern': 'BwBWbwbwb',
'value': 3
},
'4': {
'pattern': 'bwbWBwbwB',
'value': 4
},
'5': {
'pattern': 'BwbWBwbwb',
'value': 5
},
'6': {
'pattern': 'bwBWBwbwb',
'value': 6
},
'7': {
'pattern': 'bwbWbwBwB',
'value': 7
},
'8': {
'pattern': 'BwbWbwBwb',
'value': 8
},
'9': {
'pattern': 'bwBWbwBwb',
'value': 9
},
'A': {
'pattern': 'BwbwbWbwB',
'value': 10
},
'B': {
'pattern': 'bwBwbWbwB',
'value': 11
},
'C': {
'pattern': 'BwBwbWbwb',
'value': 12
},
'D': {
'pattern': 'bwbwBWbwB',
'value': 13
},
'E': {
'pattern': 'BwbwBWbwb',
'value': 14
},
'F': {
'pattern': 'bwBwBWbwb',
'value': 15
},
'G': {
'pattern': 'bwbwbWBwB',
'value': 16
},
'H': {
'pattern': 'BwbwbWBwb',
'value': 17
},
'I': {
'pattern': 'bwBwbWBwb',
'value': 18
},
'J': {
'pattern': 'bwbwBWBwb',
'value': 19
},
'K': {
'pattern': 'BwbwbwbWB',
'value': 20
},
'L': {
'pattern': 'bwBwbwbWB',
'value': 21
},
'M': {
'pattern': 'BwBwbwbWb',
'value': 22
},
'N': {
'pattern': 'bwbwBwbWB',
'value': 23
},
'O': {
'pattern': 'BwbwBwbWb',
'value': 24
},
'P': {
'pattern': 'bwBwBwbWb',
'value': 25
},
'Q': {
'pattern': 'bwbwbwBWB',
'value': 26
},
'R': {
'pattern': 'BwbwbwBWb',
'value': 27
},
'S': {
'pattern': 'bwBwbwBWb',
'value': 28
},
'T': {
'pattern': 'bwbwBwBWb',
'value': 29
},
'U': {
'pattern': 'BWbwbwbwB',
'value': 30
},
'V': {
'pattern': 'bWBwbwbwB',
'value': 31
},
'W': {
'pattern': 'BWBwbwbwb',
'value': 32
},
'X': {
'pattern': 'bWbwBwbwB',
'value': 33
},
'Y': {
'pattern': 'BWbwBwbwb',
'value': 34
},
'Z': {
'pattern': 'bWBwBwbwb',
'value': 35
},
'-': {
'pattern': 'bWbwbwBwB',
'value': 36
},
'.': {
'pattern': 'BWbwbwBwb',
'value': 37
},
' ': {
'pattern': 'bWBwbwBwb',
'value': 38
},
'$': {
'pattern': 'bWbWbWbwb',
'value': 39
},
'/': {
'pattern': 'bWbWbwbWb',
'value': 40
},
'+': {
'pattern': 'bWbwbWbWb',
'value': 41
},
'%': {
'pattern': 'bwbWbWbWb',
'value': 42
},
START: { pattern: 'bWbwBwBwb' }
},
options: { addCheckSum: false }
});
encodings.code39extended = encodings.code39.extend(deepExtend({}, code39ExtendedBase, {
name: 'Code 39 extended',
characterMap: {
SHIFT0: {
'pattern': 'bWbwbWbWb',
'value': 41
},
SHIFT1: {
'pattern': 'bWbWbwbWb',
'value': 40
},
SHIFT2: {
'pattern': 'bWbWbWbwb',
'value': 39
},
SHIFT3: {
'pattern': 'bwbWbWbWb',
'value': 42
}
}
}));
encodings.code93 = code39Base.extend({
name: 'Code 93',
cCheckSumTotal: 20,
kCheckSumTotal: 15,
checkSumMod: 47,
initValue: function (value, width, height) {
var that = this;
that.value = value;
that.width = width;
that.height = height;
that.pattern = [];
that.values = [];
that.dataLength = value.length;
},
prepareValues: function () {
var that = this, minHeight = Math.max(0.15 * that.width, 24);
if (that.height < minHeight) {
throw new Error('Insufficient Height');
}
that.setBaseUnit();
if (that.baseUnit < that.minBaseUnitLength) {
throw new Error('Insufficient Width');
}
},
setBaseUnit: function () {
var that = this, checkSumLength = 2;
that.baseUnit = that.width / (9 * (that.dataLength + 2 + checkSumLength) + that.quietZoneLength + 1);
},
addStart: function () {
var pattern = this.characterMap.START.pattern;
this.addPattern(pattern);
},
addStop: function () {
var that = this;
that.addStart();
that.pattern.push(that.characterMap.TERMINATION_BAR);
},
addBase: function (charData) {
this.addPattern(charData.pattern);
this.values.push(charData.value);
},
pushCheckSum: function () {
var that = this, checkValues = that._getCheckValues(), charData;
that.checksum = checkValues.join('');
for (var i = 0; i < checkValues.length; i++) {
charData = that.characterMap[that._findCharacterByValue(checkValues[i])];
that.addPattern(charData.pattern);
}
},
_getCheckValues: function () {
var that = this, values = that.values, length = values.length, wightedSum = 0, cValue, kValue, idx;
for (idx = length - 1; idx >= 0; idx--) {
wightedSum += that.weightedValue(values[idx], length - idx, that.cCheckSumTotal);
}
cValue = wightedSum % that.checkSumMod;
wightedSum = that.weightedValue(cValue, 1, that.kCheckSumTotal);
for (idx = length - 1; idx >= 0; idx--) {
wightedSum += that.weightedValue(values[idx], length - idx + 1, that.kCheckSumTotal);
}
kValue = wightedSum % that.checkSumMod;
return [
cValue,
kValue
];
},
_findCharacterByValue: function (value) {
for (var character in this.characterMap) {
if (this.characterMap[character].value === value) {
return character;
}
}
},
weightedValue: function (value, index, total) {
return (index % total || total) * value;
},
addPattern: function (pattern) {
var value;
for (var i = 0; i < pattern.length; i++) {
value = parseInt(pattern.charAt(i), 10);
this.pattern.push(value);
}
},
characterMap: {
'0': {
'pattern': '131112',
'value': 0
},
'1': {
'pattern': '111213',
'value': 1
},
'2': {
'pattern': '111312',
'value': 2
},
'3': {
'pattern': '111411',
'value': 3
},
'4': {
'pattern': '121113',
'value': 4
},
'5': {
'pattern': '121212',
'value': 5
},
'6': {
'pattern': '121311',
'value': 6
},
'7': {
'pattern': '111114',
'value': 7
},
'8': {
'pattern': '131211',
'value': 8
},
'9': {
'pattern': '141111',
'value': 9
},
'A': {
'pattern': '211113',
'value': 10
},
'B': {
'pattern': '211212',
'value': 11
},
'C': {
'pattern': '211311',
'value': 12
},
'D': {
'pattern': '221112',
'value': 13
},
'E': {
'pattern': '221211',
'value': 14
},
'F': {
'pattern': '231111',
'value': 15
},
'G': {
'pattern': '112113',
'value': 16
},
'H': {
'pattern': '112212',
'value': 17
},
'I': {
'pattern': '112311',
'value': 18
},
'J': {
'pattern': '122112',
'value': 19
},
'K': {
'pattern': '132111',
'value': 20
},
'L': {
'pattern': '111123',
'value': 21
},
'M': {
'pattern': '111222',
'value': 22
},
'N': {
'pattern': '111321',
'value': 23
},
'O': {
'pattern': '121122',
'value': 24
},
'P': {
'pattern': '131121',
'value': 25
},
'Q': {
'pattern': '212112',
'value': 26
},
'R': {
'pattern': '212211',
'value': 27
},
'S': {
'pattern': '211122',
'value': 28
},
'T': {
'pattern': '211221',
'value': 29
},
'U': {
'pattern': '221121',
'value': 30
},
'V': {
'pattern': '222111',
'value': 31
},
'W': {
'pattern': '112122',
'value': 32
},
'X': {
'pattern': '112221',
'value': 33
},
'Y': {
'pattern': '122121',
'value': 34
},
'Z': {
'pattern': '123111',
'value': 35
},
'-': {
'pattern': '121131',
'value': 36
},
'.': {
'pattern': '311112',
'value': 37
},
' ': {
'pattern': '311211',
'value': 38
},
'$': {
'pattern': '321111',
'value': 39
},
'/': {
'pattern': '112131',
'value': 40
},
'+': {
'pattern': '113121',
'value': 41
},
'%': {
'pattern': '211131',
'value': 42
},
SHIFT0: {
'pattern': '122211',
'value': 46
},
SHIFT1: {
'pattern': '311121',
'value': 45
},
SHIFT2: {
'pattern': '121221',
'value': 43
},
SHIFT3: {
'pattern': '312111',
'value': 44
},
START: { 'pattern': '111141' },
TERMINATION_BAR: '1'
}
});
encodings.code93extended = encodings.code93.extend(deepExtend({}, code39ExtendedBase, {
name: 'Code 93 extended',
pushCheckSum: function () {
var that = this, checkValues = that._getCheckValues(), value;
that.checksum = checkValues.join('');
for (var i = 0; i < checkValues.length; i++) {
value = checkValues[i];
if (that.shiftValuesAsciiCodes[value]) {
that.addExtended(that.shiftValuesAsciiCodes[value]);
} else {
that.addPattern(that.characterMap[that._findCharacterByValue(value)].pattern);
}
}
}
}));
var state128 = kendo.Class.extend({
init: function (encoding) {
this.encoding = encoding;
},
addStart: function () {
},
is: function () {
},
move: function () {
},
pushState: function () {
}
});
var state128AB = state128.extend({
FNC4: 'FNC4',
init: function (encoding, states) {
var that = this;
that.encoding = encoding;
that.states = states;
that._initMoves(states);
},
addStart: function () {
this.encoding.addPattern(this.START);
},
is: function (value, index) {
var code = value.charCodeAt(index);
return this.isCode(code);
},
move: function (encodingState) {
var that = this, idx = 0;
while (!that._moves[idx].call(that, encodingState) && idx < that._moves.length) {
idx++;
}
},
pushState: function (encodingState) {
var that = this, states = that.states, value = encodingState.value, maxLength = value.length, code;
if (inArray('C', states) >= 0) {
var numberMatch = value.substr(encodingState.index).match(/\d{4,}/g);
if (numberMatch) {
maxLength = value.indexOf(numberMatch[0], encodingState.index);
}
}
while ((code = encodingState.value.charCodeAt(encodingState.index)) >= 0 && that.isCode(code) && encodingState.index < maxLength) {
that.encoding.addPattern(that.getValue(code));
encodingState.index++;
}
},
_initMoves: function (states) {
var that = this;
that._moves = [];
if (inArray(that.FNC4, states) >= 0) {
that._moves.push(that._moveFNC);
}
if (inArray(that.shiftKey, states) >= 0) {
that._moves.push(that._shiftState);
}
that._moves.push(that._moveState);
},
_moveFNC: function (encodingState) {
if (encodingState.fnc) {
encodingState.fnc = false;
return encodingState.previousState == this.key;
}
},
_shiftState: function (encodingState) {
var that = this;
if (encodingState.previousState == that.shiftKey && (encodingState.index + 1 >= encodingState.value.length || that.encoding[that.shiftKey].is(encodingState.value, encodingState.index + 1))) {
that.encoding.addPattern(that.SHIFT);
encodingState.shifted = true;
return true;
}
},
_moveState: function () {
this.encoding.addPattern(this.MOVE);
return true;
},
SHIFT: 98
});
var states128 = {};
states128.A = state128AB.extend({
key: 'A',
shiftKey: 'B',
isCode: function (code) {
return 0 <= code && code < 96;
},
getValue: function (code) {
if (code < 32) {
return code + 64;
}
return code - 32;
},
MOVE: 101,
START: 103
});
states128.B = state128AB.extend({
key: 'B',
shiftKey: 'A',
isCode: function (code) {
return 32 <= code && code < 128;
},
getValue: function (code) {
return code - 32;
},
MOVE: 100,
START: 104
});
states128.C = state128.extend({
key: 'C',
addStart: function () {
this.encoding.addPattern(this.START);
},
is: function (value, index) {
var next4 = getNext(value, index, 4);
return (index + 4 <= value.length || value.length == 2) && numberRegex.test(next4);
},
move: function () {
this.encoding.addPattern(this.MOVE);
},
pushState: function (encodingState) {
var code;
while ((code = getNext(encodingState.value, encodingState.index, 2)) && numberRegex.test(code) && code.length == 2) {
this.encoding.addPattern(parseInt(code, 10));
encodingState.index += 2;
}
},
getValue: function (code) {
return code;
},
MOVE: 99,
START: 105
});
states128.FNC4 = state128.extend({
key: 'FNC4',
dependentStates: [
'A',
'B'
],
init: function (encoding, states) {
this.encoding = encoding;
this._initSubStates(states);
},
addStart: function (encodingState) {
var code = encodingState.value.charCodeAt(0) - 128, subState = this._getSubState(code);
this.encoding[subState].addStart();
},
is: function (value, index) {
var code = value.charCodeAt(index);
return this.isCode(code);
},
isCode: function (code) {
return 128 <= code && code < 256;
},
pushState: function (encodingState) {
var that = this, subState = that._initSubState(encodingState), encoding = that.encoding, length = subState.value.length;
encodingState.index += length;
if (length < 3) {
var code;
for (; subState.index < length; subState.index++) {
code = subState.value.charCodeAt(subState.index);
subState.state = that._getSubState(code);
if (subState.previousState != subState.state) {
subState.previousState = subState.state;
encoding[subState.state].move(subState);
}
encoding.addPattern(encoding[subState.state].MOVE);
encoding.addPattern(encoding[subState.state].getValue(code));
}
} else {
if (subState.state != subState.previousState) {
encoding[subState.state].move(subState);
}
that._pushStart(subState);
encoding.pushData(subState, that.subStates);
if (encodingState.index < encodingState.value.length) {
that._pushStart(subState);
}
}
encodingState.fnc = true;
encodingState.state = subState.state;
},
_pushStart: function (subState) {
var that = this;
that.encoding.addPattern(that.encoding[subState.state].MOVE);
that.encoding.addPattern(that.encoding[subState.state].MOVE);
},
_initSubState: function (encodingState) {
var that = this, subState = {
value: that._getAll(encodingState.value, encodingState.index),
index: 0
};
subState.state = that._getSubState(subState.value.charCodeAt(0));
subState.previousState = encodingState.previousState == that.key ? subState.state : encodingState.previousState;
return subState;
},
_initSubStates: function (states) {
var that = this;
that.subStates = [];
for (var i = 0; i < states.length; i++) {
if (inArray(states[i], that.dependentStates) >= 0) {
that.subStates.push(states[i]);
}
}
},
_getSubState: function (code) {
var that = this;
for (var i = 0; i < that.subStates.length; i++) {
if (that.encoding[that.subStates[i]].isCode(code)) {
return that.subStates[i];
}
}
},
_getAll: function (value, index) {
var code, result = '';
while ((code = value.charCodeAt(index++)) && this.isCode(code)) {
result += String.fromCharCode(code - 128);
}
return result;
}
});
states128.FNC1 = state128.extend({
key: 'FNC1',
startState: 'C',
dependentStates: [
'C',
'B'
],
startAI: '(',
endAI: ')',
init: function (encoding, states) {
this.encoding = encoding;
this.states = states;
},
addStart: function () {
this.encoding[this.startState].addStart();
},
is: function () {
return inArray(this.key, this.states) >= 0;
},
pushState: function (encodingState) {
var that = this, encoding = that.encoding, value = encodingState.value.replace(/\s/g, ''), regexSeparators = new RegExp('[' + that.startAI + that.endAI + ']', 'g'), index = encodingState.index, subState = { state: that.startState }, current, nextStart, separatorLength;
encoding.addPattern(that.START);
while (true) {
subState.index = 0;
separatorLength = value.charAt(index) === that.startAI ? 2 : 0;
current = separatorLength > 0 ? that.getBySeparator(value, index) : that.getByLength(value, index);
if (current.ai.length) {
nextStart = index + separatorLength + current.id.length + current.ai.length;
} else {
nextStart = value.indexOf(that.startAI, index + 1);
if (nextStart < 0) {
if (index + current.ai.max + current.id.length + separatorLength < value.length) {
throw new Error('Separators are required after variable length identifiers');
}
nextStart = value.length;
}
}
subState.value = value.substring(index, nextStart).replace(regexSeparators, '');
that.validate(current, subState.value);
encoding.pushData(subState, that.dependentStates);
if (nextStart >= value.length) {
break;
}
index = nextStart;
if (subState.state != that.startState) {
encoding[that.startState].move(subState);
subState.state = that.startState;
}
if (!current.ai.length) {
encoding.addPattern(that.START);
}
}
encodingState.index = encodingState.value.length;
},
validate: function (current, value) {
var code = value.substr(current.id.length), ai = current.ai;
if (!ai.type && !numberRegex.test(code)) {
throw new Error('Application identifier ' + current.id + ' is numeric only but contains non numeric character(s).');
}
if (ai.type == 'alphanumeric' && !alphanumericRegex.test(code)) {
throw new Error('Application identifier ' + current.id + ' is alphanumeric only but contains non alphanumeric character(s).');
}
if (ai.length && ai.length !== code.length) {
throw new Error('Application identifier ' + current.id + ' must be ' + ai.length + ' characters long.');
}
if (ai.min && ai.min > code.length) {
throw new Error('Application identifier ' + current.id + ' must be at least ' + ai.min + ' characters long.');
}
if (ai.max && ai.max < code.length) {
throw new Error('Application identifier ' + current.id + ' must be at most ' + ai.max + ' characters long.');
}
},
getByLength: function (value, index) {
var that = this, id, ai;
for (var i = 2; i <= 4; i++) {
id = getNext(value, index, i);
ai = that.getAI(id) || that.getAI(id.substring(0, id.length - 1));
if (ai) {
return {
id: id,
ai: ai
};
}
}
that.unsupportedAIError(id);
},
unsupportedAIError: function (id) {
throw new Error(kendo.format('\'{0}\' is not a supported Application Identifier'), id);
},
getBySeparator: function (value, index) {
var that = this, start = value.indexOf(that.startAI, index), end = value.indexOf(that.endAI, start), id = value.substring(start + 1, end), ai = that.getAI(id) || that.getAI(id.substr(id.length - 1));
if (!ai) {
that.unsupportedAIError(id);
}
return {
ai: ai,
id: id
};
},
getAI: function (id) {
var ai = this.applicationIdentifiers, multiKey = ai.multiKey;
if (ai[id]) {
return ai[id];
}
for (var i = 0; i < multiKey.length; i++) {
if (multiKey[i].ids && inArray(id, multiKey[i].ids) >= 0) {
return multiKey[i].type;
} else if (multiKey[i].ranges) {
var ranges = multiKey[i].ranges;
for (var j = 0; j < ranges.length; j++) {
if (ranges[j][0] <= id && id <= ranges[j][1]) {
return multiKey[i].type;
}
}
}
}
},
applicationIdentifiers: {
'22': {
max: 29,
type: 'alphanumeric'
},
'402': { length: 17 },
'7004': {
max: 4,
type: 'alphanumeric'
},
'242': {
max: 6,
type: 'alphanumeric'
},
'8020': {
max: 25,
type: 'alphanumeric'
},
'703': {
min: 3,
max: 30,
type: 'alphanumeric'
},
'8008': {
min: 8,
max: 12,
type: 'alphanumeric'
},
'253': {
min: 13,
max: 17,
type: 'alphanumeric'
},
'8003': {
min: 14,
max: 30,
type: 'alphanumeric'
},
multiKey: [
{
ids: [
'15',
'17',
'8005',
'8100'
],
ranges: [
[
11,
13
],
[
310,
316
],
[
320,
336
],
[
340,
369
]
],
type: { length: 6 }
},
{
ids: [
'240',
'241',
'250',
'251',
'400',
'401',
'403',
'7002',
'8004',
'8007',
'8110'
],
ranges: [[90 - 99]],
type: {
max: 30,
type: 'alphanumeric'
}
},
{
ids: ['7001'],
ranges: [[
410,
414
]],
type: { length: 13 }
},
{
ids: [
'10',
'21',
'254',
'420',
'8002'
],
type: {
max: 20,
type: 'alphanumeric'
}
},
{
ids: [
'00',
'8006',
'8017',
'8018'
],
type: { length: 18 }
},
{
ids: [
'01',
'02',
'8001'
],
type: { length: 14 }
},
{
ids: ['422'],
ranges: [[
424,
426
]],
type: { length: 3 }
},
{
ids: [
'20',
'8102'
],
type: { length: 2 }
},
{
ids: [
'30',
'37'
],
type: {
max: 8,
type: 'alphanumeric'
}
},
{
ids: [
'390',
'392'
],
type: {
max: 15,
type: 'alphanumeric'
}
},
{
ids: [
'421',
'423'
],
type: {
min: 3,
max: 15,
type: 'alphanumeric'
}
},
{
ids: [
'391',
'393'
],
type: {
min: 3,
max: 18,
type: 'alphanumeric'
}
},
{
ids: [
'7003',
'8101'
],
type: { length: 10 }
}
]
},
START: 102
});
var code128Base = Encoding.extend({
init: function (options) {
Encoding.fn.init.call(this, options);
this._initStates();
},
_initStates: function () {
var that = this;
for (var i = 0; i < that.states.length; i++) {
that[that.states[i]] = new states128[that.states[i]](that, that.states);
}
},
initValue: function (value, width, height) {
var that = this;
that.pattern = [];
that.value = value;
that.width = width;
that.height = height;
that.checkSum = 0;
that.totalUnits = 0;
that.index = 0;
that.position = 1;
},
addData: function () {
var that = this, encodingState = {
value: that.value,
index: 0,
state: ''
};
if (that.value.length === 0) {
return;
}
encodingState.state = encodingState.previousState = that.getNextState(encodingState, that.states);
that.addStart(encodingState);
that.pushData(encodingState, that.states);
that.addCheckSum();
that.addStop();
that.setBaseUnit();
},
pushData: function (encodingState, states) {
var that = this;
while (true) {
that[encodingState.state].pushState(encodingState);
if (encodingState.index >= encodingState.value.length) {
break;
}
if (!encodingState.shifted) {
encodingState.previousState = encodingState.state;
encodingState.state = that.getNextState(encodingState, states);
that[encodingState.state].move(encodingState);
} else {
var temp = encodingState.state;
encodingState.state = encodingState.previousState;
encodingState.previousState = temp;
encodingState.shifted = false;
}
}
},
addStart: function (encodingState) {
this[encodingState.state].addStart(encodingState);
this.position = 1;
},
addCheckSum: function () {
var that = this;
that.checksum = that.checkSum % 103;
that.addPattern(that.checksum);
},
addStop: function () {
this.addPattern(this.STOP);
},
setBaseUnit: function () {
var that = this;
that.baseUnit = that.width / (that.totalUnits + that.quietZoneLength);
},
addPattern: function (code) {
var that = this, pattern = that.characterMap[code].toString(), value;
for (var i = 0; i < pattern.length; i++) {
value = parseInt(pattern.charAt(i), 10);
that.pattern.push(value);
that.totalUnits += value;
}
that.checkSum += code * that.position++;
},
getNextState: function (encodingState, states) {
for (var i = 0; i < states.length; i++) {
if (this[states[i]].is(encodingState.value, encodingState.index)) {
return states[i];
}
}
this.invalidCharacterError(encodingState.value.charAt(encodingState.index));
},
characterMap: [
212222,
222122,
222221,
121223,
121322,
131222,
122213,
122312,
132212,
221213,
221312,
231212,
112232,
122132,
122231,
113222,
123122,
123221,
223211,
221132,
221231,
213212,
223112,
312131,
311222,
321122,
321221,
312212,
322112,
322211,
212123,
212321,
232121,
111323,
131123,
131321,
112313,
132113,
132311,
211313,
231113,
231311,
112133,
112331,
132131,
113123,
113321,
133121,
313121,
211331,
231131,
213113,
213311,
213131,
311123,
311321,
331121,
312113,
312311,
332111,
314111,
221411,
431111,
111224,
111422,
121124,
121421,
141122,
141221,
112214,
112412,
122114,
122411,
142112,
142211,
241211,
221114,
413111,
241112,
134111,
111242,
121142,
121241,
114212,
124112,
124211,
411212,
421112,
421211,
212141,
214121,
412121,
111143,
111341,
131141,
114113,
114311,
411113,
411311,
113141,
114131,
311141,
411131,
211412,
211214,
211232,
2331112
],
STOP: 106
});
encodings.code128a = code128Base.extend({
name: 'Code 128 A',
states: ['A']
});
encodings.code128b = code128Base.extend({
name: 'Code 128 B',
states: ['B']
});
encodings.code128c = code128Base.extend({
name: 'Code 128 C',
states: ['C']
});
encodings.code128 = code128Base.extend({
name: 'Code 128',
states: [
'C',
'B',
'A',
'FNC4'
]
});
encodings['gs1-128'] = code128Base.extend({
name: 'Code GS1-128',
states: [
'FNC1',
'C',
'B'
]
});
var msiBase = Encoding.extend({
initValue: function (value, width) {
var that = this;
that.pattern = [];
that.value = value;
that.checkSumLength = 0;
that.width = width;
},
setBaseUnit: function () {
var that = this, startStopLength = 7;
that.baseUnit = that.width / (12 * (that.value.length + that.checkSumLength) + that.quietZoneLength + startStopLength);
},
addData: function () {
var that = this, value = that.value;
that.addPattern(that.START);
for (var i = 0; i < value.length; i++) {
that.addCharacter(value.charAt(i));
}
if (that.options.addCheckSum) {
that.addCheckSum();
}
that.addPattern(that.STOP);
that.setBaseUnit();
},
addCharacter: function (character) {
var that = this, pattern = that.characterMap[character];
if (!pattern) {
that.invalidCharacterError(character);
}
that.addPattern(pattern);
},
addPattern: function (pattern) {
for (var i = 0; i < pattern.length; i++) {
this.pattern.push(parseInt(pattern.charAt(i), 10));
}
},
addCheckSum: function () {
var that = this, checkSumFunction = that.checkSums[that.checkSumType], checkValues;
checkValues = checkSumFunction.call(that.checkSums, that.value);
that.checksum = checkValues.join('');
for (var i = 0; i < checkValues.length; i++) {
that.checkSumLength++;
that.addPattern(that.characterMap[checkValues[i]]);
}
},
checkSums: {
Modulo10: function (value) {
var checkValues = [
0,
''
], odd = value.length % 2, idx, evenSum, oddSum;
for (idx = 0; idx < value.length; idx++) {
checkValues[(idx + odd) % 2] += parseInt(value.charAt(idx), 10);
}
oddSum = checkValues[0];
evenSum = (checkValues[1] * 2).toString();
for (idx = 0; idx < evenSum.length; idx++) {
oddSum += parseInt(evenSum.charAt(idx), 10);
}
return [(10 - oddSum % 10) % 10];
},
Modulo11: function (value) {
var weightedSum = 0, mod = 11, length = value.length, weight, checkValue;
for (var i = 0; i < length; i++) {
weight = ((length - i) % 6 || 6) + 1;
weightedSum += weight * value.charAt(i);
}
checkValue = (mod - weightedSum % mod) % mod;
if (checkValue != 10) {
return [checkValue];
}
return [
1,
0
];
},
Modulo11Modulo10: function (value) {
var checkValues = this.Modulo11(value), mod11Value;
mod11Value = value + checkValues[0];
return checkValues.concat(this.Modulo10(mod11Value));
},
Modulo10Modulo10: function (value) {
var checkValues = this.Modulo10(value), mod10Value;
mod10Value = value + checkValues[0];
return checkValues.concat(this.Modulo10(mod10Value));
}
},
characterMap: [
'12121212',
'12121221',
'12122112',
'12122121',
'12211212',
'12211221',
'12212112',
'12212121',
'21121212',
'21121221'
],
START: '21',
STOP: '121',
checkSumType: ''
});
encodings.msimod10 = msiBase.extend({
name: 'MSI Modulo10',
checkSumType: 'Modulo10'
});
encodings.msimod11 = msiBase.extend({
name: 'MSI Modulo11',
checkSumType: 'Modulo11'
});
encodings.msimod1110 = msiBase.extend({
name: 'MSI Modulo11 Modulo10',
checkSumType: 'Modulo11Modulo10'
});
encodings.msimod1010 = msiBase.extend({
name: 'MSI Modulo10 Modulo10',
checkSumType: 'Modulo10Modulo10'
});
encodings.code11 = Encoding.extend({
name: 'Code 11',
cCheckSumTotal: 10,
kCheckSumTotal: 9,
kCheckSumMinLength: 10,
checkSumMod: 11,
DASH_VALUE: 10,
DASH: '-',
START: '112211',
STOP: '11221',
initValue: function (value, width) {
var that = this;
that.pattern = [];
that.value = value;
that.width = width;
that.totalUnits = 0;
},
addData: function () {
var that = this;
var value = that.value;
that.addPattern(that.START);
for (var i = 0; i < value.length; i++) {
that.addCharacter(value.charAt(i));
}
if (that.options.addCheckSum) {
that.addCheckSum();
}
that.addPattern(that.STOP);
that.setBaseUnit();
},
setBaseUnit: function () {
var that = this;
that.baseUnit = that.width / (that.totalUnits + that.quietZoneLength);
},
addCheckSum: function () {
var that = this, value = that.value, length = value.length, cValue;
cValue = that.getWeightedSum(value, length, that.cCheckSumTotal) % that.checkSumMod;
that.checksum = cValue + '';
that.addPattern(that.characterMap[cValue]);
length++;
if (length >= that.kCheckSumMinLength) {
var kValue = (cValue + that.getWeightedSum(value, length, that.kCheckSumTotal)) % that.checkSumMod;
that.checksum += kValue;
that.addPattern(that.characterMap[kValue]);
}
},
getWeightedSum: function (value, length, total) {
var weightedSum = 0;
for (var i = 0; i < value.length; i++) {
weightedSum += this.weightedValue(this.getValue(value.charAt(i)), length, i, total);
}
return weightedSum;
},
weightedValue: function (value, length, index, total) {
var weight = (length - index) % total || total;
return weight * value;
},
getValue: function (character) {
var that = this;
if (!isNaN(character)) {
return parseInt(character, 10);
} else if (character !== that.DASH) {
that.invalidCharacterError(character);
}
return that.DASH_VALUE;
},
addCharacter: function (character) {
var that = this, value = that.getValue(character), pattern = that.characterMap[value];
that.addPattern(pattern);
},
addPattern: function (pattern) {
var value;
for (var i = 0; i < pattern.length; i++) {
value = parseInt(pattern.charAt(i), 10);
this.pattern.push(value);
this.totalUnits += value;
}
},
characterMap: [
'111121',
'211121',
'121121',
'221111',
'112121',
'212111',
'122111',
'111221',
'211211',
'211111',
'112111'
],
options: { addCheckSum: true }
});
encodings.postnet = Encoding.extend({
name: 'Postnet',
START: '2',
VALID_CODE_LENGTHS: [
5,
9,
11
],
DIGIT_SEPARATOR: '-',
initValue: function (value, width, height) {
var that = this;
that.height = height;
that.width = width;
that.baseHeight = height / 2;
that.value = value.replace(new RegExp(that.DIGIT_SEPARATOR, 'g'), '');
that.pattern = [];
that.validate(that.value);
that.checkSum = 0;
that.setBaseUnit();
},
addData: function () {
var that = this, value = that.value;
that.addPattern(that.START);
for (var i = 0; i < value.length; i++) {
that.addCharacter(value.charAt(i));
}
if (that.options.addCheckSum) {
that.addCheckSum();
}
that.addPattern(that.START);
that.pattern.pop();
},
addCharacter: function (character) {
var that = this, pattern = that.characterMap[character];
that.checkSum += parseInt(character, 10);
that.addPattern(pattern);
},
addCheckSum: function () {
var that = this;
that.checksum = (10 - that.checkSum % 10) % 10;
that.addCharacter(that.checksum);
},
setBaseUnit: function () {
var that = this, startStopLength = 3;
that.baseUnit = that.width / ((that.value.length + 1) * 10 + startStopLength + that.quietZoneLength);
},
validate: function (value) {
var that = this;
if (!numberRegex.test(value)) {
that.invalidCharacterError(value.match(/[^0-9]/)[0]);
}
if (inArray(value.length, that.VALID_CODE_LENGTHS) < 0) {
throw new Error('Invalid value length. Valid lengths for the Postnet symbology are ' + that.VALID_CODE_LENGTHS.join(','));
}
},
addPattern: function (pattern) {
var that = this, y1;
for (var i = 0; i < pattern.length; i++) {
y1 = that.height - that.baseHeight * pattern.charAt(i);
that.pattern.push({
width: 1,
y1: y1,
y2: that.height
});
that.pattern.push(1);
}
},
characterMap: [
'22111',
'11122',
'11212',
'11221',
'12112',
'12121',
'12211',
'21112',
'21121',
'21211'
]
});
encodings.ean13 = Encoding.extend({
initValue: function (value, width, height) {
value += '';
if (value.length != 12 || /\D/.test(value)) {
throw new Error('The value of the "EAN13" encoding should be 12 symbols');
}
var that = this;
that.pattern = [];
that.options.height = height;
that.baseUnit = width / (95 + that.quietZoneLength);
that.value = value;
that.checksum = that.calculateChecksum();
that.leftKey = value[0];
that.leftPart = value.substr(1, 6);
that.rightPart = value.substr(7) + that.checksum;
},
addData: function () {
var that = this;
that.addPieces(that.characterMap.start);
that.addSide(that.leftPart, that.leftKey);
that.addPieces(that.characterMap.middle);
that.addSide(that.rightPart);
that.addPieces(that.characterMap.start);
},
addSide: function (leftPart, key) {
var that = this;
for (var i = 0; i < leftPart.length; i++) {
if (key && parseInt(that.keyTable[key].charAt(i), 10)) {
that.addPieces(Array.prototype.slice.call(that.characterMap.digits[leftPart.charAt(i)]).reverse(), true);
} else {
that.addPieces(that.characterMap.digits[leftPart.charAt(i)], true);
}
}
},
addPieces: function (arrToAdd, limitedHeight) {
var that = this;
for (var i = 0; i < arrToAdd.length; i++) {
if (limitedHeight) {
that.pattern.push({
y1: 0,
y2: that.options.height * 0.95,
width: arrToAdd[i]
});
} else {
that.pattern.push(arrToAdd[i]);
}
}
},
calculateChecksum: function () {
var odd = 0, even = 0, value = this.value.split('').reverse().join('');
for (var i = 0; i < value.length; i++) {
if (i % 2) {
even += parseInt(value.charAt(i), 10);
} else {
odd += parseInt(value.charAt(i), 10);
}
}
var checksum = (10 - (3 * odd + even) % 10) % 10;
return checksum;
},
keyTable: [
'000000',
'001011',
'001101',
'001110',
'010011',
'011001',
'011100',
'010101',
'010110',
'011010'
],
characterMap: {
digits: [
[
3,
2,
1,
1
],
[
2,
2,
2,
1
],
[
2,
1,
2,
2
],
[
1,
4,
1,
1
],
[
1,
1,
3,
2
],
[
1,
2,
3,
1
],
[
1,
1,
1,
4
],
[
1,
3,
1,
2
],
[
1,
2,
1,
3
],
[
3,
1,
1,
2
]
],
start: [
1,
1,
1
],
middle: [
1,
1,
1,
1,
1
]
}
});
encodings.ean8 = encodings.ean13.extend({
initValue: function (value, width, height) {
var that = this;
if (value.length != 7 || /\D/.test(value)) {
throw new Error('Invalid value provided');
}
that.value = value;
that.options.height = height;
that.checksum = that.calculateChecksum(that.value);
that.leftPart = that.value.substr(0, 4);
that.rightPart = that.value.substr(4) + that.checksum;
that.pattern = [];
that.baseUnit = width / (67 + that.quietZoneLength);
}
});
var Barcode = Widget.extend({
init: function (element, options) {
var that = this;
Widget.fn.init.call(that, element, options);
that.element = $(element);
that.wrapper = that.element;
that.element.addClass('k-barcode').css('display', 'block');
that.surfaceWrap = $('
').css('position', 'relative').appendTo(this.element);
that.surface = draw.Surface.create(that.surfaceWrap, { type: that.options.renderAs });
that.setOptions(options);
},
setOptions: function (options) {
var that = this;
that.type = (options.type || that.options.type).toLowerCase();
if (that.type == 'upca') {
that.type = 'ean13';
options.value = '0' + options.value;
}
if (that.type == 'upce') {
that.type = 'ean8';
options.value = '0' + options.value;
}
if (!encodings[that.type]) {
throw new Error('Encoding ' + that.type + 'is not supported.');
}
that.encoding = new encodings[that.type]();
that.options = extend(true, that.options, options);
if (!defined(options.value)) {
return;
}
that.redraw();
},
redraw: function () {
var size = this._getSize();
this.surface.clear();
this.surface.setSize({
width: size.width,
height: size.height
});
this.createVisual();
this.surface.draw(this.visual);
},
getSize: function () {
return kendo.dimensions(this.element);
},
_resize: function () {
this.redraw();
},
createVisual: function () {
this.visual = this._render();
},
_render: function () {
var that = this, options = that.options, value = options.value, textOptions = options.text, textMargin = dataviz.getSpacing(textOptions.margin), size = that._getSize(), border = options.border || {}, encoding = that.encoding, contentBox = Box2D(0, 0, size.width, size.height).unpad(border.width).unpad(options.padding), barHeight = contentBox.height(), result, textToDisplay, textHeight;
var visual = new draw.Group();
that.contentBox = contentBox;
visual.append(that._getBackground(size));
if (textOptions.visible) {
textHeight = draw.util.measureText(value, { font: textOptions.font }).height;
barHeight -= textHeight + textMargin.top + textMargin.bottom;
}
result = encoding.encode(value, contentBox.width(), barHeight);
if (textOptions.visible) {
textToDisplay = value;
if (options.checksum && defined(encoding.checksum)) {
textToDisplay += ' ' + encoding.checksum;
}
visual.append(that._getText(textToDisplay));
}
that.barHeight = barHeight;
this._bandsGroup = this._getBands(result.pattern, result.baseUnit);
visual.append(this._bandsGroup);
return visual;
},
exportVisual: function () {
return this._render();
},
_getSize: function () {
var that = this, element = that.element, size = new geom.Size(DEFAULT_WIDTH, DEFAULT_HEIGHT);
if (element.width() > 0) {
size.width = element.width();
}
if (element.height() > 0) {
size.height = element.height();
}
if (that.options.width) {
size.width = that.options.width;
}
if (that.options.height) {
size.height = that.options.height;
}
return size;
},
value: function (value) {
var that = this;
if (!defined(value)) {
return that.options.value;
}
that.options.value = value + '';
that.redraw();
},
_getBands: function (pattern, baseUnit) {
var that = this, contentBox = that.contentBox, position = contentBox.x1, step, item;
var group = new draw.Group();
for (var i = 0; i < pattern.length; i++) {
item = isPlainObject(pattern[i]) ? pattern[i] : {
width: pattern[i],
y1: 0,
y2: that.barHeight
};
step = item.width * baseUnit;
if (i % 2) {
var rect = geom.Rect.fromPoints(new geom.Point(position, item.y1 + contentBox.y1), new geom.Point(position + step, item.y2 + contentBox.y1));
var path = draw.Path.fromRect(rect, {
fill: { color: that.options.color },
stroke: null
});
group.append(path);
}
position += step;
}
return group;
},
_getBackground: function (size) {
var that = this, options = that.options, border = options.border || {};
var box = Box2D(0, 0, size.width, size.height).unpad(border.width / 2);
var path = draw.Path.fromRect(box.toRect(), {
fill: { color: options.background },
stroke: {
color: border.width ? border.color : '',
width: border.width,
dashType: border.dashType
}
});
return path;
},
_getText: function (value) {
var that = this, textOptions = that.options.text, text = that._textbox = new TextBox(value, {
font: textOptions.font,
color: textOptions.color,
align: 'center',
vAlign: 'bottom',
margin: textOptions.margin
});
text.reflow(that.contentBox);
text.renderVisual();
return text.visual;
},
options: {
name: 'Barcode',
renderAs: 'svg',
value: '',
type: 'code39',
checksum: false,
width: 0,
height: 0,
color: 'black',
background: 'white',
text: {
visible: true,
font: '16px Consolas, Monaco, Sans Mono, monospace, sans-serif',
color: 'black',
margin: {
top: 0,
bottom: 0,
left: 0,
right: 0
}
},
border: {
width: 0,
dashType: 'solid',
color: 'black'
},
padding: {
top: 0,
bottom: 0,
left: 0,
right: 0
}
}
});
dataviz.ExportMixin.extend(Barcode.fn);
dataviz.ui.plugin(Barcode);
kendo.deepExtend(dataviz, {
encodings: encodings,
Encoding: Encoding
});
}(window.kendo.jQuery));
return window.kendo;
}, typeof define == 'function' && define.amd ? define : function (a1, a2, a3) {
(a3 || a2)();
}));
(function (f, define) {
define('kendo.dataviz.qrcode', [
'kendo.dataviz.core',
'kendo.drawing'
], f);
}(function () {
var __meta__ = {
id: 'dataviz.qrcode',
name: 'QRCode',
category: 'dataviz',
description: 'QRCode widget.',
depends: [
'dataviz.core',
'drawing'
]
};
(function ($, undefined) {
var kendo = window.kendo, extend = $.extend, draw = kendo.drawing, dataviz = kendo.dataviz, Widget = kendo.ui.Widget, Box2D = dataviz.Box2D, terminator = '0000', NUMERIC = 'numeric', ALPHA_NUMERIC = 'alphanumeric', BYTE = 'byte', powersOfTwo = { '1': 0 }, powersOfTwoResult = { '0': 1 }, generatorPolynomials = [
[
1,
0
],
[
1,
25,
0
]
], irregularAlignmentPatternsStartDistance = {
15: 20,
16: 20,
18: 24,
19: 24,
22: 20,
24: 22,
26: 24,
28: 20,
30: 20,
31: 24,
32: 28,
33: 24,
36: 18,
37: 22,
39: 20,
40: 24
}, versionsCodewordsInformation = [
{
L: {
groups: [[
1,
19
]],
totalDataCodewords: 19,
errorCodewordsPerBlock: 7
},
M: {
groups: [[
1,
16
]],
totalDataCodewords: 16,
errorCodewordsPerBlock: 10
},
Q: {
groups: [[
1,
13
]],
totalDataCodewords: 13,
errorCodewordsPerBlock: 13
},
H: {
groups: [[
1,
9
]],
totalDataCodewords: 9,
errorCodewordsPerBlock: 17
}
},
{
L: {
groups: [[
1,
34
]],
totalDataCodewords: 34,
errorCodewordsPerBlock: 10
},
M: {
groups: [[
1,
28
]],
totalDataCodewords: 28,
errorCodewordsPerBlock: 16
},
Q: {
groups: [[
1,
22
]],
totalDataCodewords: 22,
errorCodewordsPerBlock: 22
},
H: {
groups: [[
1,
16
]],
totalDataCodewords: 16,
errorCodewordsPerBlock: 28
}
},
{
L: {
groups: [[
1,
55
]],
totalDataCodewords: 55,
errorCodewordsPerBlock: 15
},
M: {
groups: [[
1,
44
]],
totalDataCodewords: 44,
errorCodewordsPerBlock: 26
},
Q: {
groups: [[
2,
17
]],
totalDataCodewords: 34,
errorCodewordsPerBlock: 18
},
H: {
groups: [[
2,
13
]],
totalDataCodewords: 26,
errorCodewordsPerBlock: 22
}
},
{
L: {
groups: [[
1,
80
]],
totalDataCodewords: 80,
errorCodewordsPerBlock: 20
},
M: {
groups: [[
2,
32
]],
totalDataCodewords: 64,
errorCodewordsPerBlock: 18
},
Q: {
groups: [[
2,
24
]],
totalDataCodewords: 48,
errorCodewordsPerBlock: 26
},
H: {
groups: [[
4,
9
]],
totalDataCodewords: 36,
errorCodewordsPerBlock: 16
}
},
{
L: {
groups: [[
1,
108
]],
totalDataCodewords: 108,
errorCodewordsPerBlock: 26
},
M: {
groups: [[
2,
43
]],
totalDataCodewords: 86,
errorCodewordsPerBlock: 24
},
Q: {
groups: [
[
2,
15
],
[
2,
16
]
],
totalDataCodewords: 62,
errorCodewordsPerBlock: 18
},
H: {
groups: [
[
2,
11
],
[
2,
12
]
],
totalDataCodewords: 46,
errorCodewordsPerBlock: 22
}
},
{
L: {
groups: [[
2,
68
]],
totalDataCodewords: 136,
errorCodewordsPerBlock: 18
},
M: {
groups: [[
4,
27
]],
totalDataCodewords: 108,
errorCodewordsPerBlock: 16
},
Q: {
groups: [[
4,
19
]],
totalDataCodewords: 76,
errorCodewordsPerBlock: 24
},
H: {
groups: [[
4,
15
]],
totalDataCodewords: 60,
errorCodewordsPerBlock: 28
}
},
{
L: {
groups: [[
2,
78
]],
totalDataCodewords: 156,
errorCodewordsPerBlock: 20
},
M: {
groups: [[
4,
31
]],
totalDataCodewords: 124,
errorCodewordsPerBlock: 18
},
Q: {
groups: [
[
2,
14
],
[
4,
15
]
],
totalDataCodewords: 88,
errorCodewordsPerBlock: 18
},
H: {
groups: [
[
4,
13
],
[
1,
14
]
],
totalDataCodewords: 66,
errorCodewordsPerBlock: 26
}
},
{
L: {
groups: [[
2,
97
]],
totalDataCodewords: 194,
errorCodewordsPerBlock: 24
},
M: {
groups: [
[
2,
38
],
[
2,
39
]
],
totalDataCodewords: 154,
errorCodewordsPerBlock: 22
},
Q: {
groups: [
[
4,
18
],
[
2,
19
]
],
totalDataCodewords: 110,
errorCodewordsPerBlock: 22
},
H: {
groups: [
[
4,
14
],
[
2,
15
]
],
totalDataCodewords: 86,
errorCodewordsPerBlock: 26
}
},
{
L: {
groups: [[
2,
116
]],
totalDataCodewords: 232,
errorCodewordsPerBlock: 30
},
M: {
groups: [
[
3,
36
],
[
2,
37
]
],
totalDataCodewords: 182,
errorCodewordsPerBlock: 22
},
Q: {
groups: [
[
4,
16
],
[
4,
17
]
],
totalDataCodewords: 132,
errorCodewordsPerBlock: 20
},
H: {
groups: [
[
4,
12
],
[
4,
13
]
],
totalDataCodewords: 100,
errorCodewordsPerBlock: 24
}
},
{
L: {
groups: [
[
2,
68
],
[
2,
69
]
],
totalDataCodewords: 274,
errorCodewordsPerBlock: 18
},
M: {
groups: [
[
4,
43
],
[
1,
44
]
],
totalDataCodewords: 216,
errorCodewordsPerBlock: 26
},
Q: {
groups: [
[
6,
19
],
[
2,
20
]
],
totalDataCodewords: 154,
errorCodewordsPerBlock: 24
},
H: {
groups: [
[
6,
15
],
[
2,
16
]
],
totalDataCodewords: 122,
errorCodewordsPerBlock: 28
}
},
{
L: {
groups: [[
4,
81
]],
totalDataCodewords: 324,
errorCodewordsPerBlock: 20
},
M: {
groups: [
[
1,
50
],
[
4,
51
]
],
totalDataCodewords: 254,
errorCodewordsPerBlock: 30
},
Q: {
groups: [
[
4,
22
],
[
4,
23
]
],
totalDataCodewords: 180,
errorCodewordsPerBlock: 28
},
H: {
groups: [
[
3,
12
],
[
8,
13
]
],
totalDataCodewords: 140,
errorCodewordsPerBlock: 24
}
},
{
L: {
groups: [
[
2,
92
],
[
2,
93
]
],
totalDataCodewords: 370,
errorCodewordsPerBlock: 24
},
M: {
groups: [
[
6,
36
],
[
2,
37
]
],
totalDataCodewords: 290,
errorCodewordsPerBlock: 22
},
Q: {
groups: [
[
4,
20
],
[
6,
21
]
],
totalDataCodewords: 206,
errorCodewordsPerBlock: 26
},
H: {
groups: [
[
7,
14
],
[
4,
15
]
],
totalDataCodewords: 158,
errorCodewordsPerBlock: 28
}
},
{
L: {
groups: [[
4,
107
]],
totalDataCodewords: 428,
errorCodewordsPerBlock: 26
},
M: {
groups: [
[
8,
37
],
[
1,
38
]
],
totalDataCodewords: 334,
errorCodewordsPerBlock: 22
},
Q: {
groups: [
[
8,
20
],
[
4,
21
]
],
totalDataCodewords: 244,
errorCodewordsPerBlock: 24
},
H: {
groups: [
[
12,
11
],
[
4,
12
]
],
totalDataCodewords: 180,
errorCodewordsPerBlock: 22
}
},
{
L: {
groups: [
[
3,
115
],
[
1,
116
]
],
totalDataCodewords: 461,
errorCodewordsPerBlock: 30
},
M: {
groups: [
[
4,
40
],
[
5,
41
]
],
totalDataCodewords: 365,
errorCodewordsPerBlock: 24
},
Q: {
groups: [
[
11,
16
],
[
5,
17
]
],
totalDataCodewords: 261,
errorCodewordsPerBlock: 20
},
H: {
groups: [
[
11,
12
],
[
5,
13
]
],
totalDataCodewords: 197,
errorCodewordsPerBlock: 24
}
},
{
L: {
groups: [
[
5,
87
],
[
1,
88
]
],
totalDataCodewords: 523,
errorCodewordsPerBlock: 22
},
M: {
groups: [
[
5,
41
],
[
5,
42
]
],
totalDataCodewords: 415,
errorCodewordsPerBlock: 24
},
Q: {
groups: [
[
5,
24
],
[
7,
25
]
],
totalDataCodewords: 295,
errorCodewordsPerBlock: 30
},
H: {
groups: [
[
11,
12
],
[
7,
13
]
],
totalDataCodewords: 223,
errorCodewordsPerBlock: 24
}
},
{
L: {
groups: [
[
5,
98
],
[
1,
99
]
],
totalDataCodewords: 589,
errorCodewordsPerBlock: 24
},
M: {
groups: [
[
7,
45
],
[
3,
46
]
],
totalDataCodewords: 453,
errorCodewordsPerBlock: 28
},
Q: {
groups: [
[
15,
19
],
[
2,
20
]
],
totalDataCodewords: 325,
errorCodewordsPerBlock: 24
},
H: {
groups: [
[
3,
15
],
[
13,
16
]
],
totalDataCodewords: 253,
errorCodewordsPerBlock: 30
}
},
{
L: {
groups: [
[
1,
107
],
[
5,
108
]
],
totalDataCodewords: 647,
errorCodewordsPerBlock: 28
},
M: {
groups: [
[
10,
46
],
[
1,
47
]
],
totalDataCodewords: 507,
errorCodewordsPerBlock: 28
},
Q: {
groups: [
[
1,
22
],
[
15,
23
]
],
totalDataCodewords: 367,
errorCodewordsPerBlock: 28
},
H: {
groups: [
[
2,
14
],
[
17,
15
]
],
totalDataCodewords: 283,
errorCodewordsPerBlock: 28
}
},
{
L: {
groups: [
[
5,
120
],
[
1,
121
]
],
totalDataCodewords: 721,
errorCodewordsPerBlock: 30
},
M: {
groups: [
[
9,
43
],
[
4,
44
]
],
totalDataCodewords: 563,
errorCodewordsPerBlock: 26
},
Q: {
groups: [
[
17,
22
],
[
1,
23
]
],
totalDataCodewords: 397,
errorCodewordsPerBlock: 28
},
H: {
groups: [
[
2,
14
],
[
19,
15
]
],
totalDataCodewords: 313,
errorCodewordsPerBlock: 28
}
},
{
L: {
groups: [
[
3,
113
],
[
4,
114
]
],
totalDataCodewords: 795,
errorCodewordsPerBlock: 28
},
M: {
groups: [
[
3,
44
],
[
11,
45
]
],
totalDataCodewords: 627,
errorCodewordsPerBlock: 26
},
Q: {
groups: [
[
17,
21
],
[
4,
22
]
],
totalDataCodewords: 445,
errorCodewordsPerBlock: 26
},
H: {
groups: [
[
9,
13
],
[
16,
14
]
],
totalDataCodewords: 341,
errorCodewordsPerBlock: 26
}
},
{
L: {
groups: [
[
3,
107
],
[
5,
108
]
],
totalDataCodewords: 861,
errorCodewordsPerBlock: 28
},
M: {
groups: [
[
3,
41
],
[
13,
42
]
],
totalDataCodewords: 669,
errorCodewordsPerBlock: 26
},
Q: {
groups: [
[
15,
24
],
[
5,
25
]
],
totalDataCodewords: 485,
errorCodewordsPerBlock: 30
},
H: {
groups: [
[
15,
15
],
[
10,
16
]
],
totalDataCodewords: 385,
errorCodewordsPerBlock: 28
}
},
{
L: {
groups: [
[
4,
116
],
[
4,
117
]
],
totalDataCodewords: 932,
errorCodewordsPerBlock: 28
},
M: {
groups: [[
17,
42
]],
totalDataCodewords: 714,
errorCodewordsPerBlock: 26
},
Q: {
groups: [
[
17,
22
],
[
6,
23
]
],
totalDataCodewords: 512,
errorCodewordsPerBlock: 28
},
H: {
groups: [
[
19,
16
],
[
6,
17
]
],
totalDataCodewords: 406,
errorCodewordsPerBlock: 30
}
},
{
L: {
groups: [
[
2,
111
],
[
7,
112
]
],
totalDataCodewords: 1006,
errorCodewordsPerBlock: 28
},
M: {
groups: [[
17,
46
]],
totalDataCodewords: 782,
errorCodewordsPerBlock: 28
},
Q: {
groups: [
[
7,
24
],
[
16,
25
]
],
totalDataCodewords: 568,
errorCodewordsPerBlock: 30
},
H: {
groups: [[
34,
13
]],
totalDataCodewords: 442,
errorCodewordsPerBlock: 24
}
},
{
L: {
groups: [
[
4,
121
],
[
5,
122
]
],
totalDataCodewords: 1094,
errorCodewordsPerBlock: 30
},
M: {
groups: [
[
4,
47
],
[
14,
48
]
],
totalDataCodewords: 860,
errorCodewordsPerBlock: 28
},
Q: {
groups: [
[
11,
24
],
[
14,
25
]
],
totalDataCodewords: 614,
errorCodewordsPerBlock: 30
},
H: {
groups: [
[
16,
15
],
[
14,
16
]
],
totalDataCodewords: 464,
errorCodewordsPerBlock: 30
}
},
{
L: {
groups: [
[
6,
117
],
[
4,
118
]
],
totalDataCodewords: 1174,
errorCodewordsPerBlock: 30
},
M: {
groups: [
[
6,
45
],
[
14,
46
]
],
totalDataCodewords: 914,
errorCodewordsPerBlock: 28
},
Q: {
groups: [
[
11,
24
],
[
16,
25
]
],
totalDataCodewords: 664,
errorCodewordsPerBlock: 30
},
H: {
groups: [
[
30,
16
],
[
2,
17
]
],
totalDataCodewords: 514,
errorCodewordsPerBlock: 30
}
},
{
L: {
groups: [
[
8,
106
],
[
4,
107
]
],
totalDataCodewords: 1276,
errorCodewordsPerBlock: 26
},
M: {
groups: [
[
8,
47
],
[
13,
48
]
],
totalDataCodewords: 1000,
errorCodewordsPerBlock: 28
},
Q: {
groups: [
[
7,
24
],
[
22,
25
]
],
totalDataCodewords: 718,
errorCodewordsPerBlock: 30
},
H: {
groups: [
[
22,
15
],
[
13,
16
]
],
totalDataCodewords: 538,
errorCodewordsPerBlock: 30
}
},
{
L: {
groups: [
[
10,
114
],
[
2,
115
]
],
totalDataCodewords: 1370,
errorCodewordsPerBlock: 28
},
M: {
groups: [
[
19,
46
],
[
4,
47
]
],
totalDataCodewords: 1062,
errorCodewordsPerBlock: 28
},
Q: {
groups: [
[
28,
22
],
[
6,
23
]
],
totalDataCodewords: 754,
errorCodewordsPerBlock: 28
},
H: {
groups: [
[
33,
16
],
[
4,
17
]
],
totalDataCodewords: 596,
errorCodewordsPerBlock: 30
}
},
{
L: {
groups: [
[
8,
122
],
[
4,
123
]
],
totalDataCodewords: 1468,
errorCodewordsPerBlock: 30
},
M: {
groups: [
[
22,
45
],
[
3,
46
]
],
totalDataCodewords: 1128,
errorCodewordsPerBlock: 28
},
Q: {
groups: [
[
8,
23
],
[
26,
24
]
],
totalDataCodewords: 808,
errorCodewordsPerBlock: 30
},
H: {
groups: [
[
12,
15
],
[
28,
16
]
],
totalDataCodewords: 628,
errorCodewordsPerBlock: 30
}
},
{
L: {
groups: [
[
3,
117
],
[
10,
118
]
],
totalDataCodewords: 1531,
errorCodewordsPerBlock: 30
},
M: {
groups: [
[
3,
45
],
[
23,
46
]
],
totalDataCodewords: 1193,
errorCodewordsPerBlock: 28
},
Q: {
groups: [
[
4,
24
],
[
31,
25
]
],
totalDataCodewords: 871,
errorCodewordsPerBlock: 30
},
H: {
groups: [
[
11,
15
],
[
31,
16
]
],
totalDataCodewords: 661,
errorCodewordsPerBlock: 30
}
},
{
L: {
groups: [
[
7,
116
],
[
7,
117
]
],
totalDataCodewords: 1631,
errorCodewordsPerBlock: 30
},
M: {
groups: [
[
21,
45
],
[
7,
46
]
],
totalDataCodewords: 1267,
errorCodewordsPerBlock: 28
},
Q: {
groups: [
[
1,
23
],
[
37,
24
]
],
totalDataCodewords: 911,
errorCodewordsPerBlock: 30
},
H: {
groups: [
[
19,
15
],
[
26,
16
]
],
totalDataCodewords: 701,
errorCodewordsPerBlock: 30
}
},
{
L: {
groups: [
[
5,
115
],
[
10,
116
]
],
totalDataCodewords: 1735,
errorCodewordsPerBlock: 30
},
M: {
groups: [
[
19,
47
],
[
10,
48
]
],
totalDataCodewords: 1373,
errorCodewordsPerBlock: 28
},
Q: {
groups: [
[
15,
24
],
[
25,
25
]
],
totalDataCodewords: 985,
errorCodewordsPerBlock: 30
},
H: {
groups: [
[
23,
15
],
[
25,
16
]
],
totalDataCodewords: 745,
errorCodewordsPerBlock: 30
}
},
{
L: {
groups: [
[
13,
115
],
[
3,
116
]
],
totalDataCodewords: 1843,
errorCodewordsPerBlock: 30
},
M: {
groups: [
[
2,
46
],
[
29,
47
]
],
totalDataCodewords: 1455,
errorCodewordsPerBlock: 28
},
Q: {
groups: [
[
42,
24
],
[
1,
25
]
],
totalDataCodewords: 1033,
errorCodewordsPerBlock: 30
},
H: {
groups: [
[
23,
15
],
[
28,
16
]
],
totalDataCodewords: 793,
errorCodewordsPerBlock: 30
}
},
{
L: {
groups: [[
17,
115
]],
totalDataCodewords: 1955,
errorCodewordsPerBlock: 30
},
M: {
groups: [
[
10,
46
],
[
23,
47
]
],
totalDataCodewords: 1541,
errorCodewordsPerBlock: 28
},
Q: {
groups: [
[
10,
24
],
[
35,
25
]
],
totalDataCodewords: 1115,
errorCodewordsPerBlock: 30
},
H: {
groups: [
[
19,
15
],
[
35,
16
]
],
totalDataCodewords: 845,
errorCodewordsPerBlock: 30
}
},
{
L: {
groups: [
[
17,
115
],
[
1,
116
]
],
totalDataCodewords: 2071,
errorCodewordsPerBlock: 30
},
M: {
groups: [
[
14,
46
],
[
21,
47
]
],
totalDataCodewords: 1631,
errorCodewordsPerBlock: 28
},
Q: {
groups: [
[
29,
24
],
[
19,
25
]
],
totalDataCodewords: 1171,
errorCodewordsPerBlock: 30
},
H: {
groups: [
[
11,
15
],
[
46,
16
]
],
totalDataCodewords: 901,
errorCodewordsPerBlock: 30
}
},
{
L: {
groups: [
[
13,
115
],
[
6,
116
]
],
totalDataCodewords: 2191,
errorCodewordsPerBlock: 30
},
M: {
groups: [
[
14,
46
],
[
23,
47
]
],
totalDataCodewords: 1725,
errorCodewordsPerBlock: 28
},
Q: {
groups: [
[
44,
24
],
[
7,
25
]
],
totalDataCodewords: 1231,
errorCodewordsPerBlock: 30
},
H: {
groups: [
[
59,
16
],
[
1,
17
]
],
totalDataCodewords: 961,
errorCodewordsPerBlock: 30
}
},
{
L: {
groups: [
[
12,
121
],
[
7,
122
]
],
totalDataCodewords: 2306,
errorCodewordsPerBlock: 30
},
M: {
groups: [
[
12,
47
],
[
26,
48
]
],
totalDataCodewords: 1812,
errorCodewordsPerBlock: 28
},
Q: {
groups: [
[
39,
24
],
[
14,
25
]
],
totalDataCodewords: 1286,
errorCodewordsPerBlock: 30
},
H: {
groups: [
[
22,
15
],
[
41,
16
]
],
totalDataCodewords: 986,
errorCodewordsPerBlock: 30
}
},
{
L: {
groups: [
[
6,
121
],
[
14,
122
]
],
totalDataCodewords: 2434,
errorCodewordsPerBlock: 30
},
M: {
groups: [
[
6,
47
],
[
34,
48
]
],
totalDataCodewords: 1914,
errorCodewordsPerBlock: 28
},
Q: {
groups: [
[
46,
24
],
[
10,
25
]
],
totalDataCodewords: 1354,
errorCodewordsPerBlock: 30
},
H: {
groups: [
[
2,
15
],
[
64,
16
]
],
totalDataCodewords: 1054,
errorCodewordsPerBlock: 30
}
},
{
L: {
groups: [
[
17,
122
],
[
4,
123
]
],
totalDataCodewords: 2566,
errorCodewordsPerBlock: 30
},
M: {
groups: [
[
29,
46
],
[
14,
47
]
],
totalDataCodewords: 1992,
errorCodewordsPerBlock: 28
},
Q: {
groups: [
[
49,
24
],
[
10,
25
]
],
totalDataCodewords: 1426,
errorCodewordsPerBlock: 30
},
H: {
groups: [
[
24,
15
],
[
46,
16
]
],
totalDataCodewords: 1096,
errorCodewordsPerBlock: 30
}
},
{
L: {
groups: [
[
4,
122
],
[
18,
123
]
],
totalDataCodewords: 2702,
errorCodewordsPerBlock: 30
},
M: {
groups: [
[
13,
46
],
[
32,
47
]
],
totalDataCodewords: 2102,
errorCodewordsPerBlock: 28
},
Q: {
groups: [
[
48,
24
],
[
14,
25
]
],
totalDataCodewords: 1502,
errorCodewordsPerBlock: 30
},
H: {
groups: [
[
42,
15
],
[
32,
16
]
],
totalDataCodewords: 1142,
errorCodewordsPerBlock: 30
}
},
{
L: {
groups: [
[
20,
117
],
[
4,
118
]
],
totalDataCodewords: 2812,
errorCodewordsPerBlock: 30
},
M: {
groups: [
[
40,
47
],
[
7,
48
]
],
totalDataCodewords: 2216,
errorCodewordsPerBlock: 28
},
Q: {
groups: [
[
43,
24
],
[
22,
25
]
],
totalDataCodewords: 1582,
errorCodewordsPerBlock: 30
},
H: {
groups: [
[
10,
15
],
[
67,
16
]
],
totalDataCodewords: 1222,
errorCodewordsPerBlock: 30
}
},
{
L: {
groups: [
[
19,
118
],
[
6,
119
]
],
totalDataCodewords: 2956,
errorCodewordsPerBlock: 30
},
M: {
groups: [
[
18,
47
],
[
31,
48
]
],
totalDataCodewords: 2334,
errorCodewordsPerBlock: 28
},
Q: {
groups: [
[
34,
24
],
[
34,
25
]
],
totalDataCodewords: 1666,
errorCodewordsPerBlock: 30
},
H: {
groups: [
[
20,
15
],
[
61,
16
]
],
totalDataCodewords: 1276,
errorCodewordsPerBlock: 30
}
}
], finderPattern = [
1,
0,
1,
1,
1
], alignmentPattern = [
1,
0,
1
], errorCorrectionPatterns = {
L: '01',
M: '00',
Q: '11',
H: '10'
}, formatMaskPattern = '101010000010010', formatGeneratorPolynomial = '10100110111', versionGeneratorPolynomial = '1111100100101', paddingCodewords = [
'11101100',
'00010001'
], finderPatternValue = 93, maskPatternConditions = [
function (row, column) {
return (row + column) % 2 === 0;
},
function (row) {
return row % 2 === 0;
},
function (row, column) {
return column % 3 === 0;
},
function (row, column) {
return (row + column) % 3 === 0;
},
function (row, column) {
return (Math.floor(row / 2) + Math.floor(column / 3)) % 2 === 0;
},
function (row, column) {
return row * column % 2 + row * column % 3 === 0;
},
function (row, column) {
return (row * column % 2 + row * column % 3) % 2 === 0;
},
function (row, column) {
return ((row + column) % 2 + row * column % 3) % 2 === 0;
}
], numberRegex = /^\d+/, alphaPattern = 'A-Z0-9 $%*+./:-', alphaExclusiveSet = 'A-Z $%*+./:-', alphaRegex = new RegExp('^[' + alphaExclusiveSet + ']+'), alphaNumericRegex = new RegExp('^[' + alphaPattern + ']+'), byteRegex = new RegExp('^[^' + alphaPattern + ']+'), initMinNumericBeforeAlpha = 8, initMinNumericBeforeByte = 5, initMinAlphaBeforeByte = 8, minNumericBeforeAlpha = 17, minNumericBeforeByte = 9, minAlphaBeforeByte = 16, round = Math.round;
function toDecimal(value) {
return parseInt(value, 2);
}
function toBitsString(value, length) {
var result = Number(value).toString(2);
if (result.length < length) {
result = new Array(length - result.length + 1).join(0) + result;
}
return result;
}
function splitInto(str, n) {
var result = [], idx = 0;
while (idx < str.length) {
result.push(str.substring(idx, idx + n));
idx += n;
}
return result;
}
var QRDataMode = kendo.Class.extend({
getVersionIndex: function (version) {
if (version < 10) {
return 0;
} else if (version > 26) {
return 2;
}
return 1;
},
getBitsCharacterCount: function (version) {
var mode = this;
return mode.bitsInCharacterCount[mode.getVersionIndex(version || 40)];
},
getModeCountString: function (length, version) {
var mode = this;
return mode.modeIndicator + toBitsString(length, mode.getBitsCharacterCount(version));
},
encode: function () {
},
getStringBitsLength: function () {
},
getValue: function () {
},
modeIndicator: '',
bitsInCharacterCount: []
});
var modes = {};
modes[NUMERIC] = QRDataMode.extend({
bitsInCharacterCount: [
10,
12,
14
],
modeIndicator: '0001',
getValue: function (character) {
return parseInt(character, 10);
},
encode: function (str, version) {
var mode = this, parts = splitInto(str, 3), result = mode.getModeCountString(str.length, version);
for (var i = 0; i < parts.length - 1; i++) {
result += toBitsString(parts[i], 10);
}
return result + toBitsString(parts[i], 1 + 3 * parts[i].length);
},
getStringBitsLength: function (inputLength, version) {
var mod3 = inputLength % 3;
return 4 + this.getBitsCharacterCount(version) + 10 * Math.floor(inputLength / 3) + 3 * mod3 + (mod3 === 0 ? 0 : 1);
}
});
modes[ALPHA_NUMERIC] = QRDataMode.extend({
characters: {
'0': 0,
'1': 1,
'2': 2,
'3': 3,
'4': 4,
'5': 5,
'6': 6,
'7': 7,
'8': 8,
'9': 9,
'A': 10,
'B': 11,
'C': 12,
'D': 13,
'E': 14,
'F': 15,
'G': 16,
'H': 17,
'I': 18,
'J': 19,
'K': 20,
'L': 21,
'M': 22,
'N': 23,
'O': 24,
'P': 25,
'Q': 26,
'R': 27,
'S': 28,
'T': 29,
'U': 30,
'V': 31,
'W': 32,
'X': 33,
'Y': 34,
'Z': 35,
' ': 36,
'$': 37,
'%': 38,
'*': 39,
'+': 40,
'-': 41,
'.': 42,
'/': 43,
':': 44
},
bitsInCharacterCount: [
9,
11,
13
],
modeIndicator: '0010',
getValue: function (character) {
return this.characters[character];
},
encode: function (str, version) {
var mode = this, parts = splitInto(str, 2), result = mode.getModeCountString(str.length, version), value;
for (var i = 0; i < parts.length - 1; i++) {
value = 45 * mode.getValue(parts[i].charAt(0)) + mode.getValue(parts[i].charAt(1));
result += toBitsString(value, 11);
}
value = parts[i].length == 2 ? 45 * mode.getValue(parts[i].charAt(0)) + mode.getValue(parts[i].charAt(1)) : mode.getValue(parts[i].charAt(0));
return result + toBitsString(value, 1 + 5 * parts[i].length);
},
getStringBitsLength: function (inputLength, version) {
return 4 + this.getBitsCharacterCount(version) + 11 * Math.floor(inputLength / 2) + 6 * (inputLength % 2);
}
});
modes[BYTE] = QRDataMode.extend({
bitsInCharacterCount: [
8,
16,
16
],
modeIndicator: '0100',
getValue: function (character) {
var code = character.charCodeAt(0);
if (code <= 127 || 160 <= code && code <= 255) {
return code;
} else {
throw new Error('Unsupported character: ' + character);
}
},
encode: function (str, version) {
var mode = this, result = mode.getModeCountString(str.length, version);
for (var i = 0; i < str.length; i++) {
result += toBitsString(mode.getValue(str.charAt(i)), 8);
}
return result;
},
getStringBitsLength: function (inputLength, version) {
return 4 + this.getBitsCharacterCount(version) + 8 * inputLength;
}
});
var modeInstances = {};
for (var mode in modes) {
modeInstances[mode] = new modes[mode]();
}
var FreeCellVisitor = function (matrix) {
var that = this, row = matrix.length - 1, column = matrix.length - 1, startColumn = column, dir = -1, c = 0;
that.move = function () {
row += dir * c;
c ^= 1;
column = startColumn - c;
};
that.getNextCell = function () {
while (matrix[row][column] !== undefined) {
that.move();
if (row < 0 || row >= matrix.length) {
dir = -dir;
startColumn -= startColumn != 8 ? 2 : 3;
column = startColumn;
row = dir < 0 ? matrix.length - 1 : 0;
}
}
return {
row: row,
column: column
};
};
that.getNextRemainderCell = function () {
that.move();
if (matrix[row][column] === undefined) {
return {
row: row,
column: column
};
}
};
};
function fillFunctionCell(matrices, bit, x, y) {
for (var i = 0; i < matrices.length; i++) {
matrices[i][x][y] = bit;
}
}
function fillDataCell(matrices, bit, x, y) {
for (var i = 0; i < maskPatternConditions.length; i++) {
matrices[i][x][y] = maskPatternConditions[i](x, y) ? bit ^ 1 : parseInt(bit, 10);
}
}
var fillData = function (matrices, blocks) {
var cellVisitor = new FreeCellVisitor(matrices[0]), block, codewordIdx, cell;
for (var blockIdx = 0; blockIdx < blocks.length; blockIdx++) {
block = blocks[blockIdx];
codewordIdx = 0;
while (block.length > 0) {
for (var i = 0; i < block.length; i++) {
for (var j = 0; j < 8; j++) {
cell = cellVisitor.getNextCell();
fillDataCell(matrices, block[i][codewordIdx].charAt(j), cell.row, cell.column);
}
}
codewordIdx++;
while (block[0] && codewordIdx == block[0].length) {
block.splice(0, 1);
}
}
}
while (cell = cellVisitor.getNextRemainderCell()) {
fillDataCell(matrices, 0, cell.row, cell.column);
}
};
var padDataString = function (dataString, totalDataCodewords) {
var dataBitsCount = totalDataCodewords * 8, terminatorIndex = 0, paddingCodewordIndex = 0;
while (dataString.length < dataBitsCount && terminatorIndex < terminator.length) {
dataString += terminator.charAt(terminatorIndex++);
}
if (dataString.length % 8 !== 0) {
dataString += new Array(9 - dataString.length % 8).join('0');
}
while (dataString.length < dataBitsCount) {
dataString += paddingCodewords[paddingCodewordIndex];
paddingCodewordIndex ^= 1;
}
return dataString;
};
function generatePowersOfTwo() {
var result;
for (var power = 1; power < 255; power++) {
result = powersOfTwoResult[power - 1] * 2;
if (result > 255) {
result = result ^ 285;
}
powersOfTwoResult[power] = result;
powersOfTwo[result] = power;
}
result = powersOfTwoResult[power - 1] * 2 ^ 285;
powersOfTwoResult[power] = result;
powersOfTwoResult[-1] = 0;
}
var xorPolynomials = function (x, y) {
var result = [], idx = x.length - 2;
for (var i = idx; i >= 0; i--) {
result[i] = x[i] ^ y[i];
}
return result;
};
var multiplyPolynomials = function (x, y) {
var result = [];
for (var i = 0; i < x.length; i++) {
for (var j = 0; j < y.length; j++) {
if (result[i + j] === undefined) {
result[i + j] = (x[i] + (y[j] >= 0 ? y[j] : 0)) % 255;
} else {
result[i + j] = powersOfTwo[powersOfTwoResult[result[i + j]] ^ powersOfTwoResult[(x[i] + y[j]) % 255]];
}
}
}
return result;
};
function generateGeneratorPolynomials() {
var maxErrorCorrectionCodeWordsCount = 68;
for (var idx = 2; idx <= maxErrorCorrectionCodeWordsCount; idx++) {
var firstPolynomial = generatorPolynomials[idx - 1], secondPolynomial = [
idx,
0
];
generatorPolynomials[idx] = multiplyPolynomials(firstPolynomial, secondPolynomial);
}
}
generatePowersOfTwo();
generateGeneratorPolynomials();
function multiplyByConstant(polynomial, power) {
var result = [], idx = polynomial.length - 1;
do {
result[idx] = powersOfTwoResult[(polynomial[idx] + power) % 255];
idx--;
} while (polynomial[idx] !== undefined);
return result;
}
var generateErrorCodewords = function (data, errorCodewordsCount) {
var generator = generatorPolynomials[errorCodewordsCount - 1], result = new Array(errorCodewordsCount).concat(data), generatorPolynomial = new Array(result.length - generator.length).concat(generator), steps = data.length, errorCodewords = [], divisor, idx;
for (idx = 0; idx < steps; idx++) {
divisor = multiplyByConstant(generatorPolynomial, powersOfTwo[result[result.length - 1]]);
generatorPolynomial.splice(0, 1);
result = xorPolynomials(divisor, result);
}
for (idx = result.length - 1; idx >= 0; idx--) {
errorCodewords[errorCodewordsCount - 1 - idx] = toBitsString(result[idx], 8);
}
return errorCodewords;
};
var getBlocks = function (dataStream, versionCodewordsInformation) {
var codewordStart = 0, dataBlocks = [], errorBlocks = [], dataBlock, versionGroups = versionCodewordsInformation.groups, blockCodewordsCount, groupBlocksCount, messagePolynomial, codeword;
for (var groupIdx = 0; groupIdx < versionGroups.length; groupIdx++) {
groupBlocksCount = versionGroups[groupIdx][0];
for (var blockIdx = 0; blockIdx < groupBlocksCount; blockIdx++) {
blockCodewordsCount = versionGroups[groupIdx][1];
dataBlock = [];
messagePolynomial = [];
for (var codewordIdx = 1; codewordIdx <= blockCodewordsCount; codewordIdx++) {
codeword = dataStream.substring(codewordStart, codewordStart + 8);
dataBlock.push(codeword);
messagePolynomial[blockCodewordsCount - codewordIdx] = toDecimal(codeword);
codewordStart += 8;
}
dataBlocks.push(dataBlock);
errorBlocks.push(generateErrorCodewords(messagePolynomial, versionCodewordsInformation.errorCodewordsPerBlock));
}
}
return [
dataBlocks,
errorBlocks
];
};
var chooseMode = function (str, minNumericBeforeAlpha, minNumericBeforeByte, minAlphaBeforeByte, previousMode) {
var numeric = numberRegex.exec(str), numericMatch = numeric ? numeric[0] : '', alpha = alphaRegex.exec(str), alphaMatch = alpha ? alpha[0] : '', alphaNumeric = alphaNumericRegex.exec(str), alphaNumericMatch = alphaNumeric ? alphaNumeric[0] : '', mode, modeString;
if (numericMatch && (numericMatch.length >= minNumericBeforeAlpha || str.length == numericMatch.length || numericMatch.length >= minNumericBeforeByte && !alphaNumericRegex.test(str.charAt(numericMatch.length)))) {
mode = NUMERIC;
modeString = numericMatch;
} else if (alphaNumericMatch && (str.length == alphaNumericMatch.length || alphaNumericMatch.length >= minAlphaBeforeByte || previousMode == ALPHA_NUMERIC)) {
mode = ALPHA_NUMERIC;
modeString = numericMatch || alphaMatch;
} else {
mode = BYTE;
if (alphaNumericMatch) {
modeString = alphaNumericMatch + byteRegex.exec(str.substring(alphaNumericMatch.length))[0];
} else {
modeString = byteRegex.exec(str)[0];
}
}
return {
mode: mode,
modeString: modeString
};
};
var getModes = function (str) {
var modes = [], previousMode, idx = 0;
modes.push(chooseMode(str, initMinNumericBeforeAlpha, initMinNumericBeforeByte, initMinAlphaBeforeByte, previousMode));
previousMode = modes[0].mode;
str = str.substr(modes[0].modeString.length);
while (str.length > 0) {
var nextMode = chooseMode(str, minNumericBeforeAlpha, minNumericBeforeByte, minAlphaBeforeByte, previousMode);
if (nextMode.mode != previousMode) {
previousMode = nextMode.mode;
modes.push(nextMode);
idx++;
} else {
modes[idx].modeString += nextMode.modeString;
}
str = str.substr(nextMode.modeString.length);
}
return modes;
};
var getDataCodewordsCount = function (modes) {
var length = 0, mode;
for (var i = 0; i < modes.length; i++) {
mode = modeInstances[modes[i].mode];
length += mode.getStringBitsLength(modes[i].modeString.length);
}
return Math.ceil(length / 8);
};
var getVersion = function (dataCodewordsCount, errorCorrectionLevel) {
var x = 0, y = versionsCodewordsInformation.length - 1, version = Math.floor(versionsCodewordsInformation.length / 2);
do {
if (dataCodewordsCount < versionsCodewordsInformation[version][errorCorrectionLevel].totalDataCodewords) {
y = version;
} else {
x = version;
}
version = x + Math.floor((y - x) / 2);
} while (y - x > 1);
if (dataCodewordsCount <= versionsCodewordsInformation[x][errorCorrectionLevel].totalDataCodewords) {
return version + 1;
}
return y + 1;
};
var getDataString = function (modes, version) {
var dataString = '', mode;
for (var i = 0; i < modes.length; i++) {
mode = modeInstances[modes[i].mode];
dataString += mode.encode(modes[i].modeString, version);
}
return dataString;
};
var encodeFormatInformation = function (format) {
var formatNumber = toDecimal(format), encodedString, result = '';
if (formatNumber === 0) {
return '101010000010010';
} else {
encodedString = encodeBCH(toDecimal(format), formatGeneratorPolynomial, 15);
}
for (var i = 0; i < encodedString.length; i++) {
result += encodedString.charAt(i) ^ formatMaskPattern.charAt(i);
}
return result;
};
var encodeBCH = function (value, generatorPolynomial, codeLength) {
var generatorNumber = toDecimal(generatorPolynomial), polynomialLength = generatorPolynomial.length - 1, valueNumber = value << polynomialLength, length = codeLength - polynomialLength, valueString = toBitsString(value, length), result = dividePolynomials(valueNumber, generatorNumber);
result = valueString + toBitsString(result, polynomialLength);
return result;
};
var dividePolynomials = function (numberX, numberY) {
var yLength = numberY.toString(2).length, xLength = numberX.toString(2).length;
do {
numberX ^= numberY << xLength - yLength;
xLength = numberX.toString(2).length;
} while (xLength >= yLength);
return numberX;
};
function getNumberAt(str, idx) {
return parseInt(str.charAt(idx), 10);
}
var initMatrices = function (version) {
var matrices = [], modules = 17 + 4 * version;
for (var i = 0; i < maskPatternConditions.length; i++) {
matrices[i] = new Array(modules);
for (var j = 0; j < modules; j++) {
matrices[i][j] = new Array(modules);
}
}
return matrices;
};
var addFormatInformation = function (matrices, formatString) {
var matrix = matrices[0], x, y, idx = 0, length = formatString.length;
for (x = 0, y = 8; x <= 8; x++) {
if (x !== 6) {
fillFunctionCell(matrices, getNumberAt(formatString, length - 1 - idx++), x, y);
}
}
for (x = 8, y = 7; y >= 0; y--) {
if (y !== 6) {
fillFunctionCell(matrices, getNumberAt(formatString, length - 1 - idx++), x, y);
}
}
idx = 0;
for (y = matrix.length - 1, x = 8; y >= matrix.length - 8; y--) {
fillFunctionCell(matrices, getNumberAt(formatString, length - 1 - idx++), x, y);
}
fillFunctionCell(matrices, 1, matrix.length - 8, 8);
for (x = matrix.length - 7, y = 8; x < matrix.length; x++) {
fillFunctionCell(matrices, getNumberAt(formatString, length - 1 - idx++), x, y);
}
};
var encodeVersionInformation = function (version) {
return encodeBCH(version, versionGeneratorPolynomial, 18);
};
var addVersionInformation = function (matrices, dataString) {
var matrix = matrices[0], modules = matrix.length, x1 = 0, y1 = modules - 11, x2 = modules - 11, y2 = 0, quotient, mod, value;
for (var idx = 0; idx < dataString.length; idx++) {
quotient = Math.floor(idx / 3);
mod = idx % 3;
value = getNumberAt(dataString, dataString.length - idx - 1);
fillFunctionCell(matrices, value, x1 + quotient, y1 + mod);
fillFunctionCell(matrices, value, x2 + mod, y2 + quotient);
}
};
var addCentricPattern = function (matrices, pattern, x, y) {
var size = pattern.length + 2, length = pattern.length + 1, value;
for (var i = 0; i < pattern.length; i++) {
for (var j = i; j < size - i; j++) {
value = pattern[i];
fillFunctionCell(matrices, value, x + j, y + i);
fillFunctionCell(matrices, value, x + i, y + j);
fillFunctionCell(matrices, value, x + length - j, y + length - i);
fillFunctionCell(matrices, value, x + length - i, y + length - j);
}
}
};
var addFinderSeparator = function (matrices, direction, x, y) {
var nextX = x, nextY = y, matrix = matrices[0];
do {
fillFunctionCell(matrices, 0, nextX, y);
fillFunctionCell(matrices, 0, x, nextY);
nextX += direction[0];
nextY += direction[1];
} while (nextX >= 0 && nextX < matrix.length);
};
var addFinderPatterns = function (matrices) {
var modules = matrices[0].length;
addCentricPattern(matrices, finderPattern, 0, 0);
addFinderSeparator(matrices, [
-1,
-1
], 7, 7);
addCentricPattern(matrices, finderPattern, modules - 7, 0);
addFinderSeparator(matrices, [
1,
-1
], modules - 8, 7);
addCentricPattern(matrices, finderPattern, 0, modules - 7);
addFinderSeparator(matrices, [
-1,
1
], 7, modules - 8);
};
var addAlignmentPatterns = function (matrices, version) {
if (version < 2) {
return;
}
var matrix = matrices[0], modules = matrix.length, pointsCount = Math.floor(version / 7), points = [6], startDistance, distance, idx = 0;
if (startDistance = irregularAlignmentPatternsStartDistance[version]) {
distance = (modules - 13 - startDistance) / pointsCount;
} else {
startDistance = distance = (modules - 13) / (pointsCount + 1);
}
points.push(points[idx++] + startDistance);
while (points[idx] + distance < modules) {
points.push(points[idx++] + distance);
}
for (var i = 0; i < points.length; i++) {
for (var j = 0; j < points.length; j++) {
if (matrix[points[i]][points[j]] === undefined) {
addCentricPattern(matrices, alignmentPattern, points[i] - 2, points[j] - 2);
}
}
}
};
var addTimingFunctions = function (matrices) {
var row = 6, column = 6, value = 1, modules = matrices[0].length;
for (var i = 8; i < modules - 8; i++) {
fillFunctionCell(matrices, value, row, i);
fillFunctionCell(matrices, value, i, column);
value ^= 1;
}
};
var scoreMaskMatrixes = function (matrices) {
var scores = [], previousBits = [], darkModules = [], patterns = [], adjacentSameBits = [], matrix, i, row = 0, column = 1, modules = matrices[0].length;
for (i = 0; i < matrices.length; i++) {
scores[i] = 0;
darkModules[i] = 0;
adjacentSameBits[i] = [
0,
0
];
patterns[i] = [
0,
0
];
previousBits[i] = [];
}
for (i = 0; i < modules; i++) {
for (var j = 0; j < modules; j++) {
for (var k = 0; k < matrices.length; k++) {
matrix = matrices[k];
darkModules[k] += parseInt(matrix[i][j], 10);
if (previousBits[k][row] === matrix[i][j] && i + 1 < modules && j - 1 >= 0 && matrix[i + 1][j] == previousBits[k][row] && matrix[i + 1][j - 1] == previousBits[k][row]) {
scores[k] += 3;
}
scoreFinderPatternOccurance(k, patterns, scores, row, matrix[i][j]);
scoreFinderPatternOccurance(k, patterns, scores, column, matrix[j][i]);
scoreAdjacentSameBits(k, scores, previousBits, matrix[i][j], adjacentSameBits, row);
scoreAdjacentSameBits(k, scores, previousBits, matrix[j][i], adjacentSameBits, column);
}
}
}
var total = modules * modules, minIdx, min = Number.MAX_VALUE;
for (i = 0; i < scores.length; i++) {
scores[i] += calculateDarkModulesRatioScore(darkModules[i], total);
if (scores[i] < min) {
min = scores[i];
minIdx = i;
}
}
return minIdx;
};
function scoreFinderPatternOccurance(idx, patterns, scores, rowColumn, bit) {
patterns[idx][rowColumn] = (patterns[idx][rowColumn] << 1 ^ bit) % 128;
if (patterns[idx][rowColumn] == finderPatternValue) {
scores[idx] += 40;
}
}
function scoreAdjacentSameBits(idx, scores, previousBits, bit, adjacentBits, rowColumn) {
if (previousBits[idx][rowColumn] == bit) {
adjacentBits[idx][rowColumn]++;
} else {
previousBits[idx][rowColumn] = bit;
if (adjacentBits[idx][rowColumn] >= 5) {
scores[idx] += 3 + adjacentBits[idx][rowColumn] - 5;
}
adjacentBits[idx][rowColumn] = 1;
}
}
function calculateDarkModulesRatioScore(darkModules, total) {
var percent = Math.floor(darkModules / total * 100), mod5 = percent % 5, previous = Math.abs(percent - mod5 - 50), next = Math.abs(percent + 5 - mod5 - 50), score = 10 * Math.min(previous / 5, next / 5);
return score;
}
var EncodingResult = function (dataString, version) {
this.dataString = dataString;
this.version = version;
};
var IsoEncoder = function () {
this.getEncodingResult = function (inputString, errorCorrectionLevel) {
var modes = getModes(inputString), dataCodewordsCount = getDataCodewordsCount(modes), version = getVersion(dataCodewordsCount, errorCorrectionLevel), dataString = getDataString(modes, version);
return new EncodingResult(dataString, version);
};
};
var UTF8Encoder = function () {
this.mode = modeInstances[this.encodingMode];
};
UTF8Encoder.fn = UTF8Encoder.prototype = {
encodingMode: BYTE,
utfBOM: '111011111011101110111111',
initialModeCountStringLength: 20,
getEncodingResult: function (inputString, errorCorrectionLevel) {
var that = this, data = that.encode(inputString), dataCodewordsCount = that.getDataCodewordsCount(data), version = getVersion(dataCodewordsCount, errorCorrectionLevel), dataString = that.mode.getModeCountString(data.length / 8, version) + data;
return new EncodingResult(dataString, version);
},
getDataCodewordsCount: function (data) {
var that = this, dataLength = data.length, dataCodewordsCount = Math.ceil((that.initialModeCountStringLength + dataLength) / 8);
return dataCodewordsCount;
},
encode: function (str) {
var that = this, result = that.utfBOM;
for (var i = 0; i < str.length; i++) {
result += that.encodeCharacter(str.charCodeAt(i));
}
return result;
},
encodeCharacter: function (code) {
var bytesCount = this.getBytesCount(code), bc = bytesCount - 1, result = '';
if (bytesCount == 1) {
result = toBitsString(code, 8);
} else {
var significantOnes = 8 - bytesCount;
for (var i = 0; i < bc; i++) {
result = toBitsString(code >> i * 6 & 63 | 128, 8) + result;
}
result = (code >> bc * 6 | 255 >> significantOnes << significantOnes).toString(2) + result;
}
return result;
},
getBytesCount: function (code) {
var ranges = this.ranges;
for (var i = 0; i < ranges.length; i++) {
if (code < ranges[i]) {
return i + 1;
}
}
},
ranges: [
128,
2048,
65536,
2097152,
67108864
]
};
var QRCodeDataEncoder = function (encoding) {
if (encoding && encoding.toLowerCase().indexOf('utf_8') >= 0) {
return new UTF8Encoder();
} else {
return new IsoEncoder();
}
};
var encodeData = function (inputString, errorCorrectionLevel, encoding) {
var encoder = new QRCodeDataEncoder(encoding), encodingResult = encoder.getEncodingResult(inputString, errorCorrectionLevel), version = encodingResult.version, versionInformation = versionsCodewordsInformation[version - 1][errorCorrectionLevel], dataString = padDataString(encodingResult.dataString, versionInformation.totalDataCodewords), blocks = getBlocks(dataString, versionInformation), matrices = initMatrices(version);
addFinderPatterns(matrices);
addAlignmentPatterns(matrices, version);
addTimingFunctions(matrices);
if (version >= 7) {
addVersionInformation(matrices, toBitsString(0, 18));
}
addFormatInformation(matrices, toBitsString(0, 15));
fillData(matrices, blocks);
var minIdx = scoreMaskMatrixes(matrices), optimalMatrix = matrices[minIdx];
if (version >= 7) {
addVersionInformation([optimalMatrix], encodeVersionInformation(version));
}
var formatString = errorCorrectionPatterns[errorCorrectionLevel] + toBitsString(minIdx, 3);
addFormatInformation([optimalMatrix], encodeFormatInformation(formatString));
return optimalMatrix;
};
var QRCodeDefaults = {
DEFAULT_SIZE: 200,
QUIET_ZONE_LENGTH: 4,
DEFAULT_ERROR_CORRECTION_LEVEL: 'L',
DEFAULT_BACKGROUND: '#fff',
DEFAULT_DARK_MODULE_COLOR: '#000',
MIN_BASE_UNIT_SIZE: 1
};
var QRCode = Widget.extend({
init: function (element, options) {
var that = this;
Widget.fn.init.call(that, element, options);
that.element = $(element);
that.wrapper = that.element;
that.element.addClass('k-qrcode');
that.surfaceWrap = $('
').css('position', 'relative').appendTo(this.element);
that.surface = draw.Surface.create(that.surfaceWrap, { type: that.options.renderAs });
that.setOptions(options);
},
redraw: function () {
var size = this._getSize();
this.surfaceWrap.css({
width: size,
height: size
});
this.surface.clear();
this.createVisual();
this.surface.draw(this.visual);
},
getSize: function () {
return kendo.dimensions(this.element);
},
_resize: function () {
this.redraw();
},
createVisual: function () {
this.visual = this._render();
},
exportVisual: function () {
return this._render();
},
_render: function () {
var that = this, value = that._value, baseUnit, border = that.options.border || {}, padding = that.options.padding || 0, borderWidth = border.width || 0, quietZoneSize, matrix, size, dataSize, contentSize;
border.width = borderWidth;
var visual = new draw.Group();
if (value) {
matrix = encodeData(value, that.options.errorCorrection, that.options.encoding);
size = that._getSize();
contentSize = size - 2 * (borderWidth + padding);
baseUnit = that._calculateBaseUnit(contentSize, matrix.length);
dataSize = matrix.length * baseUnit;
quietZoneSize = borderWidth + padding + (contentSize - dataSize) / 2;
visual.append(that._renderBackground(size, border));
visual.append(that._renderMatrix(matrix, baseUnit, quietZoneSize));
}
return visual;
},
_getSize: function () {
var that = this, size;
if (that.options.size) {
size = parseInt(that.options.size, 10);
} else {
var element = that.element, min = Math.min(element.width(), element.height());
if (min > 0) {
size = min;
} else {
size = QRCodeDefaults.DEFAULT_SIZE;
}
}
return size;
},
_calculateBaseUnit: function (size, matrixSize) {
var baseUnit = Math.floor(size / matrixSize);
if (baseUnit < QRCodeDefaults.MIN_BASE_UNIT_SIZE) {
throw new Error('Insufficient size.');
}
if (baseUnit * matrixSize >= size && baseUnit - 1 >= QRCodeDefaults.MIN_BASE_UNIT_SIZE) {
baseUnit--;
}
return baseUnit;
},
_renderMatrix: function (matrix, baseUnit, quietZoneSize) {
var path = new draw.MultiPath({
fill: { color: this.options.color },
stroke: null
});
for (var row = 0; row < matrix.length; row++) {
var y = quietZoneSize + row * baseUnit;
var column = 0;
while (column < matrix.length) {
while (matrix[row][column] === 0 && column < matrix.length) {
column++;
}
if (column < matrix.length) {
var x = column;
while (matrix[row][column] == 1) {
column++;
}
var x1 = round(quietZoneSize + x * baseUnit);
var y1 = round(y);
var x2 = round(quietZoneSize + column * baseUnit);
var y2 = round(y + baseUnit);
path.moveTo(x1, y1).lineTo(x1, y2).lineTo(x2, y2).lineTo(x2, y1).close();
}
}
}
return path;
},
_renderBackground: function (size, border) {
var box = Box2D(0, 0, size, size).unpad(border.width / 2);
return draw.Path.fromRect(box.toRect(), {
fill: { color: this.options.background },
stroke: {
color: border.color,
width: border.width
}
});
},
setOptions: function (options) {
var that = this;
options = options || {};
that.options = extend(that.options, options);
if (options.value !== undefined) {
that._value = that.options.value + '';
}
that.redraw();
},
value: function (value) {
var that = this;
if (value === undefined) {
return that._value;
}
that._value = value + '';
that.redraw();
},
options: {
name: 'QRCode',
renderAs: 'svg',
encoding: 'ISO_8859_1',
value: '',
errorCorrection: QRCodeDefaults.DEFAULT_ERROR_CORRECTION_LEVEL,
background: QRCodeDefaults.DEFAULT_BACKGROUND,
color: QRCodeDefaults.DEFAULT_DARK_MODULE_COLOR,
size: '',
padding: 0,
border: {
color: '',
width: 0
}
}
});
dataviz.ExportMixin.extend(QRCode.fn);
dataviz.ui.plugin(QRCode);
kendo.deepExtend(dataviz, {
QRCode: QRCode,
QRCodeDefaults: QRCodeDefaults,
QRCodeFunctions: {
FreeCellVisitor: FreeCellVisitor,
fillData: fillData,
padDataString: padDataString,
generateErrorCodewords: generateErrorCodewords,
xorPolynomials: xorPolynomials,
getBlocks: getBlocks,
multiplyPolynomials: multiplyPolynomials,
chooseMode: chooseMode,
getModes: getModes,
getDataCodewordsCount: getDataCodewordsCount,
getVersion: getVersion,
getDataString: getDataString,
encodeFormatInformation: encodeFormatInformation,
encodeBCH: encodeBCH,
dividePolynomials: dividePolynomials,
initMatrices: initMatrices,
addFormatInformation: addFormatInformation,
encodeVersionInformation: encodeVersionInformation,
addVersionInformation: addVersionInformation,
addCentricPattern: addCentricPattern,
addFinderSeparator: addFinderSeparator,
addFinderPatterns: addFinderPatterns,
addAlignmentPatterns: addAlignmentPatterns,
addTimingFunctions: addTimingFunctions,
scoreMaskMatrixes: scoreMaskMatrixes,
encodeData: encodeData,
UTF8Encoder: UTF8Encoder
},
QRCodeFields: {
modes: modeInstances,
powersOfTwo: powersOfTwo,
powersOfTwoResult: powersOfTwoResult,
generatorPolynomials: generatorPolynomials
}
});
}(window.kendo.jQuery));
return window.kendo;
}, typeof define == 'function' && define.amd ? define : function (a1, a2, a3) {
(a3 || a2)();
}));
(function (f, define) {
define('kendo.dataviz.stock', ['kendo.dataviz.chart'], f);
}(function () {
var __meta__ = {
id: 'dataviz.stockchart',
name: 'StockChart',
category: 'dataviz',
description: 'StockChart widget and associated financial series.',
depends: ['dataviz.chart']
};
(function ($, undefined) {
var kendo = window.kendo, Class = kendo.Class, Observable = kendo.Observable, deepExtend = kendo.deepExtend, math = Math, proxy = $.proxy, util = kendo.util, last = util.last, renderTemplate = util.renderTemplate, dataviz = kendo.dataviz, defined = util.defined, filterSeriesByType = dataviz.filterSeriesByType, template = kendo.template, Chart = dataviz.ui.Chart, Selection = dataviz.Selection, addDuration = dataviz.addDuration, limitValue = util.limitValue, lteDateIndex = dataviz.lteDateIndex, toDate = dataviz.toDate, toTime = dataviz.toTime;
var AUTO_CATEGORY_WIDTH = 28, CHANGE = 'change', CSS_PREFIX = 'k-', DRAG = 'drag', DRAG_END = 'dragEnd', NAVIGATOR_PANE = '_navigator', NAVIGATOR_AXIS = NAVIGATOR_PANE, EQUALLY_SPACED_SERIES = dataviz.EQUALLY_SPACED_SERIES, ZOOM_ACCELERATION = 3, ZOOM = 'zoom', ZOOM_END = 'zoomEnd';
var StockChart = Chart.extend({
init: function (element, userOptions) {
$(element).addClass(CSS_PREFIX + 'chart');
Chart.fn.init.call(this, element, userOptions);
},
_applyDefaults: function (options, themeOptions) {
var chart = this, width = chart.element.width() || dataviz.DEFAULT_WIDTH;
var stockDefaults = {
seriesDefaults: { categoryField: options.dateField },
axisDefaults: {
categoryAxis: {
name: 'default',
majorGridLines: { visible: false },
labels: { step: 2 },
majorTicks: { visible: false },
maxDateGroups: math.floor(width / AUTO_CATEGORY_WIDTH)
}
}
};
if (themeOptions) {
themeOptions = deepExtend({}, themeOptions, stockDefaults);
}
if (!chart._navigator) {
Navigator.setup(options, themeOptions);
}
Chart.fn._applyDefaults.call(chart, options, themeOptions);
},
_initDataSource: function (userOptions) {
var options = userOptions || {}, dataSource = options.dataSource, hasServerFiltering = dataSource && dataSource.serverFiltering, mainAxis = [].concat(options.categoryAxis)[0], naviOptions = options.navigator || {}, select = naviOptions.select, hasSelect = select && select.from && select.to, filter, dummyAxis;
if (hasServerFiltering && hasSelect) {
filter = [].concat(dataSource.filter || []);
dummyAxis = new dataviz.DateCategoryAxis(deepExtend({ baseUnit: 'fit' }, mainAxis, {
categories: [
select.from,
select.to
]
}));
dataSource.filter = Navigator.buildFilter(dummyAxis.range().min, select.to).concat(filter);
}
Chart.fn._initDataSource.call(this, userOptions);
},
options: {
name: 'StockChart',
dateField: 'date',
axisDefaults: {
categoryAxis: {
type: 'date',
baseUnit: 'fit',
justified: true
},
valueAxis: {
narrowRange: true,
labels: { format: 'C' }
}
},
navigator: {
select: {},
seriesDefaults: {
markers: { visible: false },
tooltip: {
visible: true,
template: '#= kendo.toString(category, \'d\') #'
},
line: { width: 2 }
},
hint: {},
visible: true
},
tooltip: { visible: true },
legend: { visible: false }
},
_resize: function () {
var t = this.options.transitions;
this.options.transitions = false;
this._fullRedraw();
this.options.transitions = t;
},
_redraw: function () {
var chart = this, navigator = chart._navigator;
if (!this._dirty() && navigator && navigator.dataSource) {
navigator.redrawSlaves();
} else {
chart._fullRedraw();
}
},
_dirty: function () {
var options = this.options;
var series = [].concat(options.series, options.navigator.series);
var seriesCount = $.grep(series, function (s) {
return s && s.visible;
}).length;
var dirty = this._seriesCount !== seriesCount;
this._seriesCount = seriesCount;
return dirty;
},
_fullRedraw: function () {
var chart = this, navigator = chart._navigator;
if (!navigator) {
navigator = chart._navigator = new Navigator(chart);
}
navigator._setRange();
Chart.fn._redraw.call(chart);
navigator._initSelection();
},
_onDataChanged: function () {
var chart = this;
Chart.fn._onDataChanged.call(chart);
chart._dataBound = true;
},
_bindCategoryAxis: function (axis, data, axisIx) {
var chart = this, categoryAxes = chart.options.categoryAxis, axesLength = categoryAxes.length, currentAxis;
Chart.fn._bindCategoryAxis.apply(this, arguments);
if (axis.name === NAVIGATOR_AXIS) {
while (axisIx < axesLength) {
currentAxis = categoryAxes[axisIx++];
if (currentAxis.pane == NAVIGATOR_PANE) {
currentAxis.categories = axis.categories;
}
}
}
},
_trackSharedTooltip: function (coords) {
var chart = this, plotArea = chart._plotArea, pane = plotArea.paneByPoint(coords);
if (pane && pane.options.name === NAVIGATOR_PANE) {
chart._unsetActivePoint();
} else {
Chart.fn._trackSharedTooltip.call(chart, coords);
}
},
destroy: function () {
var chart = this;
chart._navigator.destroy();
Chart.fn.destroy.call(chart);
}
});
var Navigator = Observable.extend({
init: function (chart) {
var navi = this;
navi.chart = chart;
navi.options = deepExtend({}, navi.options, chart.options.navigator);
navi._initDataSource();
if (!defined(navi.options.hint.visible)) {
navi.options.hint.visible = navi.options.visible;
}
chart.bind(DRAG, proxy(navi._drag, navi));
chart.bind(DRAG_END, proxy(navi._dragEnd, navi));
chart.bind(ZOOM, proxy(navi._zoom, navi));
chart.bind(ZOOM_END, proxy(navi._zoomEnd, navi));
},
options: {},
_initDataSource: function () {
var navi = this, options = navi.options, autoBind = options.autoBind, dsOptions = options.dataSource;
if (!defined(autoBind)) {
autoBind = navi.chart.options.autoBind;
}
navi._dataChangedHandler = proxy(navi._onDataChanged, navi);
if (dsOptions) {
navi.dataSource = kendo.data.DataSource.create(dsOptions).bind(CHANGE, navi._dataChangedHandler);
if (autoBind) {
navi.dataSource.fetch();
}
}
},
_onDataChanged: function () {
var navi = this, chart = navi.chart, series = chart.options.series, seriesIx, seriesLength = series.length, categoryAxes = chart.options.categoryAxis, axisIx, axesLength = categoryAxes.length, data = navi.dataSource.view(), currentSeries, currentAxis, naviCategories;
for (seriesIx = 0; seriesIx < seriesLength; seriesIx++) {
currentSeries = series[seriesIx];
if (currentSeries.axis == NAVIGATOR_AXIS && chart._isBindable(currentSeries)) {
currentSeries.data = data;
}
}
for (axisIx = 0; axisIx < axesLength; axisIx++) {
currentAxis = categoryAxes[axisIx];
if (currentAxis.pane == NAVIGATOR_PANE) {
if (currentAxis.name == NAVIGATOR_AXIS) {
chart._bindCategoryAxis(currentAxis, data, axisIx);
naviCategories = currentAxis.categories;
} else {
currentAxis.categories = naviCategories;
}
}
}
if (chart._model) {
navi.redraw();
navi.filterAxes();
if (!chart.options.dataSource || chart.options.dataSource && chart._dataBound) {
navi.redrawSlaves();
}
}
},
destroy: function () {
var navi = this, dataSource = navi.dataSource;
if (dataSource) {
dataSource.unbind(CHANGE, navi._dataChangeHandler);
}
if (navi.selection) {
navi.selection.destroy();
}
},
redraw: function () {
this._redrawSelf();
this._initSelection();
},
_initSelection: function () {
var navi = this, chart = navi.chart, options = navi.options, axis = navi.mainAxis(), axisClone = clone(axis), range = axis.range(), min = range.min, max = range.max, groups = axis.options.categories, select = navi.options.select, selection = navi.selection, from = toDate(select.from), to = toDate(select.to);
if (groups.length === 0) {
return;
}
if (selection) {
selection.destroy();
selection.wrapper.remove();
}
axisClone.box = axis.box;
selection = navi.selection = new Selection(chart, axisClone, {
min: min,
max: max,
from: from,
to: to,
selectStart: $.proxy(navi._selectStart, navi),
select: $.proxy(navi._select, navi),
selectEnd: $.proxy(navi._selectEnd, navi),
mousewheel: { zoom: 'left' }
});
if (options.hint.visible) {
navi.hint = new NavigatorHint(chart.element, {
min: min,
max: max,
template: options.hint.template,
format: options.hint.format
});
}
},
_setRange: function () {
var plotArea = this.chart._createPlotArea(true);
var axis = plotArea.namedCategoryAxes[NAVIGATOR_AXIS];
var axisOpt = axis.options;
var range = axis.range();
var min = range.min;
var max = addDuration(range.max, axisOpt.baseUnitStep, axisOpt.baseUnit);
var select = this.options.select || {};
var from = toDate(select.from) || min;
if (from < min) {
from = min;
}
var to = toDate(select.to) || max;
if (to > max) {
to = max;
}
this.options.select = {
from: from,
to: to
};
this.filterAxes();
},
_redrawSelf: function (silent) {
var plotArea = this.chart._plotArea;
if (plotArea) {
plotArea.redraw(last(plotArea.panes), silent);
}
},
redrawSlaves: function () {
var navi = this, chart = navi.chart, plotArea = chart._plotArea, slavePanes = plotArea.panes.slice(0, -1);
plotArea.srcSeries = chart.options.series;
plotArea.redraw(slavePanes);
},
_drag: function (e) {
var navi = this, chart = navi.chart, coords = chart._eventCoordinates(e.originalEvent), navigatorAxis = navi.mainAxis(), naviRange = navigatorAxis.datesRange(), inNavigator = navigatorAxis.pane.box.containsPoint(coords), axis = chart._plotArea.categoryAxis, range = e.axisRanges[axis.options.name], select = navi.options.select, selection = navi.selection, duration, from, to;
if (!range || inNavigator || !selection) {
return;
}
if (select.from && select.to) {
duration = toTime(select.to) - toTime(select.from);
} else {
duration = toTime(selection.options.to) - toTime(selection.options.from);
}
from = toDate(limitValue(toTime(range.min), naviRange.min, toTime(naviRange.max) - duration));
to = toDate(limitValue(toTime(from) + duration, toTime(naviRange.min) + duration, naviRange.max));
navi.options.select = {
from: from,
to: to
};
if (navi._liveDrag()) {
navi.filterAxes();
navi.redrawSlaves();
}
selection.set(from, to);
navi.showHint(from, to);
},
_dragEnd: function () {
var navi = this;
navi.filterAxes();
navi.filterDataSource();
navi.redrawSlaves();
if (navi.hint) {
navi.hint.hide();
}
},
_liveDrag: function () {
var support = kendo.support, isTouch = support.touch, browser = support.browser, isFirefox = browser.mozilla, isOldIE = browser.msie && browser.version < 9;
return !isTouch && !isFirefox && !isOldIE;
},
readSelection: function () {
var navi = this, selection = navi.selection, src = selection.options, dst = navi.options.select;
dst.from = src.from;
dst.to = src.to;
},
filterAxes: function () {
var navi = this, select = navi.options.select || {}, chart = navi.chart, allAxes = chart.options.categoryAxis, from = select.from, to = select.to, i, axis;
for (i = 0; i < allAxes.length; i++) {
axis = allAxes[i];
if (axis.pane !== NAVIGATOR_PANE) {
axis.min = toDate(from);
axis.max = toDate(to);
}
}
},
filterDataSource: function () {
var navi = this, select = navi.options.select || {}, chart = navi.chart, chartDataSource = chart.dataSource, hasServerFiltering = chartDataSource && chartDataSource.options.serverFiltering, axisOptions;
if (navi.dataSource && hasServerFiltering) {
axisOptions = new dataviz.DateCategoryAxis(deepExtend({ baseUnit: 'fit' }, chart.options.categoryAxis[0], {
categories: [
select.from,
select.to
]
})).options;
chartDataSource.filter(Navigator.buildFilter(addDuration(axisOptions.min, -axisOptions.baseUnitStep, axisOptions.baseUnit), addDuration(axisOptions.max, axisOptions.baseUnitStep, axisOptions.baseUnit)));
}
},
_zoom: function (e) {
var navi = this, chart = navi.chart, delta = e.delta, axis = chart._plotArea.categoryAxis, select = navi.options.select, selection = navi.selection, categories = navi.mainAxis().options.categories, fromIx, toIx;
if (!selection) {
return;
}
fromIx = lteDateIndex(selection.options.from, categories);
toIx = lteDateIndex(selection.options.to, categories);
e.originalEvent.preventDefault();
if (math.abs(delta) > 1) {
delta *= ZOOM_ACCELERATION;
}
if (toIx - fromIx > 1) {
selection.expand(delta);
navi.readSelection();
} else {
axis.options.min = select.from;
select.from = axis.scaleRange(-e.delta).min;
}
if (!kendo.support.touch) {
navi.filterAxes();
navi.redrawSlaves();
}
selection.set(select.from, select.to);
navi.showHint(navi.options.select.from, navi.options.select.to);
},
_zoomEnd: function (e) {
this._dragEnd(e);
},
showHint: function (from, to) {
var navi = this, chart = navi.chart, plotArea = chart._plotArea;
if (navi.hint) {
navi.hint.show(from, to, plotArea.backgroundBox());
}
},
_selectStart: function (e) {
var chart = this.chart;
chart._selectStart.call(chart, e);
},
_select: function (e) {
var navi = this, chart = navi.chart;
navi.showHint(e.from, e.to);
chart._select.call(chart, e);
},
_selectEnd: function (e) {
var navi = this, chart = navi.chart;
if (navi.hint) {
navi.hint.hide();
}
navi.readSelection();
navi.filterAxes();
navi.filterDataSource();
navi.redrawSlaves();
chart._selectEnd.call(chart, e);
},
mainAxis: function () {
var plotArea = this.chart._plotArea;
if (plotArea) {
return plotArea.namedCategoryAxes[NAVIGATOR_AXIS];
}
}
});
Navigator.setup = function (options, themeOptions) {
options = options || {};
themeOptions = themeOptions || {};
var naviOptions = deepExtend({}, themeOptions.navigator, options.navigator), panes = options.panes = [].concat(options.panes), paneOptions = deepExtend({}, naviOptions.pane, { name: NAVIGATOR_PANE });
if (!naviOptions.visible) {
paneOptions.visible = false;
paneOptions.height = 0.1;
}
panes.push(paneOptions);
Navigator.attachAxes(options, naviOptions);
Navigator.attachSeries(options, naviOptions, themeOptions);
};
Navigator.attachAxes = function (options, naviOptions) {
var categoryAxes, valueAxes, series = naviOptions.series || [];
categoryAxes = options.categoryAxis = [].concat(options.categoryAxis);
valueAxes = options.valueAxis = [].concat(options.valueAxis);
var equallySpacedSeries = filterSeriesByType(series, EQUALLY_SPACED_SERIES);
var justifyAxis = equallySpacedSeries.length === 0;
var base = deepExtend({
type: 'date',
pane: NAVIGATOR_PANE,
roundToBaseUnit: !justifyAxis,
justified: justifyAxis,
_collapse: false,
majorTicks: { visible: true },
tooltip: { visible: false },
labels: { step: 1 },
autoBind: !naviOptions.dataSource,
autoBaseUnitSteps: {
minutes: [1],
hours: [
1,
2
],
days: [
1,
2
],
weeks: [],
months: [1],
years: [1]
},
_overlap: false
});
var user = naviOptions.categoryAxis;
categoryAxes.push(deepExtend({}, base, { maxDateGroups: 200 }, user, {
name: NAVIGATOR_AXIS,
baseUnit: 'fit',
baseUnitStep: 'auto',
labels: { visible: false },
majorTicks: { visible: false }
}), deepExtend({}, base, user, {
name: NAVIGATOR_AXIS + '_labels',
maxDateGroups: 20,
baseUnitStep: 'auto',
plotBands: [],
autoBaseUnitSteps: { minutes: [] }
}), deepExtend({}, base, user, {
name: NAVIGATOR_AXIS + '_ticks',
maxDateGroups: 200,
majorTicks: { width: 0.5 },
plotBands: [],
labels: {
visible: false,
mirror: true
}
}));
valueAxes.push(deepExtend({
name: NAVIGATOR_AXIS,
pane: NAVIGATOR_PANE,
majorGridLines: { visible: false },
visible: false
}, naviOptions.valueAxis));
};
Navigator.attachSeries = function (options, naviOptions, themeOptions) {
var series = options.series = options.series || [], navigatorSeries = [].concat(naviOptions.series || []), seriesColors = themeOptions.seriesColors, defaults = naviOptions.seriesDefaults, i;
for (i = 0; i < navigatorSeries.length; i++) {
series.push(deepExtend({
color: seriesColors[i % seriesColors.length],
categoryField: naviOptions.dateField,
visibleInLegend: false,
tooltip: { visible: false }
}, defaults, navigatorSeries[i], {
axis: NAVIGATOR_AXIS,
categoryAxis: NAVIGATOR_AXIS,
autoBind: !naviOptions.dataSource
}));
}
};
Navigator.buildFilter = function (from, to) {
return [
{
field: 'Date',
operator: 'gte',
value: toDate(from)
},
{
field: 'Date',
operator: 'lt',
value: toDate(to)
}
];
};
var NavigatorHint = Class.extend({
init: function (container, options) {
var hint = this;
hint.options = deepExtend({}, hint.options, options);
hint.container = container;
hint.chartPadding = {
top: parseInt(container.css('paddingTop'), 10),
left: parseInt(container.css('paddingLeft'), 10)
};
hint.template = hint.template;
if (!hint.template) {
hint.template = hint.template = renderTemplate('
');
}
hint.element = $(hint.template()).appendTo(container);
},
options: {
format: '{0:d} - {1:d}',
hideDelay: 500
},
show: function (from, to, bbox) {
var hint = this, middle = toDate(toTime(from) + toTime(to - from) / 2), options = hint.options, text = kendo.format(hint.options.format, from, to), tooltip = hint.element.find('.' + CSS_PREFIX + 'tooltip'), scroll = hint.element.find('.' + CSS_PREFIX + 'scroll'), scrollWidth = bbox.width() * 0.4, minPos = bbox.center().x - scrollWidth, maxPos = bbox.center().x, posRange = maxPos - minPos, range = options.max - options.min, scale = posRange / range, offset = middle - options.min, hintTemplate;
if (hint._hideTimeout) {
clearTimeout(hint._hideTimeout);
}
if (!hint._visible) {
hint.element.stop(false, true).css('visibility', 'hidden').show();
hint._visible = true;
}
if (options.template) {
hintTemplate = template(options.template);
text = hintTemplate({
from: from,
to: to
});
}
tooltip.html(text).css({
left: bbox.center().x - tooltip.outerWidth() / 2,
top: bbox.y1
});
scroll.css({
width: scrollWidth,
left: minPos + offset * scale,
top: bbox.y1 + parseInt(tooltip.css('margin-top'), 10) + parseInt(tooltip.css('border-top-width'), 10) + tooltip.height() / 2
});
hint.element.css('visibility', 'visible');
},
hide: function () {
var hint = this;
if (hint._hideTimeout) {
clearTimeout(hint._hideTimeout);
}
hint._hideTimeout = setTimeout(function () {
hint._visible = false;
hint.element.fadeOut('slow');
}, hint.options.hideDelay);
}
});
function ClonedObject() {
}
function clone(obj) {
ClonedObject.prototype = obj;
return new ClonedObject();
}
dataviz.ui.plugin(StockChart);
deepExtend(dataviz, { Navigator: Navigator });
}(window.kendo.jQuery));
return window.kendo;
}, typeof define == 'function' && define.amd ? define : function (a1, a2, a3) {
(a3 || a2)();
}));
(function (f, define) {
define('kendo.dataviz.sparkline', ['kendo.dataviz.chart'], f);
}(function () {
var __meta__ = {
id: 'dataviz.sparkline',
name: 'Sparkline',
category: 'dataviz',
description: 'Sparkline widgets.',
depends: ['dataviz.chart']
};
(function ($, undefined) {
var kendo = window.kendo, dataviz = kendo.dataviz, Chart = dataviz.ui.Chart, ObservableArray = kendo.data.ObservableArray, SharedTooltip = dataviz.SharedTooltip, deepExtend = kendo.deepExtend, isArray = $.isArray, proxy = $.proxy, inArray = dataviz.inArray, math = Math;
var CSS_PREFIX = 'k-', DEAULT_BAR_WIDTH = 150, DEAULT_BULLET_WIDTH = 150, BAR = 'bar', BULLET = 'bullet', PIE = 'pie', LEAVE = 'leave', NO_CROSSHAIR = [
BAR,
BULLET
];
var Sparkline = Chart.extend({
init: function (element, userOptions) {
var chart = this, stage = chart.stage = $('
'), options = userOptions || {};
element = $(element).addClass(CSS_PREFIX + 'sparkline').empty().append(stage);
chart._initialWidth = math.floor(element.width());
options = wrapNumber(options);
if (isArray(options) || options instanceof ObservableArray) {
options = { seriesDefaults: { data: options } };
}
if (!options.series) {
options.series = [{ data: wrapNumber(options.data) }];
}
deepExtend(options, { seriesDefaults: { type: options.type } });
if (inArray(options.series[0].type, NO_CROSSHAIR) || inArray(options.seriesDefaults.type, NO_CROSSHAIR)) {
options = deepExtend({}, { categoryAxis: { crosshair: { visible: false } } }, options);
}
Chart.fn.init.call(chart, element, options);
},
options: {
name: 'Sparkline',
chartArea: { margin: 2 },
axisDefaults: {
visible: false,
majorGridLines: { visible: false },
valueAxis: { narrowRange: true }
},
seriesDefaults: {
type: 'line',
area: { line: { width: 0.5 } },
bar: { stack: true },
padding: 2,
width: 0.5,
overlay: { gradient: null },
highlight: { visible: false },
border: { width: 0 },
markers: {
size: 2,
visible: false
}
},
tooltip: {
visible: true,
shared: true
},
categoryAxis: {
crosshair: {
visible: true,
tooltip: { visible: false }
}
},
legend: { visible: false },
transitions: false,
pointWidth: 5,
panes: [{ clip: false }]
},
_modelOptions: function () {
var chart = this, chartOptions = chart.options, options, width = chart._initialWidth, stage = chart.stage;
chart.stage.children().hide();
var space = $('
');
chart.stage.append(space);
options = deepExtend({
width: width ? width : chart._autoWidth(),
height: stage.height(),
transitions: chartOptions.transitions
}, chartOptions.chartArea, {
inline: true,
align: false
});
stage.css({
width: options.width,
height: options.height
});
space.remove();
chart.stage.children().show();
chart.surface.resize();
return options;
},
_createTooltip: function () {
var chart = this, options = chart.options, element = chart.element, tooltip;
if (chart._sharedTooltip()) {
tooltip = new SparklineSharedTooltip(element, chart._plotArea, options.tooltip);
} else {
tooltip = Chart.fn._createTooltip.call(chart);
}
tooltip.bind(LEAVE, proxy(chart._tooltipleave, chart));
return tooltip;
},
_surfaceWrap: function () {
return this.stage;
},
_autoWidth: function () {
var chart = this, options = chart.options, margin = dataviz.getSpacing(options.chartArea.margin), series = options.series, dsTotal = chart.dataSource.total(), seriesTotal = 0, width, i, currentSeries;
for (i = 0; i < series.length; i++) {
currentSeries = series[i];
if (currentSeries.type === BAR) {
return DEAULT_BAR_WIDTH;
}
if (currentSeries.type === BULLET) {
return DEAULT_BULLET_WIDTH;
}
if (currentSeries.type === PIE) {
return chart.stage.height();
}
if (currentSeries.data) {
seriesTotal = math.max(seriesTotal, currentSeries.data.length);
}
}
width = math.max(dsTotal, seriesTotal) * options.pointWidth;
if (width > 0) {
width += margin.left + margin.right;
}
return width;
}
});
var SparklineSharedTooltip = SharedTooltip.extend({
options: { animation: { duration: 0 } },
_anchor: function (point, slot) {
var anchor = SharedTooltip.fn._anchor.call(this, point, slot);
var size = this._measure();
anchor.y = -size.height - this.options.offset;
return anchor;
},
_hideElement: function () {
if (this.element) {
this.element.hide().remove();
}
}
});
function wrapNumber(x) {
return typeof x === 'number' ? [x] : x;
}
dataviz.ui.plugin(Sparkline);
deepExtend(dataviz, { SparklineSharedTooltip: SparklineSharedTooltip });
}(window.kendo.jQuery));
return window.kendo;
}, typeof define == 'function' && define.amd ? define : function (a1, a2, a3) {
(a3 || a2)();
}));
(function (f, define) {
define('dataviz/map/location', ['kendo.drawing'], f);
}(function () {
(function ($, undefined) {
var math = Math, abs = math.abs, atan = math.atan, atan2 = math.atan2, cos = math.cos, max = math.max, min = math.min, sin = math.sin, tan = math.tan, kendo = window.kendo, Class = kendo.Class, dataviz = kendo.dataviz, deepExtend = kendo.deepExtend, util = kendo.util, defined = util.defined, deg = util.deg, rad = util.rad, round = util.round, sqr = util.sqr, valueOrDefault = util.valueOrDefault;
var Location = Class.extend({
init: function (lat, lng) {
if (arguments.length === 1) {
this.lat = lat[0];
this.lng = lat[1];
} else {
this.lat = lat;
this.lng = lng;
}
},
DISTANCE_ITERATIONS: 100,
DISTANCE_CONVERGENCE: 1e-12,
DISTANCE_PRECISION: 2,
FORMAT: '{0:N6},{1:N6}',
toArray: function () {
return [
this.lat,
this.lng
];
},
equals: function (loc) {
return loc && loc.lat === this.lat && loc.lng === this.lng;
},
clone: function () {
return new Location(this.lat, this.lng);
},
round: function (precision) {
this.lng = round(this.lng, precision);
this.lat = round(this.lat, precision);
return this;
},
wrap: function () {
this.lng = this.lng % 180;
this.lat = this.lat % 90;
return this;
},
distanceTo: function (dest, datum) {
return this.greatCircleTo(dest, datum).distance;
},
destination: function (distance, bearing, datum) {
bearing = rad(bearing);
datum = datum || dataviz.map.datums.WGS84;
var fromLat = rad(this.lat);
var fromLng = rad(this.lng);
var dToR = distance / kendo.dataviz.map.datums.WGS84.a;
var lat = math.asin(sin(fromLat) * cos(dToR) + cos(fromLat) * sin(dToR) * cos(bearing));
var lng = fromLng + atan2(sin(bearing) * sin(dToR) * cos(fromLat), cos(dToR) - sin(fromLat) * sin(lat));
return new Location(deg(lat), deg(lng));
},
greatCircleTo: function (dest, datum) {
dest = Location.create(dest);
datum = datum || dataviz.map.datums.WGS84;
if (!dest || this.clone().round(8).equals(dest.clone().round(8))) {
return {
distance: 0,
azimuthFrom: 0,
azimuthTo: 0
};
}
var a = datum.a;
var b = datum.b;
var f = datum.f;
var L = rad(dest.lng - this.lng);
var U1 = atan((1 - f) * tan(rad(this.lat)));
var sinU1 = sin(U1);
var cosU1 = cos(U1);
var U2 = atan((1 - f) * tan(rad(dest.lat)));
var sinU2 = sin(U2);
var cosU2 = cos(U2);
var lambda = L;
var prevLambda;
var i = this.DISTANCE_ITERATIONS;
var converged = false;
var sinLambda;
var cosLambda;
var sino;
var cosA2;
var coso;
var cos2om;
var sigma;
while (!converged && i-- > 0) {
sinLambda = sin(lambda);
cosLambda = cos(lambda);
sino = math.sqrt(sqr(cosU2 * sinLambda) + sqr(cosU1 * sinU2 - sinU1 * cosU2 * cosLambda));
coso = sinU1 * sinU2 + cosU1 * cosU2 * cosLambda;
sigma = atan2(sino, coso);
var sinA = cosU1 * cosU2 * sinLambda / sino;
cosA2 = 1 - sqr(sinA);
cos2om = 0;
if (cosA2 !== 0) {
cos2om = coso - 2 * sinU1 * sinU2 / cosA2;
}
prevLambda = lambda;
var C = f / 16 * cosA2 * (4 + f * (4 - 3 * cosA2));
lambda = L + (1 - C) * f * sinA * (sigma + C * sino * (cos2om + C * coso * (-1 + 2 * sqr(cos2om))));
converged = abs(lambda - prevLambda) <= this.DISTANCE_CONVERGENCE;
}
var u2 = cosA2 * (sqr(a) - sqr(b)) / sqr(b);
var A = 1 + u2 / 16384 * (4096 + u2 * (-768 + u2 * (320 - 175 * u2)));
var B = u2 / 1024 * (256 + u2 * (-128 + u2 * (74 - 47 * u2)));
var deltao = B * sino * (cos2om + B / 4 * (coso * (-1 + 2 * sqr(cos2om)) - B / 6 * cos2om * (-3 + 4 * sqr(sino)) * (-3 + 4 * sqr(cos2om))));
var azimuthFrom = atan2(cosU2 * sinLambda, cosU1 * sinU2 - sinU1 * cosU2 * cosLambda);
var azimuthTo = atan2(cosU1 * sinLambda, -sinU1 * cosU2 + cosU1 * sinU2 * cosLambda);
return {
distance: round(b * A * (sigma - deltao), this.DISTANCE_PRECISION),
azimuthFrom: deg(azimuthFrom),
azimuthTo: deg(azimuthTo)
};
}
});
Location.fn.toString = function () {
return kendo.format(this.FORMAT, this.lat, this.lng);
};
Location.fromLngLat = function (ll) {
return new Location(ll[1], ll[0]);
};
Location.fromLatLng = function (ll) {
return new Location(ll[0], ll[1]);
};
Location.create = function (a, b) {
if (defined(a)) {
if (a instanceof Location) {
return a.clone();
} else if (arguments.length === 1 && a.length === 2) {
return Location.fromLatLng(a);
} else {
return new Location(a, b);
}
}
};
var Extent = Class.extend({
init: function (nw, se) {
nw = Location.create(nw);
se = Location.create(se);
if (nw.lng + 180 > se.lng + 180 && nw.lat + 90 < se.lat + 90) {
this.se = nw;
this.nw = se;
} else {
this.se = se;
this.nw = nw;
}
},
contains: function (loc) {
var nw = this.nw, se = this.se, lng = valueOrDefault(loc.lng, loc[1]), lat = valueOrDefault(loc.lat, loc[0]);
return loc && lng + 180 >= nw.lng + 180 && lng + 180 <= se.lng + 180 && lat + 90 >= se.lat + 90 && lat + 90 <= nw.lat + 90;
},
center: function () {
var nw = this.nw;
var se = this.se;
var lng = nw.lng + (se.lng - nw.lng) / 2;
var lat = nw.lat + (se.lat - nw.lat) / 2;
return new Location(lat, lng);
},
containsAny: function (locs) {
var result = false;
for (var i = 0; i < locs.length; i++) {
result = result || this.contains(locs[i]);
}
return result;
},
include: function (loc) {
var nw = this.nw, se = this.se, lng = valueOrDefault(loc.lng, loc[1]), lat = valueOrDefault(loc.lat, loc[0]);
nw.lng = min(nw.lng, lng);
nw.lat = max(nw.lat, lat);
se.lng = max(se.lng, lng);
se.lat = min(se.lat, lat);
},
includeAll: function (locs) {
for (var i = 0; i < locs.length; i++) {
this.include(locs[i]);
}
},
edges: function () {
var nw = this.nw, se = this.se;
return {
nw: this.nw,
ne: new Location(nw.lat, se.lng),
se: this.se,
sw: new Location(se.lat, nw.lng)
};
},
toArray: function () {
var nw = this.nw, se = this.se;
return [
nw,
new Location(nw.lat, se.lng),
se,
new Location(se.lat, nw.lng)
];
},
overlaps: function (extent) {
return this.containsAny(extent.toArray()) || extent.containsAny(this.toArray());
}
});
Extent.World = new Extent([
90,
-180
], [
-90,
180
]);
Extent.create = function (a, b) {
if (a instanceof Extent) {
return a;
} else if (a && b) {
return new Extent(a, b);
} else if (a && a.length === 4 && !b) {
return new Extent([
a[0],
a[1]
], [
a[2],
a[3]
]);
}
};
deepExtend(dataviz, {
map: {
Extent: Extent,
Location: Location
}
});
}(window.kendo.jQuery));
}, typeof define == 'function' && define.amd ? define : function (a1, a2, a3) {
(a3 || a2)();
}));
(function (f, define) {
define('dataviz/map/attribution', ['kendo.drawing'], f);
}(function () {
(function () {
var kendo = window.kendo, Widget = kendo.ui.Widget, template = kendo.template, valueOrDefault = kendo.util.valueOrDefault, defined = kendo.util.defined;
var Attribution = Widget.extend({
init: function (element, options) {
Widget.fn.init.call(this, element, options);
this._initOptions(options);
this.items = [];
this.element.addClass('k-widget k-attribution');
},
options: {
name: 'Attribution',
separator: ' | ',
itemTemplate: '#= text #'
},
filter: function (extent, zoom) {
this._extent = extent;
this._zoom = zoom;
this._render();
},
add: function (item) {
if (defined(item)) {
if (typeof item === 'string') {
item = { text: item };
}
this.items.push(item);
this._render();
}
},
remove: function (text) {
var result = [];
for (var i = 0; i < this.items.length; i++) {
var item = this.items[i];
if (item.text !== text) {
result.push(item);
}
}
this.items = result;
this._render();
},
clear: function () {
this.items = [];
this.element.empty();
},
_render: function () {
var result = [];
var itemTemplate = template(this.options.itemTemplate);
for (var i = 0; i < this.items.length; i++) {
var item = this.items[i];
var text = this._itemText(item);
if (text !== '') {
result.push(itemTemplate({ text: text }));
}
}
if (result.length > 0) {
this.element.empty().append(result.join(this.options.separator)).show();
} else {
this.element.hide();
}
},
_itemText: function (item) {
var text = '';
var inZoomLevel = this._inZoomLevel(item.minZoom, item.maxZoom);
var inArea = this._inArea(item.extent);
if (inZoomLevel && inArea) {
text += item.text;
}
return text;
},
_inZoomLevel: function (min, max) {
var result = true;
min = valueOrDefault(min, -Number.MAX_VALUE);
max = valueOrDefault(max, Number.MAX_VALUE);
result = this._zoom > min && this._zoom < max;
return result;
},
_inArea: function (area) {
var result = true;
if (area) {
result = area.contains(this._extent);
}
return result;
}
});
kendo.dataviz.ui.plugin(Attribution);
}(window.kendo.jQuery));
}, typeof define == 'function' && define.amd ? define : function (a1, a2, a3) {
(a3 || a2)();
}));
(function (f, define) {
define('dataviz/map/navigator', ['kendo.core'], f);
}(function () {
(function ($) {
var kendo = window.kendo;
var Widget = kendo.ui.Widget;
var keys = kendo.keys;
var proxy = $.proxy;
var NS = '.kendoNavigator';
function button(dir) {
return kendo.format('
', dir);
}
var BUTTONS = button('n') + button('e') + button('s') + button('w');
var Navigator = Widget.extend({
init: function (element, options) {
Widget.fn.init.call(this, element, options);
this._initOptions(options);
this.element.addClass('k-widget k-header k-shadow k-navigator').append(BUTTONS).on('click' + NS, '.k-button', proxy(this, '_click'));
var parentElement = this.element.parent().closest('[' + kendo.attr('role') + ']');
this._keyroot = parentElement.length > 0 ? parentElement : this.element;
this._tabindex(this._keyroot);
this._keydown = proxy(this._keydown, this);
this._keyroot.on('keydown', this._keydown);
},
options: {
name: 'Navigator',
panStep: 1
},
events: ['pan'],
dispose: function () {
this._keyroot.off('keydown', this._keydown);
},
_pan: function (x, y) {
var panStep = this.options.panStep;
this.trigger('pan', {
x: x * panStep,
y: y * panStep
});
},
_click: function (e) {
var x = 0;
var y = 0;
var button = $(e.currentTarget);
if (button.is('.k-navigator-n')) {
y = 1;
} else if (button.is('.k-navigator-s')) {
y = -1;
} else if (button.is('.k-navigator-e')) {
x = 1;
} else if (button.is('.k-navigator-w')) {
x = -1;
}
this._pan(x, y);
e.preventDefault();
},
_keydown: function (e) {
switch (e.which) {
case keys.UP:
this._pan(0, 1);
e.preventDefault();
break;
case keys.DOWN:
this._pan(0, -1);
e.preventDefault();
break;
case keys.RIGHT:
this._pan(1, 0);
e.preventDefault();
break;
case keys.LEFT:
this._pan(-1, 0);
e.preventDefault();
break;
}
}
});
kendo.dataviz.ui.plugin(Navigator);
}(window.kendo.jQuery));
}, typeof define == 'function' && define.amd ? define : function (a1, a2, a3) {
(a3 || a2)();
}));
(function (f, define) {
define('dataviz/map/zoom', ['kendo.core'], f);
}(function () {
(function ($) {
var kendo = window.kendo;
var Widget = kendo.ui.Widget;
var keys = kendo.keys;
var proxy = $.proxy;
function button(dir, symbol) {
return kendo.format('
', dir, symbol);
}
var NS = '.kendoZoomControl';
var BUTTONS = button('in', '+') + button('out', '-');
var PLUS = 187;
var MINUS = 189;
var FF_PLUS = 61;
var FF_MINUS = 173;
var ZoomControl = Widget.extend({
init: function (element, options) {
Widget.fn.init.call(this, element, options);
this._initOptions(options);
this.element.addClass('k-widget k-zoom-control k-button-wrap k-buttons-horizontal').append(BUTTONS).on('click' + NS, '.k-button', proxy(this, '_click'));
var parentElement = this.element.parent().closest('[' + kendo.attr('role') + ']');
this._keyroot = parentElement.length > 0 ? parentElement : this.element;
this._tabindex(this._keyroot);
this._keydown = proxy(this._keydown, this);
this._keyroot.on('keydown', this._keydown);
},
options: {
name: 'ZoomControl',
zoomStep: 1
},
events: ['change'],
_change: function (dir) {
var zoomStep = this.options.zoomStep;
this.trigger('change', { delta: dir * zoomStep });
},
_click: function (e) {
var button = $(e.currentTarget);
var dir = 1;
if (button.is('.k-zoom-out')) {
dir = -1;
}
this._change(dir);
e.preventDefault();
},
_keydown: function (e) {
switch (e.which) {
case keys.NUMPAD_PLUS:
case PLUS:
case FF_PLUS:
this._change(1);
break;
case keys.NUMPAD_MINUS:
case MINUS:
case FF_MINUS:
this._change(-1);
break;
}
}
});
kendo.dataviz.ui.plugin(ZoomControl);
}(window.kendo.jQuery));
}, typeof define == 'function' && define.amd ? define : function (a1, a2, a3) {
(a3 || a2)();
}));
(function (f, define) {
define('dataviz/map/crs', [
'dataviz/map/location',
'kendo.drawing'
], f);
}(function () {
(function ($, undefined) {
var math = Math, atan = math.atan, exp = math.exp, pow = math.pow, sin = math.sin, log = math.log, tan = math.tan, kendo = window.kendo, Class = kendo.Class, dataviz = kendo.dataviz, deepExtend = kendo.deepExtend, g = kendo.geometry, Point = g.Point, map = dataviz.map, Location = map.Location, util = kendo.util, rad = util.rad, deg = util.deg, limit = util.limitValue;
var PI = math.PI, PI_DIV_2 = PI / 2, PI_DIV_4 = PI / 4, DEG_TO_RAD = PI / 180;
var WGS84 = {
a: 6378137,
b: 6356752.314245179,
f: 0.0033528106647474805,
e: 0.08181919084262149
};
var Mercator = Class.extend({
init: function (options) {
this._initOptions(options);
},
MAX_LNG: 180,
MAX_LAT: 85.0840590501,
INVERSE_ITERATIONS: 15,
INVERSE_CONVERGENCE: 1e-12,
options: {
centralMeridian: 0,
datum: WGS84
},
forward: function (loc, clamp) {
var proj = this, options = proj.options, datum = options.datum, r = datum.a, lng0 = options.centralMeridian, lat = limit(loc.lat, -proj.MAX_LAT, proj.MAX_LAT), lng = clamp ? limit(loc.lng, -proj.MAX_LNG, proj.MAX_LNG) : loc.lng, x = rad(lng - lng0) * r, y = proj._projectLat(lat);
return new Point(x, y);
},
_projectLat: function (lat) {
var datum = this.options.datum, ecc = datum.e, r = datum.a, y = rad(lat), ts = tan(PI_DIV_4 + y / 2), con = ecc * sin(y), p = pow((1 - con) / (1 + con), ecc / 2);
return r * log(ts * p);
},
inverse: function (point, clamp) {
var proj = this, options = proj.options, datum = options.datum, r = datum.a, lng0 = options.centralMeridian, lng = point.x / (DEG_TO_RAD * r) + lng0, lat = limit(proj._inverseY(point.y), -proj.MAX_LAT, proj.MAX_LAT);
if (clamp) {
lng = limit(lng, -proj.MAX_LNG, proj.MAX_LNG);
}
return new Location(lat, lng);
},
_inverseY: function (y) {
var proj = this, datum = proj.options.datum, r = datum.a, ecc = datum.e, ecch = ecc / 2, ts = exp(-y / r), phi = PI_DIV_2 - 2 * atan(ts), i;
for (i = 0; i <= proj.INVERSE_ITERATIONS; i++) {
var con = ecc * sin(phi), p = pow((1 - con) / (1 + con), ecch), dphi = PI_DIV_2 - 2 * atan(ts * p) - phi;
phi += dphi;
if (math.abs(dphi) <= proj.INVERSE_CONVERGENCE) {
break;
}
}
return deg(phi);
}
});
var SphericalMercator = Mercator.extend({
MAX_LAT: 85.0511287798,
_projectLat: function (lat) {
var r = this.options.datum.a, y = rad(lat), ts = tan(PI_DIV_4 + y / 2);
return r * log(ts);
},
_inverseY: function (y) {
var r = this.options.datum.a, ts = exp(-y / r);
return deg(PI_DIV_2 - 2 * atan(ts));
}
});
var Equirectangular = Class.extend({
forward: function (loc) {
return new Point(loc.lng, loc.lat);
},
inverse: function (point) {
return new Location(point.y, point.x);
}
});
var EPSG3857 = Class.extend({
init: function () {
var crs = this, proj = crs._proj = new SphericalMercator();
var c = this.c = 2 * PI * proj.options.datum.a;
this._tm = g.transform().translate(0.5, 0.5).scale(1 / c, -1 / c);
this._itm = g.transform().scale(c, -c).translate(-0.5, -0.5);
},
toPoint: function (loc, scale, clamp) {
var point = this._proj.forward(loc, clamp);
return point.transform(this._tm).scale(scale || 1);
},
toLocation: function (point, scale, clamp) {
point = point.clone().scale(1 / (scale || 1)).transform(this._itm);
return this._proj.inverse(point, clamp);
}
});
var EPSG3395 = Class.extend({
init: function () {
this._proj = new Mercator();
},
toPoint: function (loc) {
return this._proj.forward(loc);
},
toLocation: function (point) {
return this._proj.inverse(point);
}
});
var EPSG4326 = Class.extend({
init: function () {
this._proj = new Equirectangular();
},
toPoint: function (loc) {
return this._proj.forward(loc);
},
toLocation: function (point) {
return this._proj.inverse(point);
}
});
deepExtend(dataviz, {
map: {
crs: {
EPSG3395: EPSG3395,
EPSG3857: EPSG3857,
EPSG4326: EPSG4326
},
datums: { WGS84: WGS84 },
projections: {
Equirectangular: Equirectangular,
Mercator: Mercator,
SphericalMercator: SphericalMercator
}
}
});
}(window.kendo.jQuery));
}, typeof define == 'function' && define.amd ? define : function (a1, a2, a3) {
(a3 || a2)();
}));
(function (f, define) {
define('dataviz/map/layers/base', [
'kendo.core',
'dataviz/map/location'
], f);
}(function () {
(function ($, undefined) {
var proxy = $.proxy, kendo = window.kendo, Class = kendo.Class, dataviz = kendo.dataviz, deepExtend = kendo.deepExtend, Extent = dataviz.map.Extent, util = kendo.util, defined = util.defined;
var Layer = Class.extend({
init: function (map, options) {
this._initOptions(options);
this.map = map;
this.element = $('
').css({
'zIndex': this.options.zIndex,
'opacity': this.options.opacity
}).appendTo(map.scrollElement);
this._beforeReset = proxy(this._beforeReset, this);
this._reset = proxy(this._reset, this);
this._resize = proxy(this._resize, this);
this._panEnd = proxy(this._panEnd, this);
this._activate();
this._updateAttribution();
},
destroy: function () {
this._deactivate();
},
show: function () {
this.reset();
this._activate();
this._applyExtent(true);
},
hide: function () {
this._deactivate();
this._setVisibility(false);
},
reset: function () {
this._beforeReset();
this._reset();
},
_reset: function () {
this._applyExtent();
},
_beforeReset: $.noop,
_resize: $.noop,
_panEnd: function () {
this._applyExtent();
},
_applyExtent: function () {
var options = this.options;
var zoom = this.map.zoom();
var matchMinZoom = !defined(options.minZoom) || zoom >= options.minZoom;
var matchMaxZoom = !defined(options.maxZoom) || zoom <= options.maxZoom;
var extent = Extent.create(options.extent);
var inside = !extent || extent.overlaps(this.map.extent());
this._setVisibility(matchMinZoom && matchMaxZoom && inside);
},
_setVisibility: function (visible) {
this.element.css('display', visible ? '' : 'none');
},
_activate: function () {
var map = this.map;
map.bind('beforeReset', this._beforeReset);
map.bind('reset', this._reset);
map.bind('resize', this._resize);
map.bind('panEnd', this._panEnd);
},
_deactivate: function () {
var map = this.map;
map.unbind('beforeReset', this._beforeReset);
map.unbind('reset', this._reset);
map.unbind('resize', this._resize);
map.unbind('panEnd', this._panEnd);
},
_updateAttribution: function () {
var attr = this.map.attribution;
if (attr) {
attr.add(this.options.attribution);
}
}
});
deepExtend(dataviz, { map: { layers: { Layer: Layer } } });
}(window.kendo.jQuery));
}, typeof define == 'function' && define.amd ? define : function (a1, a2, a3) {
(a3 || a2)();
}));
(function (f, define) {
define('dataviz/map/layers/shape', [
'dataviz/map/layers/base',
'dataviz/map/location'
], f);
}(function () {
(function ($, undefined) {
var proxy = $.proxy, kendo = window.kendo, Class = kendo.Class, DataSource = kendo.data.DataSource, dataviz = kendo.dataviz, deepExtend = kendo.deepExtend, last = kendo.util.last, defined = kendo.util.defined, g = kendo.geometry, d = kendo.drawing, Group = d.Group, map = dataviz.map, Location = map.Location, Layer = map.layers.Layer;
var ShapeLayer = Layer.extend({
init: function (map, options) {
Layer.fn.init.call(this, map, options);
this.surface = d.Surface.create(this.element, {
width: map.scrollElement.width(),
height: map.scrollElement.height()
});
this._initRoot();
this.movable = new kendo.ui.Movable(this.surface.element);
this._markers = [];
this._click = this._handler('shapeClick');
this.surface.bind('click', this._click);
this._mouseenter = this._handler('shapeMouseEnter');
this.surface.bind('mouseenter', this._mouseenter);
this._mouseleave = this._handler('shapeMouseLeave');
this.surface.bind('mouseleave', this._mouseleave);
this._initDataSource();
},
options: { autoBind: true },
destroy: function () {
Layer.fn.destroy.call(this);
this.surface.destroy();
this.dataSource.unbind('change', this._dataChange);
},
setDataSource: function (dataSource) {
if (this.dataSource) {
this.dataSource.unbind('change', this._dataChange);
}
this.dataSource = kendo.data.DataSource.create(dataSource);
this.dataSource.bind('change', this._dataChange);
if (this.options.autoBind) {
this.dataSource.fetch();
}
},
_reset: function () {
Layer.fn._reset.call(this);
this._translateSurface();
if (this._data) {
this._load(this._data);
}
},
_initRoot: function () {
this._root = new Group();
this.surface.draw(this._root);
},
_beforeReset: function () {
this.surface.clear();
this._initRoot();
},
_resize: function () {
this.surface.size(this.map.size());
},
_initDataSource: function () {
var dsOptions = this.options.dataSource;
this._dataChange = proxy(this._dataChange, this);
this.dataSource = DataSource.create(dsOptions).bind('change', this._dataChange);
if (dsOptions && this.options.autoBind) {
this.dataSource.fetch();
}
},
_dataChange: function (e) {
this._data = e.sender.view();
this._load(this._data);
},
_load: function (data) {
this._clearMarkers();
if (!this._loader) {
this._loader = new GeoJSONLoader(this.map, this.options.style, this);
}
var container = new Group();
for (var i = 0; i < data.length; i++) {
var shape = this._loader.parse(data[i]);
if (shape) {
container.append(shape);
}
}
this._root.clear();
this._root.append(container);
},
shapeCreated: function (shape) {
var cancelled = false;
if (shape instanceof d.Circle) {
cancelled = defined(this._createMarker(shape));
}
if (!cancelled) {
var args = {
layer: this,
shape: shape
};
cancelled = this.map.trigger('shapeCreated', args);
}
return cancelled;
},
_createMarker: function (shape) {
var marker = this.map.markers.bind({ location: shape.location }, shape.dataItem);
if (marker) {
this._markers.push(marker);
}
return marker;
},
_clearMarkers: function () {
for (var i = 0; i < this._markers.length; i++) {
this.map.markers.remove(this._markers[i]);
}
this._markers = [];
},
_panEnd: function (e) {
Layer.fn._panEnd.call(this, e);
this._translateSurface();
},
_translateSurface: function () {
var map = this.map;
var nw = map.locationToView(map.extent().nw);
if (this.surface.translate) {
this.surface.translate(nw);
this.movable.moveTo({
x: nw.x,
y: nw.y
});
}
},
_handler: function (event) {
var layer = this;
return function (e) {
if (e.element) {
var args = {
layer: layer,
shape: e.element,
originalEvent: e.originalEvent
};
layer.map.trigger(event, args);
}
};
}
});
var GeoJSONLoader = Class.extend({
init: function (locator, defaultStyle, observer) {
this.observer = observer;
this.locator = locator;
this.style = defaultStyle;
},
parse: function (item) {
var root = new Group();
if (item.type === 'Feature') {
this._loadGeometryTo(root, item.geometry, item);
} else {
this._loadGeometryTo(root, item, item);
}
if (root.children.length < 2) {
root = root.children[0];
}
return root;
},
_shapeCreated: function (shape) {
var cancelled = false;
if (this.observer && this.observer.shapeCreated) {
cancelled = this.observer.shapeCreated(shape);
}
return cancelled;
},
_loadGeometryTo: function (container, geometry, dataItem) {
var coords = geometry.coordinates;
var i;
var path;
switch (geometry.type) {
case 'LineString':
path = this._loadPolygon(container, [coords], dataItem);
this._setLineFill(path);
break;
case 'MultiLineString':
for (i = 0; i < coords.length; i++) {
path = this._loadPolygon(container, [coords[i]], dataItem);
this._setLineFill(path);
}
break;
case 'Polygon':
this._loadPolygon(container, coords, dataItem);
break;
case 'MultiPolygon':
for (i = 0; i < coords.length; i++) {
this._loadPolygon(container, coords[i], dataItem);
}
break;
case 'Point':
this._loadPoint(container, coords, dataItem);
break;
case 'MultiPoint':
for (i = 0; i < coords.length; i++) {
this._loadPoint(container, coords[i], dataItem);
}
break;
}
},
_setLineFill: function (path) {
var segments = path.segments;
if (segments.length < 4 || !segments[0].anchor().equals(last(segments).anchor())) {
path.options.fill = null;
}
},
_loadShape: function (container, shape) {
if (!this._shapeCreated(shape)) {
container.append(shape);
}
return shape;
},
_loadPolygon: function (container, rings, dataItem) {
var shape = this._buildPolygon(rings);
shape.dataItem = dataItem;
return this._loadShape(container, shape);
},
_buildPolygon: function (rings) {
var type = rings.length > 1 ? d.MultiPath : d.Path;
var path = new type(this.style);
for (var i = 0; i < rings.length; i++) {
for (var j = 0; j < rings[i].length; j++) {
var point = this.locator.locationToView(Location.fromLngLat(rings[i][j]));
if (j === 0) {
path.moveTo(point.x, point.y);
} else {
path.lineTo(point.x, point.y);
}
}
}
return path;
},
_loadPoint: function (container, coords, dataItem) {
var location = Location.fromLngLat(coords);
var point = this.locator.locationToView(location);
var circle = new g.Circle(point, 10);
var shape = new d.Circle(circle, this.style);
shape.dataItem = dataItem;
shape.location = location;
return this._loadShape(container, shape);
}
});
deepExtend(kendo.data, {
schemas: {
geojson: {
type: 'json',
data: function (data) {
if (data.type === 'FeatureCollection') {
return data.features;
}
if (data.type === 'GeometryCollection') {
return data.geometries;
}
return data;
}
}
},
transports: { geojson: { read: { dataType: 'json' } } }
});
deepExtend(dataviz, {
map: {
layers: {
shape: ShapeLayer,
ShapeLayer: ShapeLayer
},
GeoJSONLoader: GeoJSONLoader
}
});
}(window.kendo.jQuery));
}, typeof define == 'function' && define.amd ? define : function (a1, a2, a3) {
(a3 || a2)();
}));
(function (f, define) {
define('dataviz/map/layers/bubble', ['dataviz/map/layers/shape'], f);
}(function () {
(function ($, undefined) {
var kendo = window.kendo, getter = kendo.getter, dataviz = kendo.dataviz, deepExtend = kendo.deepExtend, util = kendo.util, defined = util.defined, g = kendo.geometry, d = kendo.drawing, map = dataviz.map, Location = map.Location, ShapeLayer = map.layers.ShapeLayer;
var BubbleLayer = ShapeLayer.extend({
options: {
autoBind: true,
locationField: 'location',
valueField: 'value',
minSize: 0,
maxSize: 100,
scale: 'sqrt',
symbol: 'circle'
},
_load: function (data) {
this.surface.clear();
if (data.length === 0) {
return;
}
var opt = this.options;
var getValue = getter(opt.valueField);
data = data.slice(0);
data.sort(function (a, b) {
return getValue(b) - getValue(a);
});
var scaleType = this._scaleType();
var scale;
for (var i = 0; i < data.length; i++) {
var dataItem = data[i];
var location = getter(opt.locationField)(dataItem);
var value = getter(opt.valueField)(dataItem);
if (defined(location) && defined(value)) {
if (!scale) {
scale = new scaleType([
0,
value
], [
opt.minSize,
opt.maxSize
]);
}
location = Location.create(location);
var center = this.map.locationToView(location);
var size = scale.map(value);
var symbol = this._createSymbol({
center: center,
size: size,
style: opt.style,
dataItem: dataItem,
location: location
});
symbol.dataItem = dataItem;
symbol.location = location;
symbol.value = value;
this._drawSymbol(symbol);
}
}
},
_scaleType: function () {
var scale = this.options.scale;
if (kendo.isFunction(scale)) {
return scale;
}
return dataviz.map.scales[scale];
},
_createSymbol: function (args) {
var symbol = this.options.symbol;
if (!kendo.isFunction(symbol)) {
symbol = dataviz.map.symbols[symbol];
}
return symbol(args);
},
_drawSymbol: function (shape) {
var args = {
layer: this,
shape: shape
};
var cancelled = this.map.trigger('shapeCreated', args);
if (!cancelled) {
this.surface.draw(shape);
}
}
});
var SqrtScale = kendo.Class.extend({
init: function (domain, range) {
this._domain = domain;
this._range = range;
var domainRange = Math.sqrt(domain[1]) - Math.sqrt(domain[0]);
var outputRange = range[1] - range[0];
this._ratio = outputRange / domainRange;
},
map: function (value) {
var rel = (Math.sqrt(value) - Math.sqrt(this._domain[0])) * this._ratio;
return this._range[0] + rel;
}
});
var Symbols = {
circle: function (args) {
var geo = new g.Circle(args.center, args.size / 2);
return new d.Circle(geo, args.style);
},
square: function (args) {
var path = new d.Path(args.style);
var halfSize = args.size / 2;
var center = args.center;
path.moveTo(center.x - halfSize, center.y - halfSize).lineTo(center.x + halfSize, center.y - halfSize).lineTo(center.x + halfSize, center.y + halfSize).lineTo(center.x - halfSize, center.y + halfSize).close();
return path;
}
};
deepExtend(dataviz, {
map: {
layers: {
bubble: BubbleLayer,
BubbleLayer: BubbleLayer
},
scales: { sqrt: SqrtScale },
symbols: Symbols
}
});
}(window.kendo.jQuery));
}, typeof define == 'function' && define.amd ? define : function (a1, a2, a3) {
(a3 || a2)();
}));
(function (f, define) {
define('dataviz/map/layers/tile', [
'dataviz/map/layers/base',
'dataviz/map/location'
], f);
}(function () {
(function ($, undefined) {
var math = Math, proxy = $.proxy, kendo = window.kendo, Class = kendo.Class, template = kendo.template, dataviz = kendo.dataviz, deepExtend = kendo.deepExtend, g = kendo.geometry, Point = g.Point, Layer = dataviz.map.layers.Layer, util = kendo.util, round = util.round, renderSize = util.renderSize, limit = util.limitValue;
var TileLayer = Layer.extend({
init: function (map, options) {
Layer.fn.init.call(this, map, options);
if (typeof this.options.subdomains === 'string') {
this.options.subdomains = this.options.subdomains.split('');
}
var viewType = this._viewType();
this._view = new viewType(this.element, this.options);
},
destroy: function () {
Layer.fn.destroy.call(this);
this._view.destroy();
this._view = null;
},
_beforeReset: function () {
var map = this.map;
var origin = map.locationToLayer(map.extent().nw).round();
this._view.viewOrigin(origin);
},
_reset: function () {
Layer.fn._reset.call(this);
this._updateView();
this._view.reset();
},
_viewType: function () {
return TileView;
},
_activate: function () {
Layer.fn._activate.call(this);
if (!kendo.support.mobileOS) {
if (!this._pan) {
this._pan = kendo.throttle(proxy(this._render, this), 100);
}
this.map.bind('pan', this._pan);
}
},
_deactivate: function () {
Layer.fn._deactivate.call(this);
if (this._pan) {
this.map.unbind('pan', this._pan);
}
},
_updateView: function () {
var view = this._view, map = this.map, extent = map.extent(), extentToPoint = {
nw: map.locationToLayer(extent.nw).round(),
se: map.locationToLayer(extent.se).round()
};
view.center(map.locationToLayer(map.center()));
view.extent(extentToPoint);
view.zoom(map.zoom());
},
_resize: function () {
this._render();
},
_panEnd: function (e) {
Layer.fn._panEnd.call(this, e);
this._render();
},
_render: function () {
this._updateView();
this._view.render();
}
});
var TileView = Class.extend({
init: function (element, options) {
this.element = element;
this._initOptions(options);
this.pool = new TilePool();
},
options: {
tileSize: 256,
subdomains: [
'a',
'b',
'c'
],
urlTemplate: ''
},
center: function (center) {
this._center = center;
},
extent: function (extent) {
this._extent = extent;
},
viewOrigin: function (origin) {
this._viewOrigin = origin;
},
zoom: function (zoom) {
this._zoom = zoom;
},
pointToTileIndex: function (point) {
return new Point(math.floor(point.x / this.options.tileSize), math.floor(point.y / this.options.tileSize));
},
tileCount: function () {
var size = this.size(), firstTileIndex = this.pointToTileIndex(this._extent.nw), nw = this._extent.nw, point = this.indexToPoint(firstTileIndex).translate(-nw.x, -nw.y);
return {
x: math.ceil((math.abs(point.x) + size.width) / this.options.tileSize),
y: math.ceil((math.abs(point.y) + size.height) / this.options.tileSize)
};
},
size: function () {
var nw = this._extent.nw, se = this._extent.se, diff = se.clone().translate(-nw.x, -nw.y);
return {
width: diff.x,
height: diff.y
};
},
indexToPoint: function (index) {
var x = index.x, y = index.y;
return new Point(x * this.options.tileSize, y * this.options.tileSize);
},
subdomainText: function () {
var subdomains = this.options.subdomains;
return subdomains[this.subdomainIndex++ % subdomains.length];
},
destroy: function () {
this.element.empty();
this.pool.empty();
},
reset: function () {
this.pool.reset();
this.subdomainIndex = 0;
this.render();
},
render: function () {
var size = this.tileCount(), firstTileIndex = this.pointToTileIndex(this._extent.nw), tile, x, y;
for (x = 0; x < size.x; x++) {
for (y = 0; y < size.y; y++) {
tile = this.createTile({
x: firstTileIndex.x + x,
y: firstTileIndex.y + y
});
if (!tile.visible) {
tile.show();
}
}
}
},
createTile: function (currentIndex) {
var options = this.tileOptions(currentIndex);
var tile = this.pool.get(this._center, options);
if (tile.element.parent().length === 0) {
this.element.append(tile.element);
}
return tile;
},
tileOptions: function (currentIndex) {
var index = this.wrapIndex(currentIndex), point = this.indexToPoint(currentIndex), origin = this._viewOrigin, offset = point.clone().translate(-origin.x, -origin.y);
return {
index: index,
currentIndex: currentIndex,
point: point,
offset: roundPoint(offset),
zoom: this._zoom,
size: this.options.tileSize,
subdomain: this.subdomainText(),
urlTemplate: this.options.urlTemplate,
errorUrlTemplate: this.options.errorUrlTemplate
};
},
wrapIndex: function (index) {
var boundary = math.pow(2, this._zoom);
return {
x: this.wrapValue(index.x, boundary),
y: limit(index.y, 0, boundary - 1)
};
},
wrapValue: function (value, boundary) {
var remainder = math.abs(value) % boundary;
if (value >= 0) {
value = remainder;
} else {
value = boundary - (remainder === 0 ? boundary : remainder);
}
return value;
}
});
var ImageTile = Class.extend({
init: function (id, options) {
this.id = id;
this.visible = true;
this._initOptions(options);
this.createElement();
this.show();
},
options: {
urlTemplate: '',
errorUrlTemplate: ''
},
createElement: function () {
this.element = $('
![]()
').css({
width: this.options.size,
height: this.options.size
}).error(proxy(function (e) {
if (this.errorUrl()) {
e.target.setAttribute('src', this.errorUrl());
} else {
e.target.removeAttribute('src');
}
}, this));
},
show: function () {
var element = this.element[0];
element.style.top = renderSize(this.options.offset.y);
element.style.left = renderSize(this.options.offset.x);
var url = this.url();
if (url) {
element.setAttribute('src', url);
}
element.style.visibility = 'visible';
this.visible = true;
},
hide: function () {
this.element[0].style.visibility = 'hidden';
this.visible = false;
},
url: function () {
var urlResult = template(this.options.urlTemplate);
return urlResult(this.urlOptions());
},
errorUrl: function () {
var urlResult = template(this.options.errorUrlTemplate);
return urlResult(this.urlOptions());
},
urlOptions: function () {
var options = this.options;
return {
zoom: options.zoom,
subdomain: options.subdomain,
z: options.zoom,
x: options.index.x,
y: options.index.y,
s: options.subdomain,
quadkey: options.quadkey,
q: options.quadkey,
culture: options.culture,
c: options.culture
};
},
destroy: function () {
if (this.element) {
this.element.remove();
this.element = null;
}
}
});
var TilePool = Class.extend({
init: function () {
this._items = [];
},
options: { maxSize: 100 },
get: function (center, options) {
if (this._items.length >= this.options.maxSize) {
this._remove(center);
}
return this._create(options);
},
empty: function () {
var items = this._items;
for (var i = 0; i < items.length; i++) {
items[i].destroy();
}
this._items = [];
},
reset: function () {
var items = this._items;
for (var i = 0; i < items.length; i++) {
items[i].hide();
}
},
_create: function (options) {
var items = this._items;
var tile;
var id = util.hashKey(options.point.toString() + options.offset.toString() + options.zoom + options.urlTemplate);
for (var i = 0; i < items.length; i++) {
if (items[i].id === id) {
tile = items[i];
break;
}
}
if (tile) {
tile.show();
} else {
tile = new ImageTile(id, options);
this._items.push(tile);
}
return tile;
},
_remove: function (center) {
var items = this._items;
var maxDist = -1;
var index = -1;
for (var i = 0; i < items.length; i++) {
var dist = items[i].options.point.distanceTo(center);
if (dist > maxDist && !items[i].visible) {
index = i;
maxDist = dist;
}
}
if (index !== -1) {
items[index].destroy();
items.splice(index, 1);
}
}
});
function roundPoint(point) {
return new Point(round(point.x), round(point.y));
}
deepExtend(dataviz, {
map: {
layers: {
tile: TileLayer,
TileLayer: TileLayer,
ImageTile: ImageTile,
TilePool: TilePool,
TileView: TileView
}
}
});
}(window.kendo.jQuery));
}, typeof define == 'function' && define.amd ? define : function (a1, a2, a3) {
(a3 || a2)();
}));
(function (f, define) {
define('dataviz/map/layers/bing', ['dataviz/map/layers/tile'], f);
}(function () {
(function ($, undefined) {
var kendo = window.kendo, dataviz = kendo.dataviz, deepExtend = kendo.deepExtend, defined = kendo.util.defined, Extent = dataviz.map.Extent, Location = dataviz.map.Location, TileLayer = dataviz.map.layers.TileLayer, TileView = dataviz.map.layers.TileView;
var BingLayer = TileLayer.extend({
init: function (map, options) {
TileLayer.fn.init.call(this, map, options);
this._onMetadata = $.proxy(this._onMetadata, this);
this._fetchMetadata();
},
options: {
baseUrl: '//dev.virtualearth.net/REST/v1/Imagery/Metadata/',
imagerySet: 'road'
},
_fetchMetadata: function () {
var options = this.options;
if (!options.key) {
throw new Error('Bing tile layer: API key is required');
}
$.ajax({
url: options.baseUrl + options.imagerySet,
data: {
output: 'json',
include: 'ImageryProviders',
key: options.key,
uriScheme: this._scheme(window.location.protocol)
},
type: 'get',
dataType: 'jsonp',
jsonp: 'jsonp',
success: this._onMetadata
});
},
_scheme: function (proto) {
return proto.replace(':', '') === 'https' ? 'https' : 'http';
},
_onMetadata: function (data) {
if (data && data.resourceSets.length) {
var resource = this.resource = data.resourceSets[0].resources[0];
deepExtend(this._view.options, {
urlTemplate: resource.imageUrl.replace('{subdomain}', '#= subdomain #').replace('{quadkey}', '#= quadkey #').replace('{culture}', '#= culture #'),
subdomains: resource.imageUrlSubdomains
});
var options = this.options;
if (!defined(options.minZoom)) {
options.minZoom = resource.zoomMin;
}
if (!defined(options.maxZoom)) {
options.maxZoom = resource.zoomMax;
}
this._addAttribution();
if (this.element.css('display') !== 'none') {
this._reset();
}
}
},
_viewType: function () {
return BingView;
},
_addAttribution: function () {
var attr = this.map.attribution;
if (attr) {
var items = this.resource.imageryProviders;
if (items) {
for (var i = 0; i < items.length; i++) {
var item = items[i];
for (var y = 0; y < item.coverageAreas.length; y++) {
var area = item.coverageAreas[y];
attr.add({
text: item.attribution,
minZoom: area.zoomMin,
maxZoom: area.zoomMax,
extent: new Extent(new Location(area.bbox[2], area.bbox[1]), new Location(area.bbox[0], area.bbox[3]))
});
}
}
}
}
},
imagerySet: function (value) {
if (value) {
this.options.imagerySet = value;
this.map.attribution.clear();
this._fetchMetadata();
} else {
return this.options.imagerySet;
}
}
});
var BingView = TileView.extend({
options: { culture: 'en-US' },
tileOptions: function (currentIndex) {
var options = TileView.fn.tileOptions.call(this, currentIndex);
options.culture = this.options.culture;
options.quadkey = this.tileQuadKey(this.wrapIndex(currentIndex));
return options;
},
tileQuadKey: function (index) {
var quadKey = '', digit, mask, i;
for (i = this._zoom; i > 0; i--) {
digit = 0;
mask = 1 << i - 1;
if ((index.x & mask) !== 0) {
digit++;
}
if ((index.y & mask) !== 0) {
digit += 2;
}
quadKey += digit;
}
return quadKey;
}
});
deepExtend(dataviz, {
map: {
layers: {
bing: BingLayer,
BingLayer: BingLayer,
BingView: BingView
}
}
});
}(window.kendo.jQuery));
}, typeof define == 'function' && define.amd ? define : function (a1, a2, a3) {
(a3 || a2)();
}));
(function (f, define) {
define('dataviz/map/layers/marker', [
'dataviz/map/layers/base',
'dataviz/map/location',
'kendo.data',
'kendo.tooltip'
], f);
}(function () {
(function ($, undefined) {
var doc = document, math = Math, indexOf = $.inArray, proxy = $.proxy, kendo = window.kendo, Class = kendo.Class, DataSource = kendo.data.DataSource, Tooltip = kendo.ui.Tooltip, dataviz = kendo.dataviz, deepExtend = kendo.deepExtend, map = dataviz.map, Location = map.Location, Layer = map.layers.Layer;
var MarkerLayer = Layer.extend({
init: function (map, options) {
Layer.fn.init.call(this, map, options);
this._markerClick = proxy(this._markerClick, this);
this.element.on('click', '.k-marker', this._markerClick);
this.items = [];
this._initDataSource();
},
destroy: function () {
Layer.fn.destroy.call(this);
this.element.off('click', '.k-marker', this._markerClick);
this.dataSource.unbind('change', this._dataChange);
this.clear();
},
options: {
zIndex: 1000,
autoBind: true,
dataSource: {},
locationField: 'location',
titleField: 'title'
},
add: function (arg) {
if ($.isArray(arg)) {
for (var i = 0; i < arg.length; i++) {
this._addOne(arg[i]);
}
} else {
return this._addOne(arg);
}
},
remove: function (marker) {
marker.destroy();
var index = indexOf(marker, this.items);
if (index > -1) {
this.items.splice(index, 1);
}
},
clear: function () {
for (var i = 0; i < this.items.length; i++) {
this.items[i].destroy();
}
this.items = [];
},
update: function (marker) {
var loc = marker.location();
if (loc) {
marker.showAt(this.map.locationToView(loc));
var args = {
marker: marker,
layer: this
};
this.map.trigger('markerActivate', args);
}
},
_reset: function () {
Layer.fn._reset.call(this);
var items = this.items;
for (var i = 0; i < items.length; i++) {
this.update(items[i]);
}
},
bind: function (options, dataItem) {
var marker = map.Marker.create(options, this.options);
marker.dataItem = dataItem;
var args = {
marker: marker,
layer: this
};
var cancelled = this.map.trigger('markerCreated', args);
if (!cancelled) {
this.add(marker);
return marker;
}
},
setDataSource: function (dataSource) {
if (this.dataSource) {
this.dataSource.unbind('change', this._dataChange);
}
this.dataSource = kendo.data.DataSource.create(dataSource);
this.dataSource.bind('change', this._dataChange);
if (this.options.autoBind) {
this.dataSource.fetch();
}
},
_addOne: function (arg) {
var marker = Marker.create(arg, this.options);
marker.addTo(this);
return marker;
},
_initDataSource: function () {
var dsOptions = this.options.dataSource;
this._dataChange = proxy(this._dataChange, this);
this.dataSource = DataSource.create(dsOptions).bind('change', this._dataChange);
if (dsOptions && this.options.autoBind) {
this.dataSource.fetch();
}
},
_dataChange: function (e) {
this._load(e.sender.view());
},
_load: function (data) {
this._data = data;
this.clear();
var getLocation = kendo.getter(this.options.locationField);
var getTitle = kendo.getter(this.options.titleField);
for (var i = 0; i < data.length; i++) {
var dataItem = data[i];
this.bind({
location: getLocation(dataItem),
title: getTitle(dataItem)
}, dataItem);
}
},
_markerClick: function (e) {
var args = {
marker: $(e.target).data('kendoMarker'),
layer: this
};
this.map.trigger('markerClick', args);
}
});
var Marker = Class.extend({
init: function (options) {
this.options = options || {};
},
addTo: function (parent) {
this.layer = parent.markers || parent;
this.layer.items.push(this);
this.layer.update(this);
},
location: function (value) {
if (value) {
this.options.location = Location.create(value).toArray();
if (this.layer) {
this.layer.update(this);
}
return this;
} else {
return Location.create(this.options.location);
}
},
showAt: function (point) {
this.render();
this.element.css({
left: math.round(point.x),
top: math.round(point.y)
});
if (this.tooltip && this.tooltip.popup) {
this.tooltip.popup._position();
}
},
hide: function () {
if (this.element) {
this.element.remove();
this.element = null;
}
if (this.tooltip) {
this.tooltip.destroy();
this.tooltip = null;
}
},
destroy: function () {
this.layer = null;
this.hide();
},
render: function () {
if (!this.element) {
var options = this.options;
var layer = this.layer;
this.element = $(doc.createElement('span')).addClass('k-marker k-marker-' + kendo.toHyphens(options.shape || 'pin')).attr('title', options.title).attr(options.attributes || {}).data('kendoMarker', this).css('zIndex', options.zIndex);
if (layer) {
layer.element.append(this.element);
}
this.renderTooltip();
}
},
renderTooltip: function () {
var marker = this;
var title = marker.options.title;
var options = marker.options.tooltip || {};
if (options && Tooltip) {
var template = options.template;
if (template) {
var contentTemplate = kendo.template(template);
options.content = function (e) {
e.location = marker.location();
e.marker = marker;
return contentTemplate(e);
};
}
if (title || options.content || options.contentUrl) {
this.tooltip = new Tooltip(this.element, options);
this.tooltip.marker = this;
}
}
}
});
Marker.create = function (arg, defaults) {
if (arg instanceof Marker) {
return arg;
}
return new Marker(deepExtend({}, defaults, arg));
};
deepExtend(dataviz, {
map: {
layers: {
marker: MarkerLayer,
MarkerLayer: MarkerLayer
},
Marker: Marker
}
});
}(window.kendo.jQuery));
}, typeof define == 'function' && define.amd ? define : function (a1, a2, a3) {
(a3 || a2)();
}));
(function (f, define) {
define('dataviz/map/main', [
'dataviz/map/crs',
'dataviz/map/location'
], f);
}(function () {
(function ($, undefined) {
var doc = document, math = Math, min = math.min, pow = math.pow, proxy = $.proxy, kendo = window.kendo, Widget = kendo.ui.Widget, deepExtend = kendo.deepExtend, dataviz = kendo.dataviz, ui = dataviz.ui, g = kendo.geometry, Point = g.Point, map = dataviz.map, Extent = map.Extent, Location = map.Location, EPSG3857 = map.crs.EPSG3857, util = kendo.util, defined = util.defined, limit = util.limitValue, renderPos = util.renderPos, valueOrDefault = util.valueOrDefault;
var CSS_PREFIX = 'k-', FRICTION = 0.9, FRICTION_MOBILE = 0.93, MOUSEWHEEL = 'DOMMouseScroll mousewheel', VELOCITY_MULTIPLIER = 5;
var Map = Widget.extend({
init: function (element, options) {
kendo.destroy(element);
Widget.fn.init.call(this, element);
this._initOptions(options);
this.bind(this.events, options);
this.crs = new EPSG3857();
this.element.addClass(CSS_PREFIX + this.options.name.toLowerCase()).css('position', 'relative').empty().append(doc.createElement('div'));
this._viewOrigin = this._getOrigin();
this._initScroller();
this._initMarkers();
this._initControls();
this._initLayers();
this._reset();
this._mousewheel = proxy(this._mousewheel, this);
this.element.bind('click', proxy(this._click, this));
this.element.bind(MOUSEWHEEL, this._mousewheel);
},
options: {
name: 'Map',
controls: {
attribution: true,
navigator: { panStep: 100 },
zoom: true
},
layers: [],
layerDefaults: {
shape: {
style: {
fill: { color: '#fff' },
stroke: {
color: '#aaa',
width: 0.5
}
}
},
bubble: {
style: {
fill: {
color: '#fff',
opacity: 0.5
},
stroke: {
color: '#aaa',
width: 0.5
}
}
},
marker: {
shape: 'pinTarget',
tooltip: { position: 'top' }
}
},
center: [
0,
0
],
zoom: 3,
minSize: 256,
minZoom: 1,
maxZoom: 19,
markers: [],
markerDefaults: {
shape: 'pinTarget',
tooltip: { position: 'top' }
},
wraparound: true
},
events: [
'beforeReset',
'click',
'reset',
'pan',
'panEnd',
'markerActivate',
'markerClick',
'markerCreated',
'shapeClick',
'shapeCreated',
'shapeMouseEnter',
'shapeMouseLeave',
'zoomStart',
'zoomEnd'
],
destroy: function () {
this.scroller.destroy();
if (this.navigator) {
this.navigator.destroy();
}
if (this.attribution) {
this.attribution.destroy();
}
if (this.zoomControl) {
this.zoomControl.destroy();
}
this.markers.destroy();
for (var i = 0; i < this.layers.length; i++) {
this.layers[i].destroy();
}
Widget.fn.destroy.call(this);
},
zoom: function (level) {
var options = this.options;
if (defined(level)) {
level = math.round(limit(level, options.minZoom, options.maxZoom));
if (options.zoom !== level) {
options.zoom = level;
this._reset();
}
return this;
} else {
return options.zoom;
}
},
center: function (center) {
if (center) {
this.options.center = Location.create(center).toArray();
this._reset();
return this;
} else {
return Location.create(this.options.center);
}
},
extent: function (extent) {
if (extent) {
this._setExtent(extent);
return this;
} else {
return this._getExtent();
}
},
setOptions: function (options) {
Widget.fn.setOptions.call(this, options);
this._reset();
},
locationToLayer: function (location, zoom) {
var clamp = !this.options.wraparound;
location = Location.create(location);
return this.crs.toPoint(location, this._layerSize(zoom), clamp);
},
layerToLocation: function (point, zoom) {
var clamp = !this.options.wraparound;
point = Point.create(point);
return this.crs.toLocation(point, this._layerSize(zoom), clamp);
},
locationToView: function (location) {
location = Location.create(location);
var origin = this.locationToLayer(this._viewOrigin);
var point = this.locationToLayer(location);
return point.translateWith(origin.scale(-1));
},
viewToLocation: function (point, zoom) {
var origin = this.locationToLayer(this._getOrigin(), zoom);
point = Point.create(point);
point = point.clone().translateWith(origin);
return this.layerToLocation(point, zoom);
},
eventOffset: function (e) {
var offset = this.element.offset();
var event = e.originalEvent || e;
var x = valueOrDefault(event.pageX, event.clientX) - offset.left;
var y = valueOrDefault(event.pageY, event.clientY) - offset.top;
return new g.Point(x, y);
},
eventToView: function (e) {
var cursor = this.eventOffset(e);
return this.locationToView(this.viewToLocation(cursor));
},
eventToLayer: function (e) {
return this.locationToLayer(this.eventToLocation(e));
},
eventToLocation: function (e) {
var cursor = this.eventOffset(e);
return this.viewToLocation(cursor);
},
viewSize: function () {
var element = this.element;
var scale = this._layerSize();
var width = element.width();
if (!this.options.wraparound) {
width = min(scale, width);
}
return {
width: width,
height: min(scale, element.height())
};
},
exportVisual: function () {
this._reset();
return false;
},
_setOrigin: function (origin, zoom) {
var size = this.viewSize(), topLeft;
origin = this._origin = Location.create(origin);
topLeft = this.locationToLayer(origin, zoom);
topLeft.x += size.width / 2;
topLeft.y += size.height / 2;
this.options.center = this.layerToLocation(topLeft, zoom).toArray();
return this;
},
_getOrigin: function (invalidate) {
var size = this.viewSize(), topLeft;
if (invalidate || !this._origin) {
topLeft = this.locationToLayer(this.center());
topLeft.x -= size.width / 2;
topLeft.y -= size.height / 2;
this._origin = this.layerToLocation(topLeft);
}
return this._origin;
},
_setExtent: function (extent) {
extent = Extent.create(extent);
this.center(extent.center());
var width = this.element.width();
var height = this.element.height();
for (var zoom = this.options.maxZoom; zoom >= this.options.minZoom; zoom--) {
var nw = this.locationToLayer(extent.nw, zoom);
var se = this.locationToLayer(extent.se, zoom);
var layerWidth = math.abs(se.x - nw.x);
var layerHeight = math.abs(se.y - nw.y);
if (layerWidth <= width && layerHeight <= height) {
break;
}
}
this.zoom(zoom);
},
_getExtent: function () {
var nw = this._getOrigin();
var bottomRight = this.locationToLayer(nw);
var size = this.viewSize();
bottomRight.x += size.width;
bottomRight.y += size.height;
var se = this.layerToLocation(bottomRight);
return new Extent(nw, se);
},
_zoomAround: function (pivot, level) {
this._setOrigin(this.layerToLocation(pivot, level), level);
this.zoom(level);
},
_initControls: function () {
var controls = this.options.controls;
if (ui.Attribution && controls.attribution) {
this._createAttribution(controls.attribution);
}
if (!kendo.support.mobileOS) {
if (ui.Navigator && controls.navigator) {
this._createNavigator(controls.navigator);
}
if (ui.ZoomControl && controls.zoom) {
this._createZoomControl(controls.zoom);
}
}
},
_createControlElement: function (options, defaultPos) {
var pos = options.position || defaultPos;
var posSelector = '.' + renderPos(pos).replace(' ', '.');
var wrap = $('.k-map-controls' + posSelector, this.element);
if (wrap.length === 0) {
wrap = $('
').addClass('k-map-controls ' + renderPos(pos)).appendTo(this.element);
}
return $('
').appendTo(wrap);
},
_createAttribution: function (options) {
var element = this._createControlElement(options, 'bottomRight');
this.attribution = new ui.Attribution(element, options);
},
_createNavigator: function (options) {
var element = this._createControlElement(options, 'topLeft');
var navigator = this.navigator = new ui.Navigator(element, options);
this._navigatorPan = proxy(this._navigatorPan, this);
navigator.bind('pan', this._navigatorPan);
this._navigatorCenter = proxy(this._navigatorCenter, this);
navigator.bind('center', this._navigatorCenter);
},
_navigatorPan: function (e) {
var map = this;
var scroller = map.scroller;
var x = scroller.scrollLeft + e.x;
var y = scroller.scrollTop - e.y;
var bounds = this._virtualSize;
var height = this.element.height();
var width = this.element.width();
x = limit(x, bounds.x.min, bounds.x.max - width);
y = limit(y, bounds.y.min, bounds.y.max - height);
map.scroller.one('scroll', function (e) {
map._scrollEnd(e);
});
map.scroller.scrollTo(-x, -y);
},
_navigatorCenter: function () {
this.center(this.options.center);
},
_createZoomControl: function (options) {
var element = this._createControlElement(options, 'topLeft');
var zoomControl = this.zoomControl = new ui.ZoomControl(element, options);
this._zoomControlChange = proxy(this._zoomControlChange, this);
zoomControl.bind('change', this._zoomControlChange);
},
_zoomControlChange: function (e) {
if (!this.trigger('zoomStart', { originalEvent: e })) {
this.zoom(this.zoom() + e.delta);
this.trigger('zoomEnd', { originalEvent: e });
}
},
_initScroller: function () {
var friction = kendo.support.mobileOS ? FRICTION_MOBILE : FRICTION;
var zoomable = this.options.zoomable !== false;
var scroller = this.scroller = new kendo.mobile.ui.Scroller(this.element.children(0), {
friction: friction,
velocityMultiplier: VELOCITY_MULTIPLIER,
zoom: zoomable,
mousewheelScrolling: false
});
scroller.bind('scroll', proxy(this._scroll, this));
scroller.bind('scrollEnd', proxy(this._scrollEnd, this));
scroller.userEvents.bind('gesturestart', proxy(this._scaleStart, this));
scroller.userEvents.bind('gestureend', proxy(this._scale, this));
this.scrollElement = scroller.scrollElement;
},
_initLayers: function () {
var defs = this.options.layers, layers = this.layers = [];
for (var i = 0; i < defs.length; i++) {
var options = defs[i];
var type = options.type || 'shape';
var defaults = this.options.layerDefaults[type];
var impl = dataviz.map.layers[type];
layers.push(new impl(this, deepExtend({}, defaults, options)));
}
},
_initMarkers: function () {
this.markers = new map.layers.MarkerLayer(this, this.options.markerDefaults);
this.markers.add(this.options.markers);
},
_scroll: function (e) {
var origin = this.locationToLayer(this._viewOrigin).round();
var movable = e.sender.movable;
var offset = new g.Point(movable.x, movable.y).scale(-1).scale(1 / movable.scale);
origin.x += offset.x;
origin.y += offset.y;
this._scrollOffset = offset;
this._setOrigin(this.layerToLocation(origin));
this.trigger('pan', {
originalEvent: e,
origin: this._getOrigin(),
center: this.center()
});
},
_scrollEnd: function (e) {
if (!this._scrollOffset || !this._panComplete()) {
return;
}
this._scrollOffset = null;
this._panEndTS = new Date();
this.trigger('panEnd', {
originalEvent: e,
origin: this._getOrigin(),
center: this.center()
});
},
_panComplete: function () {
return new Date() - (this._panEndTS || 0) > 50;
},
_scaleStart: function (e) {
if (this.trigger('zoomStart', { originalEvent: e })) {
var touch = e.touches[1];
if (touch) {
touch.cancel();
}
}
},
_scale: function (e) {
var scale = this.scroller.movable.scale;
var zoom = this._scaleToZoom(scale);
var gestureCenter = new g.Point(e.center.x, e.center.y);
var centerLocation = this.viewToLocation(gestureCenter, zoom);
var centerPoint = this.locationToLayer(centerLocation, zoom);
var originPoint = centerPoint.translate(-gestureCenter.x, -gestureCenter.y);
this._zoomAround(originPoint, zoom);
this.trigger('zoomEnd', { originalEvent: e });
},
_scaleToZoom: function (scaleDelta) {
var scale = this._layerSize() * scaleDelta;
var tiles = scale / this.options.minSize;
var zoom = math.log(tiles) / math.log(2);
return math.round(zoom);
},
_reset: function () {
if (this.attribution) {
this.attribution.filter(this.center(), this.zoom());
}
this._viewOrigin = this._getOrigin(true);
this._resetScroller();
this.trigger('beforeReset');
this.trigger('reset');
},
_resetScroller: function () {
var scroller = this.scroller;
var x = scroller.dimensions.x;
var y = scroller.dimensions.y;
var scale = this._layerSize();
var nw = this.extent().nw;
var topLeft = this.locationToLayer(nw).round();
scroller.movable.round = true;
scroller.reset();
scroller.userEvents.cancel();
var zoom = this.zoom();
scroller.dimensions.forcedMinScale = pow(2, this.options.minZoom - zoom);
scroller.dimensions.maxScale = pow(2, this.options.maxZoom - zoom);
var xBounds = {
min: -topLeft.x,
max: scale - topLeft.x
};
var yBounds = {
min: -topLeft.y,
max: scale - topLeft.y
};
if (this.options.wraparound) {
xBounds.max = 20 * scale;
xBounds.min = -xBounds.max;
}
if (this.options.pannable === false) {
var viewSize = this.viewSize();
xBounds.min = yBounds.min = 0;
xBounds.max = viewSize.width;
yBounds.max = viewSize.height;
}
x.makeVirtual();
y.makeVirtual();
x.virtualSize(xBounds.min, xBounds.max);
y.virtualSize(yBounds.min, yBounds.max);
this._virtualSize = {
x: xBounds,
y: yBounds
};
},
_renderLayers: function () {
var defs = this.options.layers, layers = this.layers = [], scrollWrap = this.scrollWrap;
scrollWrap.empty();
for (var i = 0; i < defs.length; i++) {
var options = defs[i];
var type = options.type || 'shape';
var defaults = this.options.layerDefaults[type];
var impl = dataviz.map.layers[type];
layers.push(new impl(this, deepExtend({}, defaults, options)));
}
},
_layerSize: function (zoom) {
zoom = valueOrDefault(zoom, this.options.zoom);
return this.options.minSize * pow(2, zoom);
},
_click: function (e) {
if (!this._panComplete()) {
return;
}
var cursor = this.eventOffset(e);
this.trigger('click', {
originalEvent: e,
location: this.viewToLocation(cursor)
});
},
_mousewheel: function (e) {
e.preventDefault();
var delta = dataviz.mwDelta(e) > 0 ? -1 : 1;
var options = this.options;
var fromZoom = this.zoom();
var toZoom = limit(fromZoom + delta, options.minZoom, options.maxZoom);
if (options.zoomable !== false && toZoom !== fromZoom) {
if (!this.trigger('zoomStart', { originalEvent: e })) {
var cursor = this.eventOffset(e);
var location = this.viewToLocation(cursor);
var postZoom = this.locationToLayer(location, toZoom);
var origin = postZoom.translate(-cursor.x, -cursor.y);
this._zoomAround(origin, toZoom);
this.trigger('zoomEnd', { originalEvent: e });
}
}
}
});
dataviz.ui.plugin(Map);
}(window.kendo.jQuery));
}, typeof define == 'function' && define.amd ? define : function (a1, a2, a3) {
(a3 || a2)();
}));
(function (f, define) {
define('kendo.dataviz.map', [
'kendo.data',
'kendo.userevents',
'kendo.tooltip',
'kendo.mobile.scroller',
'kendo.draganddrop',
'kendo.drawing',
'dataviz/map/location',
'dataviz/map/attribution',
'dataviz/map/navigator',
'dataviz/map/zoom',
'dataviz/map/crs',
'dataviz/map/layers/base',
'dataviz/map/layers/shape',
'dataviz/map/layers/bubble',
'dataviz/map/layers/tile',
'dataviz/map/layers/bing',
'dataviz/map/layers/marker',
'dataviz/map/main'
], f);
}(function () {
var __meta__ = {
id: 'dataviz.map',
name: 'Map',
category: 'dataviz',
description: 'The Kendo DataViz Map displays spatial data',
depends: [
'data',
'userevents',
'tooltip',
'dataviz.core',
'drawing',
'mobile.scroller'
]
};
}, typeof define == 'function' && define.amd ? define : function (a1, a2, a3) {
(a3 || a2)();
}));
(function (f, define) {
define('dataviz/diagram/utils', ['kendo.core'], f);
}(function () {
(function ($, undefined) {
var kendo = window.kendo, diagram = kendo.dataviz.diagram = {}, deepExtend = kendo.deepExtend, isArray = $.isArray, EPSILON = 0.000001;
var Utils = {};
deepExtend(Utils, {
isNearZero: function (num) {
return Math.abs(num) < EPSILON;
},
isDefined: function (obj) {
return typeof obj !== 'undefined';
},
isUndefined: function (obj) {
return typeof obj === 'undefined' || obj === null;
},
isObject: function (obj) {
return obj === Object(obj);
},
has: function (obj, key) {
return Object.hasOwnProperty.call(obj, key);
},
isString: function (obj) {
return Object.prototype.toString.call(obj) == '[object String]';
},
isBoolean: function (obj) {
return Object.prototype.toString.call(obj) == '[object Boolean]';
},
isType: function (obj, type) {
return Object.prototype.toString.call(obj) == '[object ' + type + ']';
},
isNumber: function (obj) {
return !isNaN(parseFloat(obj)) && isFinite(obj);
},
isEmpty: function (obj) {
if (obj === null) {
return true;
}
if (isArray(obj) || Utils.isString(obj)) {
return obj.length === 0;
}
for (var key in obj) {
if (Utils.has(obj, key)) {
return false;
}
}
return true;
},
simpleExtend: function (destination, source) {
if (!Utils.isObject(source)) {
return;
}
for (var name in source) {
destination[name] = source[name];
}
},
initArray: function createIdArray(size, value) {
var array = [];
for (var i = 0; i < size; ++i) {
array[i] = value;
}
return array;
},
serializePoints: function (points) {
var res = [];
for (var i = 0; i < points.length; i++) {
var p = points[i];
res.push(p.x + ';' + p.y);
}
return res.join(';');
},
deserializePoints: function (s) {
var v = s.split(';'), points = [];
if (v.length % 2 !== 0) {
throw 'Not an array of points.';
}
for (var i = 0; i < v.length; i += 2) {
points.push(new diagram.Point(parseInt(v[i], 10), parseInt(v[i + 1], 10)));
}
return points;
},
randomInteger: function (lower, upper) {
return parseInt(Math.floor(Math.random() * upper) + lower, 10);
},
DFT: function (el, func) {
func(el);
if (el.childNodes) {
for (var i = 0; i < el.childNodes.length; i++) {
var item = el.childNodes[i];
this.DFT(item, func);
}
}
},
getMatrixAngle: function (m) {
if (m === null || m.d === 0) {
return 0;
}
return Math.atan2(m.b, m.d) * 180 / Math.PI;
},
getMatrixScaling: function (m) {
var sX = Math.sqrt(m.a * m.a + m.c * m.c);
var sY = Math.sqrt(m.b * m.b + m.d * m.d);
return [
sX,
sY
];
}
});
function Range(start, stop, step) {
if (typeof start == 'undefined' || typeof stop == 'undefined') {
return [];
}
if (step && Utils.sign(stop - start) != Utils.sign(step)) {
throw 'The sign of the increment should allow to reach the stop-value.';
}
step = step || 1;
start = start || 0;
stop = stop || start;
if ((stop - start) / step === Infinity) {
throw 'Infinite range defined.';
}
var range = [], i = -1, j;
function rangeIntegerScale(x) {
var k = 1;
while (x * k % 1) {
k *= 10;
}
return k;
}
var k = rangeIntegerScale(Math.abs(step));
start *= k;
stop *= k;
step *= k;
if (start > stop && step > 0) {
step = -step;
}
if (step < 0) {
while ((j = start + step * ++i) >= stop) {
range.push(j / k);
}
} else {
while ((j = start + step * ++i) <= stop) {
range.push(j / k);
}
}
return range;
}
function findRadian(start, end) {
if (start == end) {
return 0;
}
var sngXComp = end.x - start.x, sngYComp = start.y - end.y, atan = Math.atan(sngXComp / sngYComp);
if (sngYComp >= 0) {
return sngXComp < 0 ? atan + 2 * Math.PI : atan;
}
return atan + Math.PI;
}
Utils.sign = function (number) {
return number ? number < 0 ? -1 : 1 : 0;
};
Utils.findAngle = function (center, end) {
return findRadian(center, end) * 180 / Math.PI;
};
Utils.forEach = function (arr, iterator, thisRef) {
for (var i = 0; i < arr.length; i++) {
iterator.call(thisRef, arr[i], i, arr);
}
};
Utils.any = function (arr, predicate) {
for (var i = 0; i < arr.length; ++i) {
if (predicate(arr[i])) {
return arr[i];
}
}
return null;
};
Utils.remove = function (arr, what) {
var ax;
while ((ax = Utils.indexOf(arr, what)) !== -1) {
arr.splice(ax, 1);
}
return arr;
};
Utils.contains = function (arr, obj) {
return Utils.indexOf(arr, obj) !== -1;
};
Utils.indexOf = function (arr, what) {
return $.inArray(what, arr);
};
Utils.fold = function (list, iterator, acc, context) {
var initial = arguments.length > 2;
for (var i = 0; i < list.length; i++) {
var value = list[i];
if (!initial) {
acc = value;
initial = true;
} else {
acc = iterator.call(context, acc, value, i, list);
}
}
if (!initial) {
throw 'Reduce of empty array with no initial value';
}
return acc;
};
Utils.find = function (arr, iterator, context) {
var result;
Utils.any(arr, function (value, index, list) {
if (iterator.call(context, value, index, list)) {
result = value;
return true;
}
return false;
});
return result;
};
Utils.first = function (arr, constraint, context) {
if (arr.length === 0) {
return null;
}
if (Utils.isUndefined(constraint)) {
return arr[0];
}
return Utils.find(arr, constraint, context);
};
Utils.insert = function (arr, element, position) {
arr.splice(position, 0, element);
return arr;
};
Utils.all = function (arr, iterator, context) {
var result = true;
var value;
for (var i = 0; i < arr.length; i++) {
value = arr[i];
result = result && iterator.call(context, value, i, arr);
if (!result) {
break;
}
}
return result;
};
Utils.clear = function (arr) {
arr.splice(0, arr.length);
};
Utils.bisort = function (a, b, sortfunc) {
if (Utils.isUndefined(a)) {
throw 'First array is not specified.';
}
if (Utils.isUndefined(b)) {
throw 'Second array is not specified.';
}
if (a.length != b.length) {
throw 'The two arrays should have equal length';
}
var all = [], i;
for (i = 0; i < a.length; i++) {
all.push({
'x': a[i],
'y': b[i]
});
}
if (Utils.isUndefined(sortfunc)) {
all.sort(function (m, n) {
return m.x - n.x;
});
} else {
all.sort(function (m, n) {
return sortfunc(m.x, n.x);
});
}
Utils.clear(a);
Utils.clear(b);
for (i = 0; i < all.length; i++) {
a.push(all[i].x);
b.push(all[i].y);
}
};
Utils.addRange = function (arr, range) {
arr.push.apply(arr, range);
};
var Easing = {
easeInOut: function (pos) {
return -Math.cos(pos * Math.PI) / 2 + 0.5;
}
};
var Ticker = kendo.Class.extend({
init: function () {
this.adapters = [];
this.target = 0;
this.tick = 0;
this.interval = 20;
this.duration = 800;
this.lastTime = null;
this.handlers = [];
var _this = this;
this.transition = Easing.easeInOut;
this.timerDelegate = function () {
_this.onTimerEvent();
};
},
addAdapter: function (a) {
this.adapters.push(a);
},
onComplete: function (handler) {
this.handlers.push(handler);
},
removeHandler: function (handler) {
this.handlers = $.grep(this.handlers, function (h) {
return h !== handler;
});
},
trigger: function () {
var _this = this;
if (this.handlers) {
Utils.forEach(this.handlers, function (h) {
return h.call(_this.caller !== null ? _this.caller : _this);
});
}
},
onStep: function () {
},
seekTo: function (to) {
this.seekFromTo(this.tick, to);
},
seekFromTo: function (from, to) {
this.target = Math.max(0, Math.min(1, to));
this.tick = Math.max(0, Math.min(1, from));
this.lastTime = new Date().getTime();
if (!this.intervalId) {
this.intervalId = window.setInterval(this.timerDelegate, this.interval);
}
},
stop: function () {
if (this.intervalId) {
window.clearInterval(this.intervalId);
this.intervalId = null;
this.trigger();
}
},
play: function (origin) {
if (this.adapters.length === 0) {
return;
}
if (origin !== null) {
this.caller = origin;
}
this.initState();
this.seekFromTo(0, 1);
},
reverse: function () {
this.seekFromTo(1, 0);
},
initState: function () {
if (this.adapters.length === 0) {
return;
}
for (var i = 0; i < this.adapters.length; i++) {
this.adapters[i].initState();
}
},
propagate: function () {
var value = this.transition(this.tick);
for (var i = 0; i < this.adapters.length; i++) {
this.adapters[i].update(value);
}
},
onTimerEvent: function () {
var now = new Date().getTime();
var timePassed = now - this.lastTime;
this.lastTime = now;
var movement = timePassed / this.duration * (this.tick < this.target ? 1 : -1);
if (Math.abs(movement) >= Math.abs(this.tick - this.target)) {
this.tick = this.target;
} else {
this.tick += movement;
}
try {
this.propagate();
} finally {
this.onStep.call(this);
if (this.target == this.tick) {
this.stop();
}
}
}
});
kendo.deepExtend(diagram, {
init: function (element) {
kendo.init(element, diagram.ui);
},
Utils: Utils,
Range: Range,
Ticker: Ticker
});
}(window.kendo.jQuery));
}, typeof define == 'function' && define.amd ? define : function (a1, a2, a3) {
(a3 || a2)();
}));
(function (f, define) {
define('dataviz/diagram/math', [
'dataviz/diagram/utils',
'kendo.dataviz.core'
], f);
}(function () {
(function ($, undefined) {
var kendo = window.kendo, diagram = kendo.dataviz.diagram, Class = kendo.Class, deepExtend = kendo.deepExtend, dataviz = kendo.dataviz, Utils = diagram.Utils, Point = dataviz.Point2D, isFunction = kendo.isFunction, contains = Utils.contains, map = $.map;
var HITTESTAREA = 3, EPSILON = 0.000001;
deepExtend(Point.fn, {
plus: function (p) {
return new Point(this.x + p.x, this.y + p.y);
},
minus: function (p) {
return new Point(this.x - p.x, this.y - p.y);
},
offset: function (value) {
return new Point(this.x - value, this.y - value);
},
times: function (s) {
return new Point(this.x * s, this.y * s);
},
normalize: function () {
if (this.length() === 0) {
return new Point();
}
return this.times(1 / this.length());
},
length: function () {
return Math.sqrt(this.x * this.x + this.y * this.y);
},
toString: function () {
return '(' + this.x + ',' + this.y + ')';
},
lengthSquared: function () {
return this.x * this.x + this.y * this.y;
},
middleOf: function MiddleOf(p, q) {
return new Point(q.x - p.x, q.y - p.y).times(0.5).plus(p);
},
toPolar: function (useDegrees) {
var factor = 1;
if (useDegrees) {
factor = 180 / Math.PI;
}
var a = Math.atan2(Math.abs(this.y), Math.abs(this.x));
var halfpi = Math.PI / 2;
var len = this.length();
if (this.x === 0) {
if (this.y === 0) {
return new Polar(0, 0);
}
if (this.y > 0) {
return new Polar(len, factor * halfpi);
}
if (this.y < 0) {
return new Polar(len, factor * 3 * halfpi);
}
} else if (this.x > 0) {
if (this.y === 0) {
return new Polar(len, 0);
}
if (this.y > 0) {
return new Polar(len, factor * a);
}
if (this.y < 0) {
return new Polar(len, factor * (4 * halfpi - a));
}
} else {
if (this.y === 0) {
return new Polar(len, 2 * halfpi);
}
if (this.y > 0) {
return new Polar(len, factor * (2 * halfpi - a));
}
if (this.y < 0) {
return new Polar(len, factor * (2 * halfpi + a));
}
}
},
isOnLine: function (from, to) {
if (from.x > to.x) {
var temp = to;
to = from;
from = temp;
}
var r1 = new Rect(from.x, from.y).inflate(HITTESTAREA, HITTESTAREA), r2 = new Rect(to.x, to.y).inflate(HITTESTAREA, HITTESTAREA), o1, u1;
if (r1.union(r2).contains(this)) {
if (from.x === to.x || from.y === to.y) {
return true;
} else if (from.y < to.y) {
o1 = r1.x + (r2.x - r1.x) * (this.y - (r1.y + r1.height)) / (r2.y + r2.height - (r1.y + r1.height));
u1 = r1.x + r1.width + (r2.x + r2.width - (r1.x + r1.width)) * (this.y - r1.y) / (r2.y - r1.y);
} else {
o1 = r1.x + (r2.x - r1.x) * (this.y - r1.y) / (r2.y - r1.y);
u1 = r1.x + r1.width + (r2.x + r2.width - (r1.x + r1.width)) * (this.y - (r1.y + r1.height)) / (r2.y + r2.height - (r1.y + r1.height));
}
return this.x > o1 && this.x < u1;
}
return false;
}
});
deepExtend(Point, {
parse: function (str) {
var tempStr = str.slice(1, str.length - 1), xy = tempStr.split(','), x = parseInt(xy[0], 10), y = parseInt(xy[1], 10);
if (!isNaN(x) && !isNaN(y)) {
return new Point(x, y);
}
}
});
var PathDefiner = Class.extend({
init: function (p, left, right) {
this.point = p;
this.left = left;
this.right = right;
}
});
var Rect = Class.extend({
init: function (x, y, width, height) {
this.x = x || 0;
this.y = y || 0;
this.width = width || 0;
this.height = height || 0;
},
contains: function (point) {
return point.x >= this.x && point.x <= this.x + this.width && point.y >= this.y && point.y <= this.y + this.height;
},
inflate: function (dx, dy) {
if (dy === undefined) {
dy = dx;
}
this.x -= dx;
this.y -= dy;
this.width += 2 * dx + 1;
this.height += 2 * dy + 1;
return this;
},
offset: function (dx, dy) {
var x = dx, y = dy;
if (dx instanceof Point) {
x = dx.x;
y = dx.y;
}
this.x += x;
this.y += y;
return this;
},
union: function (r) {
var x1 = Math.min(this.x, r.x);
var y1 = Math.min(this.y, r.y);
var x2 = Math.max(this.x + this.width, r.x + r.width);
var y2 = Math.max(this.y + this.height, r.y + r.height);
return new Rect(x1, y1, x2 - x1, y2 - y1);
},
center: function () {
return new Point(this.x + this.width / 2, this.y + this.height / 2);
},
top: function () {
return new Point(this.x + this.width / 2, this.y);
},
right: function () {
return new Point(this.x + this.width, this.y + this.height / 2);
},
bottom: function () {
return new Point(this.x + this.width / 2, this.y + this.height);
},
left: function () {
return new Point(this.x, this.y + this.height / 2);
},
topLeft: function () {
return new Point(this.x, this.y);
},
topRight: function () {
return new Point(this.x + this.width, this.y);
},
bottomLeft: function () {
return new Point(this.x, this.y + this.height);
},
bottomRight: function () {
return new Point(this.x + this.width, this.y + this.height);
},
clone: function () {
return new Rect(this.x, this.y, this.width, this.height);
},
isEmpty: function () {
return !this.width && !this.height;
},
equals: function (rect) {
return this.x === rect.x && this.y === rect.y && this.width === rect.width && this.height === rect.height;
},
rotatedBounds: function (angle) {
var rect = this.clone(), points = this.rotatedPoints(angle), tl = points[0], tr = points[1], br = points[2], bl = points[3];
rect.x = Math.min(br.x, tl.x, tr.x, bl.x);
rect.y = Math.min(br.y, tl.y, tr.y, bl.y);
rect.width = Math.max(br.x, tl.x, tr.x, bl.x) - rect.x;
rect.height = Math.max(br.y, tl.y, tr.y, bl.y) - rect.y;
return rect;
},
rotatedPoints: function (angle) {
var rect = this, c = rect.center(), br = rect.bottomRight().rotate(c, 360 - angle), tl = rect.topLeft().rotate(c, 360 - angle), tr = rect.topRight().rotate(c, 360 - angle), bl = rect.bottomLeft().rotate(c, 360 - angle);
return [
tl,
tr,
br,
bl
];
},
toString: function (delimiter) {
delimiter = delimiter || ' ';
return this.x + delimiter + this.y + delimiter + this.width + delimiter + this.height;
},
scale: function (scaleX, scaleY, staicPoint, adornerCenter, angle) {
var tl = this.topLeft();
var thisCenter = this.center();
tl.rotate(thisCenter, 360 - angle).rotate(adornerCenter, angle);
var delta = staicPoint.minus(tl);
var scaled = new Point(delta.x * scaleX, delta.y * scaleY);
var position = delta.minus(scaled);
tl = tl.plus(position);
tl.rotate(adornerCenter, 360 - angle).rotate(thisCenter, angle);
this.x = tl.x;
this.y = tl.y;
this.width *= scaleX;
this.height *= scaleY;
},
zoom: function (zoom) {
this.x *= zoom;
this.y *= zoom;
this.width *= zoom;
this.height *= zoom;
return this;
},
overlaps: function (rect) {
var bottomRight = this.bottomRight();
var rectBottomRight = rect.bottomRight();
var overlaps = !(bottomRight.x < rect.x || bottomRight.y < rect.y || rectBottomRight.x < this.x || rectBottomRight.y < this.y);
return overlaps;
}
});
var Size = Class.extend({
init: function (width, height) {
this.width = width;
this.height = height;
}
});
Size.prototype.Empty = new Size(0, 0);
Rect.toRect = function (rect) {
if (!(rect instanceof Rect)) {
rect = new Rect(rect.x, rect.y, rect.width, rect.height);
}
return rect;
};
Rect.empty = function () {
return new Rect(0, 0, 0, 0);
};
Rect.fromPoints = function (p, q) {
if (isNaN(p.x) || isNaN(p.y) || isNaN(q.x) || isNaN(q.y)) {
throw 'Some values are NaN.';
}
return new Rect(Math.min(p.x, q.x), Math.min(p.y, q.y), Math.abs(p.x - q.x), Math.abs(p.y - q.y));
};
function isNearZero(num) {
return Math.abs(num) < EPSILON;
}
function intersectLine(start1, end1, start2, end2, isSegment) {
var tangensdiff = (end1.x - start1.x) * (end2.y - start2.y) - (end1.y - start1.y) * (end2.x - start2.x);
if (isNearZero(tangensdiff)) {
return;
}
var num1 = (start1.y - start2.y) * (end2.x - start2.x) - (start1.x - start2.x) * (end2.y - start2.y);
var num2 = (start1.y - start2.y) * (end1.x - start1.x) - (start1.x - start2.x) * (end1.y - start1.y);
var r = num1 / tangensdiff;
var s = num2 / tangensdiff;
if (isSegment && (r < 0 || r > 1 || s < 0 || s > 1)) {
return;
}
return new Point(start1.x + r * (end1.x - start1.x), start1.y + r * (end1.y - start1.y));
}
var Intersect = {
lines: function (start1, end1, start2, end2) {
return intersectLine(start1, end1, start2, end2);
},
segments: function (start1, end1, start2, end2) {
return intersectLine(start1, end1, start2, end2, true);
},
rectWithLine: function (rect, start, end) {
return Intersect.segments(start, end, rect.topLeft(), rect.topRight()) || Intersect.segments(start, end, rect.topRight(), rect.bottomRight()) || Intersect.segments(start, end, rect.bottomLeft(), rect.bottomRight()) || Intersect.segments(start, end, rect.topLeft(), rect.bottomLeft());
},
rects: function (rect1, rect2, angle) {
var tl = rect2.topLeft(), tr = rect2.topRight(), bl = rect2.bottomLeft(), br = rect2.bottomRight();
var center = rect2.center();
if (angle) {
tl = tl.rotate(center, angle);
tr = tr.rotate(center, angle);
bl = bl.rotate(center, angle);
br = br.rotate(center, angle);
}
var intersect = rect1.contains(tl) || rect1.contains(tr) || rect1.contains(bl) || rect1.contains(br) || Intersect.rectWithLine(rect1, tl, tr) || Intersect.rectWithLine(rect1, tl, bl) || Intersect.rectWithLine(rect1, tr, br) || Intersect.rectWithLine(rect1, bl, br);
if (!intersect) {
tl = rect1.topLeft();
tr = rect1.topRight();
bl = rect1.bottomLeft();
br = rect1.bottomRight();
if (angle) {
var reverseAngle = 360 - angle;
tl = tl.rotate(center, reverseAngle);
tr = tr.rotate(center, reverseAngle);
bl = bl.rotate(center, reverseAngle);
br = br.rotate(center, reverseAngle);
}
intersect = rect2.contains(tl) || rect2.contains(tr) || rect2.contains(bl) || rect2.contains(br);
}
return intersect;
}
};
var RectAlign = Class.extend({
init: function (container) {
this.container = Rect.toRect(container);
},
align: function (content, alignment) {
var alignValues = alignment.toLowerCase().split(' ');
for (var i = 0; i < alignValues.length; i++) {
content = this._singleAlign(content, alignValues[i]);
}
return content;
},
_singleAlign: function (content, alignment) {
if (isFunction(this[alignment])) {
return this[alignment](content);
} else {
return content;
}
},
left: function (content) {
return this._align(content, this._left);
},
center: function (content) {
return this._align(content, this._center);
},
right: function (content) {
return this._align(content, this._right);
},
stretch: function (content) {
return this._align(content, this._stretch);
},
top: function (content) {
return this._align(content, this._top);
},
middle: function (content) {
return this._align(content, this._middle);
},
bottom: function (content) {
return this._align(content, this._bottom);
},
_left: function (container, content) {
content.x = container.x;
},
_center: function (container, content) {
content.x = (container.width - content.width) / 2 || 0;
},
_right: function (container, content) {
content.x = container.width - content.width;
},
_top: function (container, content) {
content.y = container.y;
},
_middle: function (container, content) {
content.y = (container.height - content.height) / 2 || 0;
},
_bottom: function (container, content) {
content.y = container.height - content.height;
},
_stretch: function (container, content) {
content.x = 0;
content.y = 0;
content.height = container.height;
content.width = container.width;
},
_align: function (content, alignCalc) {
content = Rect.toRect(content);
alignCalc(this.container, content);
return content;
}
});
var Polar = Class.extend({
init: function (r, a) {
this.r = r;
this.angle = a;
}
});
var Matrix = Class.extend({
init: function (a, b, c, d, e, f) {
this.a = a || 0;
this.b = b || 0;
this.c = c || 0;
this.d = d || 0;
this.e = e || 0;
this.f = f || 0;
},
plus: function (m) {
this.a += m.a;
this.b += m.b;
this.c += m.c;
this.d += m.d;
this.e += m.e;
this.f += m.f;
},
minus: function (m) {
this.a -= m.a;
this.b -= m.b;
this.c -= m.c;
this.d -= m.d;
this.e -= m.e;
this.f -= m.f;
},
times: function (m) {
return new Matrix(this.a * m.a + this.c * m.b, this.b * m.a + this.d * m.b, this.a * m.c + this.c * m.d, this.b * m.c + this.d * m.d, this.a * m.e + this.c * m.f + this.e, this.b * m.e + this.d * m.f + this.f);
},
apply: function (p) {
return new Point(this.a * p.x + this.c * p.y + this.e, this.b * p.x + this.d * p.y + this.f);
},
applyRect: function (r) {
return Rect.fromPoints(this.apply(r.topLeft()), this.apply(r.bottomRight()));
},
toString: function () {
return 'matrix(' + this.a + ' ' + this.b + ' ' + this.c + ' ' + this.d + ' ' + this.e + ' ' + this.f + ')';
}
});
deepExtend(Matrix, {
fromSVGMatrix: function (vm) {
var m = new Matrix();
m.a = vm.a;
m.b = vm.b;
m.c = vm.c;
m.d = vm.d;
m.e = vm.e;
m.f = vm.f;
return m;
},
fromMatrixVector: function (v) {
var m = new Matrix();
m.a = v.a;
m.b = v.b;
m.c = v.c;
m.d = v.d;
m.e = v.e;
m.f = v.f;
return m;
},
fromList: function (v) {
if (v.length !== 6) {
throw 'The given list should consist of six elements.';
}
var m = new Matrix();
m.a = v[0];
m.b = v[1];
m.c = v[2];
m.d = v[3];
m.e = v[4];
m.f = v[5];
return m;
},
translation: function (x, y) {
var m = new Matrix();
m.a = 1;
m.b = 0;
m.c = 0;
m.d = 1;
m.e = x;
m.f = y;
return m;
},
unit: function () {
return new Matrix(1, 0, 0, 1, 0, 0);
},
rotation: function (angle, x, y) {
var m = new Matrix();
m.a = Math.cos(angle * Math.PI / 180);
m.b = Math.sin(angle * Math.PI / 180);
m.c = -m.b;
m.d = m.a;
m.e = x - x * m.a + y * m.b || 0;
m.f = y - y * m.a - x * m.b || 0;
return m;
},
scaling: function (scaleX, scaleY) {
var m = new Matrix();
m.a = scaleX;
m.b = 0;
m.c = 0;
m.d = scaleY;
m.e = 0;
m.f = 0;
return m;
},
parse: function (v) {
var parts, nums;
if (v) {
v = v.trim();
if (v.slice(0, 6).toLowerCase() === 'matrix') {
nums = v.slice(7, v.length - 1).trim();
parts = nums.split(',');
if (parts.length === 6) {
return Matrix.fromList(map(parts, function (p) {
return parseFloat(p);
}));
}
parts = nums.split(' ');
if (parts.length === 6) {
return Matrix.fromList(map(parts, function (p) {
return parseFloat(p);
}));
}
}
if (v.slice(0, 1) === '(' && v.slice(v.length - 1) === ')') {
v = v.substr(1, v.length - 1);
}
if (v.indexOf(',') > 0) {
parts = v.split(',');
if (parts.length === 6) {
return Matrix.fromList(map(parts, function (p) {
return parseFloat(p);
}));
}
}
if (v.indexOf(' ') > 0) {
parts = v.split(' ');
if (parts.length === 6) {
return Matrix.fromList(map(parts, function (p) {
return parseFloat(p);
}));
}
}
}
return parts;
}
});
var MatrixVector = Class.extend({
init: function (a, b, c, d, e, f) {
this.a = a || 0;
this.b = b || 0;
this.c = c || 0;
this.d = d || 0;
this.e = e || 0;
this.f = f || 0;
},
fromMatrix: function FromMatrix(m) {
var v = new MatrixVector();
v.a = m.a;
v.b = m.b;
v.c = m.c;
v.d = m.d;
v.e = m.e;
v.f = m.f;
return v;
}
});
function normalVariable(mean, deviation) {
var x, y, r;
do {
x = Math.random() * 2 - 1;
y = Math.random() * 2 - 1;
r = x * x + y * y;
} while (!r || r > 1);
return mean + deviation * x * Math.sqrt(-2 * Math.log(r) / r);
}
function randomId(length) {
if (Utils.isUndefined(length)) {
length = 10;
}
var result = '';
var chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
for (var i = length; i > 0; --i) {
result += chars.charAt(Math.round(Math.random() * (chars.length - 1)));
}
return result;
}
var Geometry = {
_distanceToLineSquared: function (p, a, b) {
function d2(pt1, pt2) {
return (pt1.x - pt2.x) * (pt1.x - pt2.x) + (pt1.y - pt2.y) * (pt1.y - pt2.y);
}
if (a === b) {
return d2(p, a);
}
var vx = b.x - a.x, vy = b.y - a.y, dot = (p.x - a.x) * vx + (p.y - a.y) * vy;
if (dot < 0) {
return d2(a, p);
}
dot = (b.x - p.x) * vx + (b.y - p.y) * vy;
if (dot < 0) {
return d2(b, p);
}
dot = (b.x - p.x) * vy - (b.y - p.y) * vx;
return dot * dot / (vx * vx + vy * vy);
},
distanceToLine: function (p, a, b) {
return Math.sqrt(this._distanceToLineSquared(p, a, b));
},
distanceToPolyline: function (p, points) {
var minimum = Number.MAX_VALUE;
if (Utils.isUndefined(points) || points.length === 0) {
return Number.MAX_VALUE;
}
for (var s = 0; s < points.length - 1; s++) {
var p1 = points[s];
var p2 = points[s + 1];
var d = this._distanceToLineSquared(p, p1, p2);
if (d < minimum) {
minimum = d;
}
}
return Math.sqrt(minimum);
}
};
var HashTable = kendo.Class.extend({
init: function () {
this._buckets = [];
this.length = 0;
},
add: function (key, value) {
var obj = this._createGetBucket(key);
if (Utils.isDefined(value)) {
obj.value = value;
}
return obj;
},
get: function (key) {
if (this._bucketExists(key)) {
return this._createGetBucket(key);
}
return null;
},
set: function (key, value) {
this.add(key, value);
},
containsKey: function (key) {
return this._bucketExists(key);
},
remove: function (key) {
if (this._bucketExists(key)) {
var hashId = this._hash(key);
delete this._buckets[hashId];
this.length--;
return key;
}
},
forEach: function (func) {
var hashes = this._hashes();
for (var i = 0, len = hashes.length; i < len; i++) {
var hash = hashes[i];
var bucket = this._buckets[hash];
if (Utils.isUndefined(bucket)) {
continue;
}
func(bucket);
}
},
clone: function () {
var ht = new HashTable();
var hashes = this._hashes();
for (var i = 0, len = hashes.length; i < len; i++) {
var hash = hashes[i];
var bucket = this._buckets[hash];
if (Utils.isUndefined(bucket)) {
continue;
}
ht.add(bucket.key, bucket.value);
}
return ht;
},
_hashes: function () {
var hashes = [];
for (var hash in this._buckets) {
if (this._buckets.hasOwnProperty(hash)) {
hashes.push(hash);
}
}
return hashes;
},
_bucketExists: function (key) {
var hashId = this._hash(key);
return Utils.isDefined(this._buckets[hashId]);
},
_createGetBucket: function (key) {
var hashId = this._hash(key);
var bucket = this._buckets[hashId];
if (Utils.isUndefined(bucket)) {
bucket = { key: key };
this._buckets[hashId] = bucket;
this.length++;
}
return bucket;
},
_hash: function (key) {
if (Utils.isNumber(key)) {
return key;
}
if (Utils.isString(key)) {
return this._hashString(key);
}
if (Utils.isObject(key)) {
return this._objectHashId(key);
}
throw 'Unsupported key type.';
},
_hashString: function (s) {
var result = 0;
if (s.length === 0) {
return result;
}
for (var i = 0; i < s.length; i++) {
var ch = s.charCodeAt(i);
result = result * 32 - result + ch;
}
return result;
},
_objectHashId: function (key) {
var id = key._hashId;
if (Utils.isUndefined(id)) {
id = randomId();
key._hashId = id;
}
return id;
}
});
var Dictionary = kendo.Observable.extend({
init: function (dictionary) {
var that = this;
kendo.Observable.fn.init.call(that);
this._hashTable = new HashTable();
this.length = 0;
if (Utils.isDefined(dictionary)) {
if ($.isArray(dictionary)) {
for (var i = 0; i < dictionary.length; i++) {
this.add(dictionary[i]);
}
} else {
dictionary.forEach(function (k, v) {
this.add(k, v);
}, this);
}
}
},
add: function (key, value) {
var entry = this._hashTable.get(key);
if (!entry) {
entry = this._hashTable.add(key);
this.length++;
this.trigger('changed');
}
entry.value = value;
},
set: function (key, value) {
this.add(key, value);
},
get: function (key) {
var entry = this._hashTable.get(key);
if (entry) {
return entry.value;
}
throw new Error('Cannot find key ' + key);
},
containsKey: function (key) {
return this._hashTable.containsKey(key);
},
remove: function (key) {
if (this.containsKey(key)) {
this.trigger('changed');
this.length--;
return this._hashTable.remove(key);
}
},
forEach: function (func, thisRef) {
this._hashTable.forEach(function (entry) {
func.call(thisRef, entry.key, entry.value);
});
},
forEachValue: function (func, thisRef) {
this._hashTable.forEach(function (entry) {
func.call(thisRef, entry.value);
});
},
forEachKey: function (func, thisRef) {
this._hashTable.forEach(function (entry) {
func.call(thisRef, entry.key);
});
},
keys: function () {
var keys = [];
this.forEachKey(function (key) {
keys.push(key);
});
return keys;
}
});
var Queue = kendo.Class.extend({
init: function () {
this._tail = null;
this._head = null;
this.length = 0;
},
enqueue: function (value) {
var entry = {
value: value,
next: null
};
if (!this._head) {
this._head = entry;
this._tail = this._head;
} else {
this._tail.next = entry;
this._tail = this._tail.next;
}
this.length++;
},
dequeue: function () {
if (this.length < 1) {
throw new Error('The queue is empty.');
}
var value = this._head.value;
this._head = this._head.next;
this.length--;
return value;
},
contains: function (item) {
var current = this._head;
while (current) {
if (current.value === item) {
return true;
}
current = current.next;
}
return false;
}
});
var Set = kendo.Observable.extend({
init: function (resource) {
var that = this;
kendo.Observable.fn.init.call(that);
this._hashTable = new HashTable();
this.length = 0;
if (Utils.isDefined(resource)) {
if (resource instanceof HashTable) {
resource.forEach(function (d) {
this.add(d);
});
} else if (resource instanceof Dictionary) {
resource.forEach(function (k, v) {
this.add({
key: k,
value: v
});
}, this);
}
}
},
contains: function (item) {
return this._hashTable.containsKey(item);
},
add: function (item) {
var entry = this._hashTable.get(item);
if (!entry) {
this._hashTable.add(item, item);
this.length++;
this.trigger('changed');
}
},
get: function (item) {
if (this.contains(item)) {
return this._hashTable.get(item).value;
} else {
return null;
}
},
hash: function (item) {
return this._hashTable._hash(item);
},
remove: function (item) {
if (this.contains(item)) {
this._hashTable.remove(item);
this.length--;
this.trigger('changed');
}
},
forEach: function (func, context) {
this._hashTable.forEach(function (kv) {
func(kv.value);
}, context);
},
toArray: function () {
var r = [];
this.forEach(function (d) {
r.push(d);
});
return r;
}
});
var Node = kendo.Class.extend({
init: function (id, shape) {
this.links = [];
this.outgoing = [];
this.incoming = [];
this.weight = 1;
if (Utils.isDefined(id)) {
this.id = id;
} else {
this.id = randomId();
}
if (Utils.isDefined(shape)) {
this.associatedShape = shape;
var b = shape.bounds();
this.width = b.width;
this.height = b.height;
this.x = b.x;
this.y = b.y;
} else {
this.associatedShape = null;
}
this.data = null;
this.type = 'Node';
this.shortForm = 'Node \'' + this.id + '\'';
this.isVirtual = false;
},
isIsolated: function () {
return Utils.isEmpty(this.links);
},
bounds: function (r) {
if (!Utils.isDefined(r)) {
return new diagram.Rect(this.x, this.y, this.width, this.height);
}
this.x = r.x;
this.y = r.y;
this.width = r.width;
this.height = r.height;
},
isLinkedTo: function (node) {
var that = this;
return Utils.any(that.links, function (link) {
return link.getComplement(that) === node;
});
},
getChildren: function () {
if (this.outgoing.length === 0) {
return [];
}
var children = [];
for (var i = 0, len = this.outgoing.length; i < len; i++) {
var link = this.outgoing[i];
children.push(link.getComplement(this));
}
return children;
},
getParents: function () {
if (this.incoming.length === 0) {
return [];
}
var parents = [];
for (var i = 0, len = this.incoming.length; i < len; i++) {
var link = this.incoming[i];
parents.push(link.getComplement(this));
}
return parents;
},
clone: function () {
var copy = new Node();
if (Utils.isDefined(this.weight)) {
copy.weight = this.weight;
}
if (Utils.isDefined(this.balance)) {
copy.balance = this.balance;
}
if (Utils.isDefined(this.owner)) {
copy.owner = this.owner;
}
copy.associatedShape = this.associatedShape;
copy.x = this.x;
copy.y = this.y;
copy.width = this.width;
copy.height = this.height;
return copy;
},
adjacentTo: function (node) {
return this.isLinkedTo(node) !== null;
},
removeLink: function (link) {
if (link.source === this) {
Utils.remove(this.links, link);
Utils.remove(this.outgoing, link);
link.source = null;
}
if (link.target === this) {
Utils.remove(this.links, link);
Utils.remove(this.incoming, link);
link.target = null;
}
},
hasLinkTo: function (node) {
return Utils.any(this.outgoing, function (link) {
return link.target === node;
});
},
degree: function () {
return this.links.length;
},
incidentWith: function (link) {
return contains(this.links, link);
},
getLinksWith: function (node) {
return Utils.all(this.links, function (link) {
return link.getComplement(this) === node;
}, this);
},
getNeighbors: function () {
var neighbors = [];
Utils.forEach(this.incoming, function (e) {
neighbors.push(e.getComplement(this));
}, this);
Utils.forEach(this.outgoing, function (e) {
neighbors.push(e.getComplement(this));
}, this);
return neighbors;
}
});
var Link = kendo.Class.extend({
init: function (source, target, id, connection) {
if (Utils.isUndefined(source)) {
throw 'The source of the new link is not set.';
}
if (Utils.isUndefined(target)) {
throw 'The target of the new link is not set.';
}
var sourceFound, targetFound;
if (Utils.isString(source)) {
sourceFound = new Node(source);
} else {
sourceFound = source;
}
if (Utils.isString(target)) {
targetFound = new Node(target);
} else {
targetFound = target;
}
this.source = sourceFound;
this.target = targetFound;
this.source.links.push(this);
this.target.links.push(this);
this.source.outgoing.push(this);
this.target.incoming.push(this);
if (Utils.isDefined(id)) {
this.id = id;
} else {
this.id = randomId();
}
if (Utils.isDefined(connection)) {
this.associatedConnection = connection;
} else {
this.associatedConnection = null;
}
this.type = 'Link';
this.shortForm = 'Link \'' + this.source.id + '->' + this.target.id + '\'';
},
getComplement: function (node) {
if (this.source !== node && this.target !== node) {
throw 'The given node is not incident with this link.';
}
return this.source === node ? this.target : this.source;
},
getCommonNode: function (link) {
if (this.source === link.source || this.source === link.target) {
return this.source;
}
if (this.target === link.source || this.target === link.target) {
return this.target;
}
return null;
},
isBridging: function (v1, v2) {
return this.source === v1 && this.target === v2 || this.source === v2 && this.target === v1;
},
getNodes: function () {
return [
this.source,
this.target
];
},
incidentWith: function (node) {
return this.source === node || this.target === node;
},
adjacentTo: function (link) {
return contains(this.source.links, link) || contains(this.target.links, link);
},
changeSource: function (node) {
Utils.remove(this.source.links, this);
Utils.remove(this.source.outgoing, this);
node.links.push(this);
node.outgoing.push(this);
this.source = node;
},
changeTarget: function (node) {
Utils.remove(this.target.links, this);
Utils.remove(this.target.incoming, this);
node.links.push(this);
node.incoming.push(this);
this.target = node;
},
changesNodes: function (v, w) {
if (this.source === v) {
this.changeSource(w);
} else if (this.target === v) {
this.changeTarget(w);
}
},
reverse: function () {
var oldSource = this.source;
var oldTarget = this.target;
this.source = oldTarget;
Utils.remove(oldSource.outgoing, this);
this.source.outgoing.push(this);
this.target = oldSource;
Utils.remove(oldTarget.incoming, this);
this.target.incoming.push(this);
return this;
},
directTo: function (target) {
if (this.source !== target && this.target !== target) {
throw 'The given node is not incident with this link.';
}
if (this.target !== target) {
this.reverse();
}
},
createReverseEdge: function () {
var r = this.clone();
r.reverse();
r.reversed = true;
return r;
},
clone: function () {
var clone = new Link(this.source, this.target);
return clone;
}
});
var Graph = kendo.Class.extend({
init: function (idOrDiagram) {
this.links = [];
this.nodes = [];
this._nodeMap = new Dictionary();
this.diagram = null;
this._root = null;
if (Utils.isDefined(idOrDiagram)) {
if (Utils.isString(idOrDiagram)) {
this.id = idOrDiagram;
} else {
this.diagram = idOrDiagram;
this.id = idOrDiagram.id;
}
} else {
this.id = randomId();
}
this.bounds = new Rect();
this._hasCachedRelationships = false;
this.type = 'Graph';
},
cacheRelationships: function (forceRebuild) {
if (Utils.isUndefined(forceRebuild)) {
forceRebuild = false;
}
if (this._hasCachedRelationships && !forceRebuild) {
return;
}
for (var i = 0, len = this.nodes.length; i < len; i++) {
var node = this.nodes[i];
node.children = this.getChildren(node);
node.parents = this.getParents(node);
}
this._hasCachedRelationships = true;
},
assignLevels: function (startNode, offset, visited) {
if (!startNode) {
throw 'Start node not specified.';
}
if (Utils.isUndefined(offset)) {
offset = 0;
}
this.cacheRelationships();
if (Utils.isUndefined(visited)) {
visited = new Dictionary();
Utils.forEach(this.nodes, function (n) {
visited.add(n, false);
});
}
visited.set(startNode, true);
startNode.level = offset;
var children = startNode.children;
for (var i = 0, len = children.length; i < len; i++) {
var child = children[i];
if (!child || visited.get(child)) {
continue;
}
this.assignLevels(child, offset + 1, visited);
}
},
root: function (value) {
if (Utils.isUndefined(value)) {
if (!this._root) {
var found = Utils.first(this.nodes, function (n) {
return n.incoming.length === 0;
});
if (found) {
return found;
}
return Utils.first(this.nodes);
} else {
return this._root;
}
} else {
this._root = value;
}
},
getConnectedComponents: function () {
this.componentIndex = 0;
this.setItemIndices();
var componentId = Utils.initArray(this.nodes.length, -1);
for (var v = 0; v < this.nodes.length; v++) {
if (componentId[v] === -1) {
this._collectConnectedNodes(componentId, v);
this.componentIndex++;
}
}
var components = [], i;
for (i = 0; i < this.componentIndex; ++i) {
components[i] = new Graph();
}
for (i = 0; i < componentId.length; ++i) {
var graph = components[componentId[i]];
graph.addNodeAndOutgoings(this.nodes[i]);
}
components.sort(function (a, b) {
return b.nodes.length - a.nodes.length;
});
return components;
},
_collectConnectedNodes: function (setIds, nodeIndex) {
setIds[nodeIndex] = this.componentIndex;
var node = this.nodes[nodeIndex];
Utils.forEach(node.links, function (link) {
var next = link.getComplement(node);
var nextId = next.index;
if (setIds[nextId] === -1) {
this._collectConnectedNodes(setIds, nextId);
}
}, this);
},
calcBounds: function () {
if (this.isEmpty()) {
this.bounds = new Rect();
return this.bounds;
}
var b = null;
for (var i = 0, len = this.nodes.length; i < len; i++) {
var node = this.nodes[i];
if (!b) {
b = node.bounds();
} else {
b = b.union(node.bounds());
}
}
this.bounds = b;
return this.bounds;
},
getSpanningTree: function (root) {
var tree = new Graph();
var map = new Dictionary(), source, target;
tree.root = root.clone();
tree.root.level = 0;
tree.root.id = root.id;
map.add(root, tree.root);
root.level = 0;
var visited = [];
var remaining = [];
tree._addNode(tree.root);
visited.push(root);
remaining.push(root);
var levelCount = 1;
while (remaining.length > 0) {
var next = remaining.pop();
for (var ni = 0; ni < next.links.length; ni++) {
var link = next.links[ni];
var cn = link.getComplement(next);
if (contains(visited, cn)) {
continue;
}
cn.level = next.level + 1;
if (levelCount < cn.level + 1) {
levelCount = cn.level + 1;
}
if (!contains(remaining, cn)) {
remaining.push(cn);
}
if (!contains(visited, cn)) {
visited.push(cn);
}
if (map.containsKey(next)) {
source = map.get(next);
} else {
source = next.clone();
source.level = next.level;
source.id = next.id;
map.add(next, source);
}
if (map.containsKey(cn)) {
target = map.get(cn);
} else {
target = cn.clone();
target.level = cn.level;
target.id = cn.id;
map.add(cn, target);
}
var newLink = new Link(source, target);
tree.addLink(newLink);
}
}
var treeLevels = [];
for (var i = 0; i < levelCount; i++) {
treeLevels.push([]);
}
Utils.forEach(tree.nodes, function (node) {
treeLevels[node.level].push(node);
});
tree.treeLevels = treeLevels;
tree.cacheRelationships();
return tree;
},
takeRandomNode: function (excludedNodes, incidenceLessThan) {
if (Utils.isUndefined(excludedNodes)) {
excludedNodes = [];
}
if (Utils.isUndefined(incidenceLessThan)) {
incidenceLessThan = 4;
}
if (this.nodes.length === 0) {
return null;
}
if (this.nodes.length === 1) {
return contains(excludedNodes, this.nodes[0]) ? null : this.nodes[0];
}
var pool = $.grep(this.nodes, function (node) {
return !contains(excludedNodes, node) && node.degree() <= incidenceLessThan;
});
if (Utils.isEmpty(pool)) {
return null;
}
return pool[Utils.randomInteger(0, pool.length)];
},
isEmpty: function () {
return Utils.isEmpty(this.nodes);
},
isHealthy: function () {
return Utils.all(this.links, function (link) {
return contains(this.nodes, link.source) && contains(this.nodes, link.target);
}, this);
},
getParents: function (n) {
if (!this.hasNode(n)) {
throw 'The given node is not part of this graph.';
}
return n.getParents();
},
getChildren: function (n) {
if (!this.hasNode(n)) {
throw 'The given node is not part of this graph.';
}
return n.getChildren();
},
addLink: function (sourceOrLink, target, owner) {
if (Utils.isUndefined(sourceOrLink)) {
throw 'The source of the link is not defined.';
}
if (Utils.isUndefined(target)) {
if (Utils.isDefined(sourceOrLink.type) && sourceOrLink.type === 'Link') {
this.addExistingLink(sourceOrLink);
return;
} else {
throw 'The target of the link is not defined.';
}
}
var foundSource = this.getNode(sourceOrLink);
if (Utils.isUndefined(foundSource)) {
foundSource = this.addNode(sourceOrLink);
}
var foundTarget = this.getNode(target);
if (Utils.isUndefined(foundTarget)) {
foundTarget = this.addNode(target);
}
var newLink = new Link(foundSource, foundTarget);
if (Utils.isDefined(owner)) {
newLink.owner = owner;
}
this.links.push(newLink);
return newLink;
},
removeAllLinks: function () {
while (this.links.length > 0) {
var link = this.links[0];
this.removeLink(link);
}
},
addExistingLink: function (link) {
if (this.hasLink(link)) {
return;
}
this.links.push(link);
if (this.hasNode(link.source.id)) {
var s = this.getNode(link.source.id);
link.changeSource(s);
} else {
this.addNode(link.source);
}
if (this.hasNode(link.target.id)) {
var t = this.getNode(link.target.id);
link.changeTarget(t);
} else {
this.addNode(link.target);
}
},
hasLink: function (linkOrId) {
if (Utils.isString(linkOrId)) {
return Utils.any(this.links, function (link) {
return link.id === linkOrId;
});
}
if (linkOrId.type === 'Link') {
return contains(this.links, linkOrId);
}
throw 'The given object is neither an identifier nor a Link.';
},
getNode: function (nodeOrId) {
var id = nodeOrId.id || nodeOrId;
if (this._nodeMap.containsKey(id)) {
return this._nodeMap.get(id);
}
},
hasNode: function (nodeOrId) {
var id = nodeOrId.id || nodeOrId;
return this._nodeMap.containsKey(id);
},
_addNode: function (node) {
this.nodes.push(node);
this._nodeMap.add(node.id, node);
},
_removeNode: function (node) {
Utils.remove(this.nodes, node);
this._nodeMap.remove(node.id);
},
removeNode: function (nodeOrId) {
var n = nodeOrId;
if (Utils.isString(nodeOrId)) {
n = this.getNode(nodeOrId);
}
if (Utils.isDefined(n)) {
var links = n.links;
n.links = [];
for (var i = 0, len = links.length; i < len; i++) {
var link = links[i];
this.removeLink(link);
}
this._removeNode(n);
} else {
throw 'The identifier should be a Node or the Id (string) of a node.';
}
},
areConnected: function (n1, n2) {
return Utils.any(this.links, function (link) {
return link.source == n1 && link.target == n2 || link.source == n2 && link.target == n1;
});
},
removeLink: function (link) {
Utils.remove(this.links, link);
Utils.remove(link.source.outgoing, link);
Utils.remove(link.source.links, link);
Utils.remove(link.target.incoming, link);
Utils.remove(link.target.links, link);
},
addNode: function (nodeOrId, layoutRect, owner) {
var newNode = null;
if (!Utils.isDefined(nodeOrId)) {
throw 'No Node or identifier for a new Node is given.';
}
if (Utils.isString(nodeOrId)) {
if (this.hasNode(nodeOrId)) {
return this.getNode(nodeOrId);
}
newNode = new Node(nodeOrId);
} else {
if (this.hasNode(nodeOrId)) {
return this.getNode(nodeOrId);
}
newNode = nodeOrId;
}
if (Utils.isDefined(layoutRect)) {
newNode.bounds(layoutRect);
}
if (Utils.isDefined(owner)) {
newNode.owner = owner;
}
this._addNode(newNode);
return newNode;
},
addNodeAndOutgoings: function (node) {
if (!this.hasNode(node)) {
this._addNode(node);
}
var newLinks = node.outgoing;
node.outgoing = [];
Utils.forEach(newLinks, function (link) {
this.addExistingLink(link);
}, this);
},
setItemIndices: function () {
var i;
for (i = 0; i < this.nodes.length; ++i) {
this.nodes[i].index = i;
}
for (i = 0; i < this.links.length; ++i) {
this.links[i].index = i;
}
},
clone: function (saveMapping) {
var copy = new Graph();
var save = Utils.isDefined(saveMapping) && saveMapping === true;
if (save) {
copy.nodeMap = new Dictionary();
copy.linkMap = new Dictionary();
}
var map = new Dictionary();
Utils.forEach(this.nodes, function (nOriginal) {
var nCopy = nOriginal.clone();
map.set(nOriginal, nCopy);
copy._addNode(nCopy);
if (save) {
copy.nodeMap.set(nCopy, nOriginal);
}
});
Utils.forEach(this.links, function (linkOriginal) {
if (map.containsKey(linkOriginal.source) && map.containsKey(linkOriginal.target)) {
var linkCopy = copy.addLink(map.get(linkOriginal.source), map.get(linkOriginal.target));
if (save) {
copy.linkMap.set(linkCopy, linkOriginal);
}
}
});
return copy;
},
linearize: function (addIds) {
return Graph.Utils.linearize(this, addIds);
},
depthFirstTraversal: function (startNode, action) {
if (Utils.isUndefined(startNode)) {
throw 'You need to supply a starting node.';
}
if (Utils.isUndefined(action)) {
throw 'You need to supply an action.';
}
if (!this.hasNode(startNode)) {
throw 'The given start-node is not part of this graph';
}
var foundNode = this.getNode(startNode);
var visited = [];
this._dftIterator(foundNode, action, visited);
},
_dftIterator: function (node, action, visited) {
action(node);
visited.push(node);
var children = node.getChildren();
for (var i = 0, len = children.length; i < len; i++) {
var child = children[i];
if (contains(visited, child)) {
continue;
}
this._dftIterator(child, action, visited);
}
},
breadthFirstTraversal: function (startNode, action) {
if (Utils.isUndefined(startNode)) {
throw 'You need to supply a starting node.';
}
if (Utils.isUndefined(action)) {
throw 'You need to supply an action.';
}
if (!this.hasNode(startNode)) {
throw 'The given start-node is not part of this graph';
}
var foundNode = this.getNode(startNode);
var queue = new Queue();
var visited = [];
queue.enqueue(foundNode);
while (queue.length > 0) {
var node = queue.dequeue();
action(node);
visited.push(node);
var children = node.getChildren();
for (var i = 0, len = children.length; i < len; i++) {
var child = children[i];
if (contains(visited, child) || contains(queue, child)) {
continue;
}
queue.enqueue(child);
}
}
},
_stronglyConnectedComponents: function (excludeSingleItems, node, indices, lowLinks, connected, stack, index) {
indices.add(node, index);
lowLinks.add(node, index);
index++;
stack.push(node);
var children = node.getChildren(), next;
for (var i = 0, len = children.length; i < len; i++) {
next = children[i];
if (!indices.containsKey(next)) {
this._stronglyConnectedComponents(excludeSingleItems, next, indices, lowLinks, connected, stack, index);
lowLinks.add(node, Math.min(lowLinks.get(node), lowLinks.get(next)));
} else if (contains(stack, next)) {
lowLinks.add(node, Math.min(lowLinks.get(node), indices.get(next)));
}
}
if (lowLinks.get(node) === indices.get(node)) {
var component = [];
do {
next = stack.pop();
component.push(next);
} while (next !== node);
if (!excludeSingleItems || component.length > 1) {
connected.push(component);
}
}
},
findCycles: function (excludeSingleItems) {
if (Utils.isUndefined(excludeSingleItems)) {
excludeSingleItems = true;
}
var indices = new Dictionary();
var lowLinks = new Dictionary();
var connected = [];
var stack = [];
for (var i = 0, len = this.nodes.length; i < len; i++) {
var node = this.nodes[i];
if (indices.containsKey(node)) {
continue;
}
this._stronglyConnectedComponents(excludeSingleItems, node, indices, lowLinks, connected, stack, 0);
}
return connected;
},
isAcyclic: function () {
return Utils.isEmpty(this.findCycles());
},
isSubGraph: function (other) {
var otherArray = other.linearize();
var thisArray = this.linearize();
return Utils.all(otherArray, function (s) {
return contains(thisArray, s);
});
},
makeAcyclic: function () {
if (this.isEmpty() || this.nodes.length <= 1 || this.links.length <= 1) {
return [];
}
if (this.nodes.length == 2) {
var result = [];
if (this.links.length > 1) {
var oneLink = this.links[0];
var oneNode = oneLink.source;
for (var i = 0, len = this.links.length; i < len; i++) {
var link = this.links[i];
if (link.source == oneNode) {
continue;
}
var rev = link.reverse();
result.push(rev);
}
}
return result;
}
var copy = this.clone(true);
var N = this.nodes.length;
var intensityCatalog = new Dictionary();
var flowIntensity = function (node) {
if (node.outgoing.length === 0) {
return 2 - N;
} else if (node.incoming.length === 0) {
return N - 2;
} else {
return node.outgoing.length - node.incoming.length;
}
};
var catalogEqualIntensity = function (node, intensityCatalog) {
var intensity = flowIntensity(node, N);
if (!intensityCatalog.containsKey(intensity)) {
intensityCatalog.set(intensity, []);
}
intensityCatalog.get(intensity).push(node);
};
Utils.forEach(copy.nodes, function (v) {
catalogEqualIntensity(v, intensityCatalog);
});
var sourceStack = [];
var targetStack = [];
while (copy.nodes.length > 0) {
var source, target, intensity;
if (intensityCatalog.containsKey(2 - N)) {
var targets = intensityCatalog.get(2 - N);
while (targets.length > 0) {
target = targets.pop();
for (var li = 0; li < target.links.length; li++) {
var targetLink = target.links[li];
source = targetLink.getComplement(target);
intensity = flowIntensity(source, N);
Utils.remove(intensityCatalog.get(intensity), source);
source.removeLink(targetLink);
catalogEqualIntensity(source, intensityCatalog);
}
copy._removeNode(target);
targetStack.unshift(target);
}
}
if (intensityCatalog.containsKey(N - 2)) {
var sources = intensityCatalog.get(N - 2);
while (sources.length > 0) {
source = sources.pop();
for (var si = 0; si < source.links.length; si++) {
var sourceLink = source.links[si];
target = sourceLink.getComplement(source);
intensity = flowIntensity(target, N);
Utils.remove(intensityCatalog.get(intensity), target);
target.removeLink(sourceLink);
catalogEqualIntensity(target, intensityCatalog);
}
sourceStack.push(source);
copy._removeNode(source);
}
}
if (copy.nodes.length > 0) {
for (var k = N - 3; k > 2 - N; k--) {
if (intensityCatalog.containsKey(k) && intensityCatalog.get(k).length > 0) {
var maxdiff = intensityCatalog.get(k);
var v = maxdiff.pop();
for (var ri = 0; ri < v.links.length; ri++) {
var ril = v.links[ri];
var u = ril.getComplement(v);
intensity = flowIntensity(u, N);
Utils.remove(intensityCatalog.get(intensity), u);
u.removeLink(ril);
catalogEqualIntensity(u, intensityCatalog);
}
sourceStack.push(v);
copy._removeNode(v);
break;
}
}
}
}
sourceStack = sourceStack.concat(targetStack);
var vertexOrder = new Dictionary();
for (var kk = 0; kk < this.nodes.length; kk++) {
vertexOrder.set(copy.nodeMap.get(sourceStack[kk]), kk);
}
var reversedEdges = [];
Utils.forEach(this.links, function (link) {
if (vertexOrder.get(link.source) > vertexOrder.get(link.target)) {
link.reverse();
reversedEdges.push(link);
}
});
return reversedEdges;
}
});
Graph.Predefined = {
EightGraph: function () {
return Graph.Utils.parse([
'1->2',
'2->3',
'3->4',
'4->1',
'3->5',
'5->6',
'6->7',
'7->3'
]);
},
Mindmap: function () {
return Graph.Utils.parse([
'0->1',
'0->2',
'0->3',
'0->4',
'0->5',
'1->6',
'1->7',
'7->8',
'2->9',
'9->10',
'9->11',
'3->12',
'12->13',
'13->14',
'4->15',
'4->16',
'15->17',
'15->18',
'18->19',
'18->20',
'14->21',
'14->22',
'5->23',
'23->24',
'23->25',
'6->26'
]);
},
ThreeGraph: function () {
return Graph.Utils.parse([
'1->2',
'2->3',
'3->1'
]);
},
BinaryTree: function (levels) {
if (Utils.isUndefined(levels)) {
levels = 5;
}
return Graph.Utils.createBalancedTree(levels, 2);
},
Linear: function (length) {
if (Utils.isUndefined(length)) {
length = 10;
}
return Graph.Utils.createBalancedTree(length, 1);
},
Tree: function (levels, siblingsCount) {
return Graph.Utils.createBalancedTree(levels, siblingsCount);
},
Forest: function (levels, siblingsCount, trees) {
return Graph.Utils.createBalancedForest(levels, siblingsCount, trees);
},
Workflow: function () {
return Graph.Utils.parse([
'0->1',
'1->2',
'2->3',
'1->4',
'4->3',
'3->5',
'5->6',
'6->3',
'6->7',
'5->4'
]);
},
Grid: function (n, m) {
var g = new diagram.Graph();
if (n <= 0 && m <= 0) {
return g;
}
for (var i = 0; i < n + 1; i++) {
var previous = null;
for (var j = 0; j < m + 1; j++) {
var node = new Node(i.toString() + '.' + j.toString());
g.addNode(node);
if (previous) {
g.addLink(previous, node);
}
if (i > 0) {
var left = g.getNode((i - 1).toString() + '.' + j.toString());
g.addLink(left, node);
}
previous = node;
}
}
return g;
}
};
Graph.Utils = {
parse: function (graphString) {
var previousLink, graph = new diagram.Graph(), parts = graphString.slice();
for (var i = 0, len = parts.length; i < len; i++) {
var part = parts[i];
if (Utils.isString(part)) {
if (part.indexOf('->') < 0) {
throw 'The link should be specified as \'a->b\'.';
}
var p = part.split('->');
if (p.length != 2) {
throw 'The link should be specified as \'a->b\'.';
}
previousLink = new Link(p[0], p[1]);
graph.addLink(previousLink);
}
if (Utils.isObject(part)) {
if (!previousLink) {
throw 'Specification found before Link definition.';
}
kendo.deepExtend(previousLink, part);
}
}
return graph;
},
linearize: function (graph, addIds) {
if (Utils.isUndefined(graph)) {
throw 'Expected an instance of a Graph object in slot one.';
}
if (Utils.isUndefined(addIds)) {
addIds = false;
}
var lin = [];
for (var i = 0, len = graph.links.length; i < len; i++) {
var link = graph.links[i];
lin.push(link.source.id + '->' + link.target.id);
if (addIds) {
lin.push({ id: link.id });
}
}
return lin;
},
_addShape: function (kendoDiagram, p, id, shapeDefaults) {
if (Utils.isUndefined(p)) {
p = new diagram.Point(0, 0);
}
if (Utils.isUndefined(id)) {
id = randomId();
}
shapeDefaults = kendo.deepExtend({
width: 20,
height: 20,
id: id,
radius: 10,
fill: '#778899',
data: 'circle',
undoable: false,
x: p.x,
y: p.y
}, shapeDefaults);
return kendoDiagram.addShape(shapeDefaults);
},
_addConnection: function (diagram, from, to, options) {
return diagram.connect(from, to, options);
},
createDiagramFromGraph: function (diagram, graph, doLayout, randomSize) {
if (Utils.isUndefined(diagram)) {
throw 'The diagram surface is undefined.';
}
if (Utils.isUndefined(graph)) {
throw 'No graph specification defined.';
}
if (Utils.isUndefined(doLayout)) {
doLayout = true;
}
if (Utils.isUndefined(randomSize)) {
randomSize = false;
}
var width = diagram.element.clientWidth || 200;
var height = diagram.element.clientHeight || 200;
var map = [], node, shape;
for (var i = 0, len = graph.nodes.length; i < len; i++) {
node = graph.nodes[i];
var p = node.position;
if (Utils.isUndefined(p)) {
if (Utils.isDefined(node.x) && Utils.isDefined(node.y)) {
p = new Point(node.x, node.y);
} else {
p = new Point(Utils.randomInteger(10, width - 20), Utils.randomInteger(10, height - 20));
}
}
var opt = {};
if (node.id === '0') {
} else if (randomSize) {
kendo.deepExtend(opt, {
width: Math.random() * 150 + 20,
height: Math.random() * 80 + 50,
data: 'rectangle',
fill: { color: '#778899' }
});
}
shape = this._addShape(diagram, p, node.id, opt);
var bounds = shape.bounds();
if (Utils.isDefined(bounds)) {
node.x = bounds.x;
node.y = bounds.y;
node.width = bounds.width;
node.height = bounds.height;
}
map[node.id] = shape;
}
for (var gli = 0; gli < graph.links.length; gli++) {
var link = graph.links[gli];
var sourceShape = map[link.source.id];
if (Utils.isUndefined(sourceShape)) {
continue;
}
var targetShape = map[link.target.id];
if (Utils.isUndefined(targetShape)) {
continue;
}
this._addConnection(diagram, sourceShape, targetShape, { id: link.id });
}
if (doLayout) {
var l = new diagram.SpringLayout(diagram);
l.layoutGraph(graph, { limitToView: false });
for (var shi = 0; shi < graph.nodes.length; shi++) {
node = graph.nodes[shi];
shape = map[node.id];
shape.bounds(new Rect(node.x, node.y, node.width, node.height));
}
}
},
createBalancedTree: function (levels, siblingsCount) {
if (Utils.isUndefined(levels)) {
levels = 3;
}
if (Utils.isUndefined(siblingsCount)) {
siblingsCount = 3;
}
var g = new diagram.Graph(), counter = -1, lastAdded = [], news;
if (levels <= 0 || siblingsCount <= 0) {
return g;
}
var root = new Node((++counter).toString());
g.addNode(root);
g.root = root;
lastAdded.push(root);
for (var i = 0; i < levels; i++) {
news = [];
for (var j = 0; j < lastAdded.length; j++) {
var parent = lastAdded[j];
for (var k = 0; k < siblingsCount; k++) {
var item = new Node((++counter).toString());
g.addLink(parent, item);
news.push(item);
}
}
lastAdded = news;
}
return g;
},
createBalancedForest: function (levels, siblingsCount, treeCount) {
if (Utils.isUndefined(levels)) {
levels = 3;
}
if (Utils.isUndefined(siblingsCount)) {
siblingsCount = 3;
}
if (Utils.isUndefined(treeCount)) {
treeCount = 5;
}
var g = new diagram.Graph(), counter = -1, lastAdded = [], news;
if (levels <= 0 || siblingsCount <= 0 || treeCount <= 0) {
return g;
}
for (var t = 0; t < treeCount; t++) {
var root = new Node((++counter).toString());
g.addNode(root);
lastAdded = [root];
for (var i = 0; i < levels; i++) {
news = [];
for (var j = 0; j < lastAdded.length; j++) {
var parent = lastAdded[j];
for (var k = 0; k < siblingsCount; k++) {
var item = new Node((++counter).toString());
g.addLink(parent, item);
news.push(item);
}
}
lastAdded = news;
}
}
return g;
},
createRandomConnectedGraph: function (nodeCount, maxIncidence, isTree) {
if (Utils.isUndefined(nodeCount)) {
nodeCount = 40;
}
if (Utils.isUndefined(maxIncidence)) {
maxIncidence = 4;
}
if (Utils.isUndefined(isTree)) {
isTree = false;
}
var g = new diagram.Graph(), counter = -1;
if (nodeCount <= 0) {
return g;
}
var root = new Node((++counter).toString());
g.addNode(root);
if (nodeCount === 1) {
return g;
}
if (nodeCount > 1) {
for (var i = 1; i < nodeCount; i++) {
var poolNode = g.takeRandomNode([], maxIncidence);
if (!poolNode) {
break;
}
var newNode = g.addNode(i.toString());
g.addLink(poolNode, newNode);
}
if (!isTree && nodeCount > 1) {
var randomAdditions = Utils.randomInteger(1, nodeCount);
for (var ri = 0; ri < randomAdditions; ri++) {
var n1 = g.takeRandomNode([], maxIncidence);
var n2 = g.takeRandomNode([], maxIncidence);
if (n1 && n2 && !g.areConnected(n1, n2)) {
g.addLink(n1, n2);
}
}
}
return g;
}
},
randomDiagram: function (diagram, shapeCount, maxIncidence, isTree, randomSize) {
var g = kendo.dataviz.diagram.Graph.Utils.createRandomConnectedGraph(shapeCount, maxIncidence, isTree);
Graph.Utils.createDiagramFromGraph(diagram, g, false, randomSize);
}
};
kendo.deepExtend(diagram, {
init: function (element) {
kendo.init(element, diagram.ui);
},
Point: Point,
Intersect: Intersect,
Geometry: Geometry,
Rect: Rect,
Size: Size,
RectAlign: RectAlign,
Matrix: Matrix,
MatrixVector: MatrixVector,
normalVariable: normalVariable,
randomId: randomId,
Dictionary: Dictionary,
HashTable: HashTable,
Queue: Queue,
Set: Set,
Node: Node,
Link: Link,
Graph: Graph,
PathDefiner: PathDefiner
});
}(window.kendo.jQuery));
}, typeof define == 'function' && define.amd ? define : function (a1, a2, a3) {
(a3 || a2)();
}));
(function (f, define) {
define('dataviz/diagram/svg', [
'kendo.drawing',
'dataviz/diagram/math'
], f);
}(function () {
(function ($, undefined) {
var kendo = window.kendo, diagram = kendo.dataviz.diagram, Class = kendo.Class, deepExtend = kendo.deepExtend, Point = diagram.Point, Rect = diagram.Rect, Matrix = diagram.Matrix, Utils = diagram.Utils, isNumber = Utils.isNumber, isString = Utils.isString, MatrixVector = diagram.MatrixVector, g = kendo.geometry, d = kendo.drawing, defined = kendo.util.defined, inArray = $.inArray;
var TRANSPARENT = 'transparent', Markers = {
none: 'none',
arrowStart: 'ArrowStart',
filledCircle: 'FilledCircle',
arrowEnd: 'ArrowEnd'
}, FULL_CIRCLE_ANGLE = 360, START = 'start', END = 'end', WIDTH = 'width', HEIGHT = 'height', X = 'x', Y = 'y';
diagram.Markers = Markers;
function diffNumericOptions(options, fields) {
var elementOptions = this.options;
var hasChanges = false;
var value, field;
for (var i = 0; i < fields.length; i++) {
field = fields[i];
value = options[field];
if (isNumber(value) && elementOptions[field] !== value) {
elementOptions[field] = value;
hasChanges = true;
}
}
return hasChanges;
}
var Scale = Class.extend({
init: function (x, y) {
this.x = x;
this.y = y;
},
toMatrix: function () {
return Matrix.scaling(this.x, this.y);
},
toString: function () {
return kendo.format('scale({0},{1})', this.x, this.y);
},
invert: function () {
return new Scale(1 / this.x, 1 / this.y);
}
});
var Translation = Class.extend({
init: function (x, y) {
this.x = x;
this.y = y;
},
toMatrixVector: function () {
return new MatrixVector(0, 0, 0, 0, this.x, this.y);
},
toMatrix: function () {
return Matrix.translation(this.x, this.y);
},
toString: function () {
return kendo.format('translate({0},{1})', this.x, this.y);
},
plus: function (delta) {
this.x += delta.x;
this.y += delta.y;
},
times: function (factor) {
this.x *= factor;
this.y *= factor;
},
length: function () {
return Math.sqrt(this.x * this.x + this.y * this.y);
},
normalize: function () {
if (this.Length === 0) {
return;
}
this.times(1 / this.length());
},
invert: function () {
return new Translation(-this.x, -this.y);
}
});
var Rotation = Class.extend({
init: function (angle, x, y) {
this.x = x || 0;
this.y = y || 0;
this.angle = angle;
},
toString: function () {
if (this.x && this.y) {
return kendo.format('rotate({0},{1},{2})', this.angle, this.x, this.y);
} else {
return kendo.format('rotate({0})', this.angle);
}
},
toMatrix: function () {
return Matrix.rotation(this.angle, this.x, this.y);
},
center: function () {
return new Point(this.x, this.y);
},
invert: function () {
return new Rotation(FULL_CIRCLE_ANGLE - this.angle, this.x, this.y);
}
});
Rotation.ZERO = new Rotation(0);
Rotation.create = function (rotation) {
return new Rotation(rotation.angle, rotation.x, rotation.y);
};
Rotation.parse = function (str) {
var values = str.slice(1, str.length - 1).split(','), angle = values[0], x = values[1], y = values[2];
var rotation = new Rotation(angle, x, y);
return rotation;
};
var CompositeTransform = Class.extend({
init: function (x, y, scaleX, scaleY, angle, center) {
this.translate = new Translation(x, y);
if (scaleX !== undefined && scaleY !== undefined) {
this.scale = new Scale(scaleX, scaleY);
}
if (angle !== undefined) {
this.rotate = center ? new Rotation(angle, center.x, center.y) : new Rotation(angle);
}
},
toString: function () {
var toString = function (transform) {
return transform ? transform.toString() : '';
};
return toString(this.translate) + toString(this.rotate) + toString(this.scale);
},
render: function (visual) {
visual._transform = this;
visual._renderTransform();
},
toMatrix: function () {
var m = Matrix.unit();
if (this.translate) {
m = m.times(this.translate.toMatrix());
}
if (this.rotate) {
m = m.times(this.rotate.toMatrix());
}
if (this.scale) {
m = m.times(this.scale.toMatrix());
}
return m;
},
invert: function () {
var rotate = this.rotate ? this.rotate.invert() : undefined, rotateMatrix = rotate ? rotate.toMatrix() : Matrix.unit(), scale = this.scale ? this.scale.invert() : undefined, scaleMatrix = scale ? scale.toMatrix() : Matrix.unit();
var translatePoint = new Point(-this.translate.x, -this.translate.y);
translatePoint = rotateMatrix.times(scaleMatrix).apply(translatePoint);
var translate = new Translation(translatePoint.x, translatePoint.y);
var transform = new CompositeTransform();
transform.translate = translate;
transform.rotate = rotate;
transform.scale = scale;
return transform;
}
});
var AutoSizeableMixin = {
_setScale: function () {
var options = this.options;
var originWidth = this._originWidth;
var originHeight = this._originHeight;
var scaleX = options.width / originWidth;
var scaleY = options.height / originHeight;
if (!isNumber(scaleX)) {
scaleX = 1;
}
if (!isNumber(scaleY)) {
scaleY = 1;
}
this._transform.scale = new Scale(scaleX, scaleY);
},
_setTranslate: function () {
var options = this.options;
var x = options.x || 0;
var y = options.y || 0;
this._transform.translate = new Translation(x, y);
},
_initSize: function () {
var options = this.options;
var transform = false;
if (options.autoSize !== false && (defined(options.width) || defined(options.height))) {
this._measure(true);
this._setScale();
transform = true;
}
if (defined(options.x) || defined(options.y)) {
this._setTranslate();
transform = true;
}
if (transform) {
this._renderTransform();
}
},
_updateSize: function (options) {
var update = false;
if (this.options.autoSize !== false && this._diffNumericOptions(options, [
WIDTH,
HEIGHT
])) {
update = true;
this._measure(true);
this._setScale();
}
if (this._diffNumericOptions(options, [
X,
Y
])) {
update = true;
this._setTranslate();
}
if (update) {
this._renderTransform();
}
return update;
}
};
var Element = Class.extend({
init: function (options) {
var element = this;
element.options = deepExtend({}, element.options, options);
element.id = element.options.id;
element._originSize = Rect.empty();
element._transform = new CompositeTransform();
},
visible: function (value) {
return this.drawingContainer().visible(value);
},
redraw: function (options) {
if (options && options.id) {
this.id = options.id;
}
},
position: function (x, y) {
var options = this.options;
if (!defined(x)) {
return new Point(options.x, options.y);
}
if (defined(y)) {
options.x = x;
options.y = y;
} else if (x instanceof Point) {
options.x = x.x;
options.y = x.y;
}
this._transform.translate = new Translation(options.x, options.y);
this._renderTransform();
},
rotate: function (angle, center) {
if (defined(angle)) {
this._transform.rotate = new Rotation(angle, center.x, center.y);
this._renderTransform();
}
return this._transform.rotate || Rotation.ZERO;
},
drawingContainer: function () {
return this.drawingElement;
},
_renderTransform: function () {
var matrix = this._transform.toMatrix();
this.drawingContainer().transform(new g.Matrix(matrix.a, matrix.b, matrix.c, matrix.d, matrix.e, matrix.f));
},
_hover: function () {
},
_diffNumericOptions: diffNumericOptions,
_measure: function (force) {
var rect;
if (!this._measured || force) {
var box = this._boundingBox() || new g.Rect();
var startPoint = box.topLeft();
rect = new Rect(startPoint.x, startPoint.y, box.width(), box.height());
this._originSize = rect;
this._originWidth = rect.width;
this._originHeight = rect.height;
this._measured = true;
} else {
rect = this._originSize;
}
return rect;
},
_boundingBox: function () {
return this.drawingElement.rawBBox();
}
});
var VisualBase = Element.extend({
init: function (options) {
Element.fn.init.call(this, options);
options = this.options;
options.fill = normalizeDrawingOptions(options.fill);
options.stroke = normalizeDrawingOptions(options.stroke);
},
options: {
stroke: {
color: 'gray',
width: 1
},
fill: { color: TRANSPARENT }
},
fill: function (color, opacity) {
this._fill({
color: getColor(color),
opacity: opacity
});
},
stroke: function (color, width, opacity) {
this._stroke({
color: getColor(color),
width: width,
opacity: opacity
});
},
redraw: function (options) {
if (options) {
var stroke = options.stroke;
var fill = options.fill;
if (stroke) {
this._stroke(normalizeDrawingOptions(stroke));
}
if (fill) {
this._fill(normalizeDrawingOptions(fill));
}
Element.fn.redraw.call(this, options);
}
},
_hover: function (show) {
var drawingElement = this.drawingElement;
var options = this.options;
var hover = options.hover;
if (hover && hover.fill) {
var fill = show ? normalizeDrawingOptions(hover.fill) : options.fill;
drawingElement.fill(fill.color, fill.opacity);
}
},
_stroke: function (strokeOptions) {
var options = this.options;
deepExtend(options, { stroke: strokeOptions });
strokeOptions = options.stroke;
var stroke = null;
if (strokeOptions.width > 0) {
stroke = {
color: strokeOptions.color,
width: strokeOptions.width,
opacity: strokeOptions.opacity,
dashType: strokeOptions.dashType
};
}
this.drawingElement.options.set('stroke', stroke);
},
_fill: function (fillOptions) {
var options = this.options;
deepExtend(options, { fill: fillOptions || {} });
var fill = options.fill;
if (fill.gradient) {
var gradient = fill.gradient;
var GradientClass = gradient.type === 'radial' ? d.RadialGradient : d.LinearGradient;
this.drawingElement.fill(new GradientClass(gradient));
} else {
this.drawingElement.fill(fill.color, fill.opacity);
}
}
});
var TextBlock = VisualBase.extend({
init: function (options) {
options = this._textColor(options);
VisualBase.fn.init.call(this, options);
this._font();
this._initText();
this._initSize();
},
options: {
fontSize: 15,
fontFamily: 'sans-serif',
stroke: { width: 0 },
fill: { color: 'black' },
autoSize: true
},
_initText: function () {
var options = this.options;
this.drawingElement = new d.Text(defined(options.text) ? options.text : '', new g.Point(), { font: options.font });
this._fill();
this._stroke();
},
_textColor: function (options) {
if (options && options.color) {
options = deepExtend({}, options, { fill: { color: options.color } });
}
return options;
},
_font: function () {
var options = this.options;
if (options.fontFamily && defined(options.fontSize)) {
options.font = options.fontSize + 'px ' + options.fontFamily;
} else {
delete options.font;
}
},
content: function (text) {
return this.drawingElement.content(text);
},
redraw: function (options) {
if (options) {
var sizeChanged = false;
var textOptions = this.options;
options = this._textColor(options);
VisualBase.fn.redraw.call(this, options);
if (options.fontFamily || defined(options.fontSize)) {
deepExtend(textOptions, {
fontFamily: options.fontFamily,
fontSize: options.fontSize
});
this._font();
this.drawingElement.options.set('font', textOptions.font);
sizeChanged = true;
}
if (options.text) {
this.content(options.text);
sizeChanged = true;
}
if (!this._updateSize(options) && sizeChanged) {
this._initSize();
}
}
}
});
deepExtend(TextBlock.fn, AutoSizeableMixin);
var Rectangle = VisualBase.extend({
init: function (options) {
VisualBase.fn.init.call(this, options);
this._initPath();
this._setPosition();
},
_setPosition: function () {
var options = this.options;
var x = options.x;
var y = options.y;
if (defined(x) || defined(y)) {
this.position(x || 0, y || 0);
}
},
redraw: function (options) {
if (options) {
VisualBase.fn.redraw.call(this, options);
if (this._diffNumericOptions(options, [
WIDTH,
HEIGHT
])) {
this._drawPath();
}
if (this._diffNumericOptions(options, [
X,
Y
])) {
this._setPosition();
}
}
},
_initPath: function () {
var options = this.options;
this.drawingElement = new d.Path({
stroke: options.stroke,
closed: true
});
this._fill();
this._drawPath();
},
_drawPath: function () {
var drawingElement = this.drawingElement;
var sizeOptions = sizeOptionsOrDefault(this.options);
var width = sizeOptions.width;
var height = sizeOptions.height;
drawingElement.segments.elements([
createSegment(0, 0),
createSegment(width, 0),
createSegment(width, height),
createSegment(0, height)
]);
}
});
var MarkerBase = VisualBase.extend({
init: function (options) {
VisualBase.fn.init.call(this, options);
var anchor = this.options.anchor;
this.anchor = new g.Point(anchor.x, anchor.y);
this.createElement();
},
options: {
stroke: {
color: TRANSPARENT,
width: 0
},
fill: { color: 'black' }
},
_transformToPath: function (point, path) {
var transform = path.transform();
if (point && transform) {
point = point.transformCopy(transform);
}
return point;
},
redraw: function (options) {
if (options) {
if (options.position) {
this.options.position = options.position;
}
VisualBase.fn.redraw.call(this, options);
}
}
});
var CircleMarker = MarkerBase.extend({
options: {
radius: 4,
anchor: {
x: 0,
y: 0
}
},
createElement: function () {
var options = this.options;
this.drawingElement = new d.Circle(new g.Circle(this.anchor, options.radius), {
fill: options.fill,
stroke: options.stroke
});
},
positionMarker: function (path) {
var options = this.options;
var position = options.position;
var segments = path.segments;
var targetSegment;
var point;
if (position == START) {
targetSegment = segments[0];
} else {
targetSegment = segments[segments.length - 1];
}
if (targetSegment) {
point = this._transformToPath(targetSegment.anchor(), path);
this.drawingElement.transform(g.transform().translate(point.x, point.y));
}
}
});
var ArrowMarker = MarkerBase.extend({
options: {
path: 'M 0 0 L 10 5 L 0 10 L 3 5 z',
anchor: {
x: 10,
y: 5
}
},
createElement: function () {
var options = this.options;
this.drawingElement = d.Path.parse(options.path, {
fill: options.fill,
stroke: options.stroke
});
},
positionMarker: function (path) {
var points = this._linePoints(path);
var start = points.start;
var end = points.end;
var transform = g.transform();
if (start) {
transform.rotate(lineAngle(start, end), end);
}
if (end) {
var anchor = this.anchor;
var translate = end.clone().translate(-anchor.x, -anchor.y);
transform.translate(translate.x, translate.y);
}
this.drawingElement.transform(transform);
},
_linePoints: function (path) {
var options = this.options;
var segments = path.segments;
var startPoint, endPoint, targetSegment;
if (options.position == START) {
targetSegment = segments[0];
if (targetSegment) {
endPoint = targetSegment.anchor();
startPoint = targetSegment.controlOut();
var nextSegment = segments[1];
if (!startPoint && nextSegment) {
startPoint = nextSegment.anchor();
}
}
} else {
targetSegment = segments[segments.length - 1];
if (targetSegment) {
endPoint = targetSegment.anchor();
startPoint = targetSegment.controlIn();
var prevSegment = segments[segments.length - 2];
if (!startPoint && prevSegment) {
startPoint = prevSegment.anchor();
}
}
}
if (endPoint) {
return {
start: this._transformToPath(startPoint, path),
end: this._transformToPath(endPoint, path)
};
}
}
});
var MarkerPathMixin = {
_getPath: function (position) {
var path = this.drawingElement;
if (path instanceof d.MultiPath) {
if (position == START) {
path = path.paths[0];
} else {
path = path.paths[path.paths.length - 1];
}
}
if (path && path.segments.length) {
return path;
}
},
_normalizeMarkerOptions: function (options) {
var startCap = options.startCap;
var endCap = options.endCap;
if (isString(startCap)) {
options.startCap = { type: startCap };
}
if (isString(endCap)) {
options.endCap = { type: endCap };
}
},
_removeMarker: function (position) {
var marker = this._markers[position];
if (marker) {
this.drawingContainer().remove(marker.drawingElement);
delete this._markers[position];
}
},
_createMarkers: function () {
var options = this.options;
this._normalizeMarkerOptions(options);
this._markers = {};
this._markers[START] = this._createMarker(options.startCap, START);
this._markers[END] = this._createMarker(options.endCap, END);
},
_createMarker: function (options, position) {
var type = (options || {}).type;
var path = this._getPath(position);
var markerType, marker;
if (!path) {
this._removeMarker(position);
return;
}
if (type == Markers.filledCircle) {
markerType = CircleMarker;
} else if (type == Markers.arrowStart || type == Markers.arrowEnd) {
markerType = ArrowMarker;
} else {
this._removeMarker(position);
}
if (markerType) {
marker = new markerType(deepExtend({}, options, { position: position }));
marker.positionMarker(path);
this.drawingContainer().append(marker.drawingElement);
return marker;
}
},
_positionMarker: function (position) {
var marker = this._markers[position];
if (marker) {
var path = this._getPath(position);
if (path) {
marker.positionMarker(path);
} else {
this._removeMarker(position);
}
}
},
_capMap: {
start: 'startCap',
end: 'endCap'
},
_redrawMarker: function (pathChange, position, options) {
this._normalizeMarkerOptions(options);
var pathOptions = this.options;
var cap = this._capMap[position];
var pathCapType = (pathOptions[cap] || {}).type;
var optionsCap = options[cap];
var created = false;
if (optionsCap) {
pathOptions[cap] = deepExtend({}, pathOptions[cap], optionsCap);
if (optionsCap.type && pathCapType != optionsCap.type) {
this._removeMarker(position);
this._markers[position] = this._createMarker(pathOptions[cap], position);
created = true;
} else if (this._markers[position]) {
this._markers[position].redraw(optionsCap);
}
} else if (pathChange && !this._markers[position] && pathOptions[cap]) {
this._markers[position] = this._createMarker(pathOptions[cap], position);
created = true;
}
return created;
},
_redrawMarkers: function (pathChange, options) {
if (!this._redrawMarker(pathChange, START, options) && pathChange) {
this._positionMarker(START);
}
if (!this._redrawMarker(pathChange, END, options) && pathChange) {
this._positionMarker(END);
}
}
};
var Path = VisualBase.extend({
init: function (options) {
VisualBase.fn.init.call(this, options);
this.container = new d.Group();
this._createElements();
this._initSize();
},
options: { autoSize: true },
drawingContainer: function () {
return this.container;
},
data: function (value) {
var options = this.options;
if (value) {
if (options.data != value) {
options.data = value;
this._setData(value);
this._initSize();
this._redrawMarkers(true, {});
}
} else {
return options.data;
}
},
redraw: function (options) {
if (options) {
VisualBase.fn.redraw.call(this, options);
var pathOptions = this.options;
var data = options.data;
if (defined(data) && pathOptions.data != data) {
pathOptions.data = data;
this._setData(data);
if (!this._updateSize(options)) {
this._initSize();
}
this._redrawMarkers(true, options);
} else {
this._updateSize(options);
this._redrawMarkers(false, options);
}
}
},
_createElements: function () {
var options = this.options;
this.drawingElement = d.Path.parse(options.data || '', { stroke: options.stroke });
this._fill();
this.container.append(this.drawingElement);
this._createMarkers();
},
_setData: function (data) {
var drawingElement = this.drawingElement;
var multipath = d.Path.parse(data || '');
var paths = multipath.paths.slice(0);
multipath.paths.elements([]);
drawingElement.paths.elements(paths);
}
});
deepExtend(Path.fn, AutoSizeableMixin);
deepExtend(Path.fn, MarkerPathMixin);
var Line = VisualBase.extend({
init: function (options) {
VisualBase.fn.init.call(this, options);
this.container = new d.Group();
this._initPath();
this._createMarkers();
},
drawingContainer: function () {
return this.container;
},
redraw: function (options) {
if (options) {
options = options || {};
var from = options.from;
var to = options.to;
if (from) {
this.options.from = from;
}
if (to) {
this.options.to = to;
}
if (from || to) {
this._drawPath();
this._redrawMarkers(true, options);
} else {
this._redrawMarkers(false, options);
}
VisualBase.fn.redraw.call(this, options);
}
},
_initPath: function () {
var options = this.options;
var drawingElement = this.drawingElement = new d.Path({ stroke: options.stroke });
this._fill();
this._drawPath();
this.container.append(drawingElement);
},
_drawPath: function () {
var options = this.options;
var drawingElement = this.drawingElement;
var from = options.from || new Point();
var to = options.to || new Point();
drawingElement.segments.elements([
createSegment(from.x, from.y),
createSegment(to.x, to.y)
]);
}
});
deepExtend(Line.fn, MarkerPathMixin);
var Polyline = VisualBase.extend({
init: function (options) {
VisualBase.fn.init.call(this, options);
this.container = new d.Group();
this._initPath();
this._createMarkers();
},
drawingContainer: function () {
return this.container;
},
points: function (points) {
var options = this.options;
if (points) {
options.points = points;
this._updatePath();
} else {
return options.points;
}
},
redraw: function (options) {
if (options) {
var points = options.points;
VisualBase.fn.redraw.call(this, options);
if (points && this._pointsDiffer(points)) {
this.points(points);
this._redrawMarkers(true, options);
} else {
this._redrawMarkers(false, options);
}
}
},
_initPath: function () {
var options = this.options;
this.drawingElement = new d.Path({ stroke: options.stroke });
this._fill();
this.container.append(this.drawingElement);
if (options.points) {
this._updatePath();
}
},
_pointsDiffer: function (points) {
var currentPoints = this.options.points;
var differ = currentPoints.length !== points.length;
if (!differ) {
for (var i = 0; i < points.length; i++) {
if (currentPoints[i].x !== points[i].x || currentPoints[i].y !== points[i].y) {
differ = true;
break;
}
}
}
return differ;
},
_updatePath: function () {
var drawingElement = this.drawingElement;
var options = this.options;
var points = options.points;
var segments = [];
var point;
for (var i = 0; i < points.length; i++) {
point = points[i];
segments.push(createSegment(point.x, point.y));
}
drawingElement.segments.elements(segments);
},
options: { points: [] }
});
deepExtend(Polyline.fn, MarkerPathMixin);
var Image = Element.extend({
init: function (options) {
Element.fn.init.call(this, options);
this._initImage();
},
redraw: function (options) {
if (options) {
if (options.source) {
this.drawingElement.src(options.source);
}
if (this._diffNumericOptions(options, [
WIDTH,
HEIGHT,
X,
Y
])) {
this.drawingElement.rect(this._rect());
}
Element.fn.redraw.call(this, options);
}
},
_initImage: function () {
var options = this.options;
var rect = this._rect();
this.drawingElement = new d.Image(options.source, rect, {});
},
_rect: function () {
var sizeOptions = sizeOptionsOrDefault(this.options);
var origin = new g.Point(sizeOptions.x, sizeOptions.y);
var size = new g.Size(sizeOptions.width, sizeOptions.height);
return new g.Rect(origin, size);
}
});
var Group = Element.extend({
init: function (options) {
this.children = [];
Element.fn.init.call(this, options);
this.drawingElement = new d.Group();
this._initSize();
},
options: { autoSize: false },
append: function (visual) {
this.drawingElement.append(visual.drawingContainer());
this.children.push(visual);
this._childrenChange = true;
},
remove: function (visual) {
if (this._remove(visual)) {
this._childrenChange = true;
}
},
_remove: function (visual) {
var index = inArray(visual, this.children);
if (index >= 0) {
this.drawingElement.removeAt(index);
this.children.splice(index, 1);
return true;
}
},
clear: function () {
this.drawingElement.clear();
this.children = [];
this._childrenChange = true;
},
toFront: function (visuals) {
var visual;
for (var i = 0; i < visuals.length; i++) {
visual = visuals[i];
if (this._remove(visual)) {
this.append(visual);
}
}
},
toBack: function (visuals) {
this._reorderChildren(visuals, 0);
},
toIndex: function (visuals, indices) {
this._reorderChildren(visuals, indices);
},
_reorderChildren: function (visuals, indices) {
var group = this.drawingElement;
var drawingChildren = group.children.slice(0);
var children = this.children;
var fixedPosition = isNumber(indices);
var i, index, toIndex, drawingElement, visual;
for (i = 0; i < visuals.length; i++) {
visual = visuals[i];
drawingElement = visual.drawingContainer();
index = inArray(visual, children);
if (index >= 0) {
drawingChildren.splice(index, 1);
children.splice(index, 1);
toIndex = fixedPosition ? indices : indices[i];
drawingChildren.splice(toIndex, 0, drawingElement);
children.splice(toIndex, 0, visual);
}
}
group.clear();
group.append.apply(group, drawingChildren);
},
redraw: function (options) {
if (options) {
if (this._childrenChange) {
this._childrenChange = false;
if (!this._updateSize(options)) {
this._initSize();
}
} else {
this._updateSize(options);
}
Element.fn.redraw.call(this, options);
}
},
_boundingBox: function () {
var children = this.children;
var boundingBox;
var visual, childBoundingBox;
for (var i = 0; i < children.length; i++) {
visual = children[i];
if (visual.visible() && visual._includeInBBox !== false) {
childBoundingBox = visual.drawingContainer().clippedBBox(null);
if (childBoundingBox) {
if (boundingBox) {
boundingBox = g.Rect.union(boundingBox, childBoundingBox);
} else {
boundingBox = childBoundingBox;
}
}
}
}
return boundingBox;
}
});
deepExtend(Group.fn, AutoSizeableMixin);
var Layout = Group.extend({
init: function (rect, options) {
this.children = [];
Element.fn.init.call(this, options);
this.drawingElement = new d.Layout(toDrawingRect(rect), options);
this._initSize();
},
rect: function (rect) {
if (rect) {
this.drawingElement.rect(toDrawingRect(rect));
} else {
var drawingRect = this.drawingElement.rect();
if (drawingRect) {
return new Rect(drawingRect.origin.x, drawingRect.origin.y, drawingRect.size.width, drawingRect.size.height);
}
}
},
reflow: function () {
this.drawingElement.reflow();
},
redraw: function (options) {
kendo.deepExtend(this.drawingElement.options, options);
Group.fn.redraw.call(this, options);
}
});
var Circle = VisualBase.extend({
init: function (options) {
VisualBase.fn.init.call(this, options);
this._initCircle();
this._initSize();
},
redraw: function (options) {
if (options) {
var circleOptions = this.options;
if (options.center) {
deepExtend(circleOptions, { center: options.center });
this._center.move(circleOptions.center.x, circleOptions.center.y);
}
if (this._diffNumericOptions(options, ['radius'])) {
this._circle.setRadius(circleOptions.radius);
}
this._updateSize(options);
VisualBase.fn.redraw.call(this, options);
}
},
_initCircle: function () {
var options = this.options;
var width = options.width;
var height = options.height;
var radius = options.radius;
if (!defined(radius)) {
if (!defined(width)) {
width = height;
}
if (!defined(height)) {
height = width;
}
options.radius = radius = Math.min(width, height) / 2;
}
var center = options.center || {
x: radius,
y: radius
};
this._center = new g.Point(center.x, center.y);
this._circle = new g.Circle(this._center, radius);
this.drawingElement = new d.Circle(this._circle, { stroke: options.stroke });
this._fill();
}
});
deepExtend(Circle.fn, AutoSizeableMixin);
var Canvas = Class.extend({
init: function (element, options) {
options = options || {};
this.element = element;
this.surface = d.Surface.create(element, options);
if (kendo.isFunction(this.surface.translate)) {
this.translate = this._translate;
}
this.drawingElement = new d.Group();
this._viewBox = new Rect(0, 0, options.width, options.height);
this.size(this._viewBox);
},
bounds: function () {
var box = this.drawingElement.clippedBBox();
return new Rect(0, 0, box.width(), box.height());
},
size: function (size) {
var viewBox = this._viewBox;
if (defined(size)) {
viewBox.width = size.width;
viewBox.height = size.height;
this.surface.setSize(size);
}
return {
width: viewBox.width,
height: viewBox.height
};
},
_translate: function (x, y) {
var viewBox = this._viewBox;
if (defined(x) && defined(y)) {
viewBox.x = x;
viewBox.y = y;
this.surface.translate({
x: x,
y: y
});
}
return {
x: viewBox.x,
y: viewBox.y
};
},
draw: function () {
this.surface.draw(this.drawingElement);
},
append: function (visual) {
this.drawingElement.append(visual.drawingContainer());
return this;
},
remove: function (visual) {
this.drawingElement.remove(visual.drawingContainer());
},
insertBefore: function () {
},
clear: function () {
this.drawingElement.clear();
},
destroy: function (clearHtml) {
this.surface.destroy();
if (clearHtml) {
$(this.element).remove();
}
}
});
function sizeOptionsOrDefault(options) {
return {
x: options.x || 0,
y: options.y || 0,
width: options.width || 0,
height: options.height || 0
};
}
function normalizeDrawingOptions(options) {
if (options) {
var drawingOptions = options;
if (isString(drawingOptions)) {
drawingOptions = { color: drawingOptions };
}
if (drawingOptions.color) {
drawingOptions.color = getColor(drawingOptions.color);
}
return drawingOptions;
}
}
function getColor(value) {
var color;
if (value != TRANSPARENT) {
color = new d.Color(value).toHex();
} else {
color = value;
}
return color;
}
function lineAngle(p1, p2) {
var xDiff = p2.x - p1.x;
var yDiff = p2.y - p1.y;
var angle = kendo.util.deg(Math.atan2(yDiff, xDiff));
return angle;
}
function createSegment(x, y) {
return new d.Segment(new g.Point(x, y));
}
function toDrawingRect(rect) {
if (rect) {
return new g.Rect([
rect.x,
rect.y
], [
rect.width,
rect.height
]);
}
}
kendo.deepExtend(diagram, {
init: function (element) {
kendo.init(element, diagram.ui);
},
diffNumericOptions: diffNumericOptions,
Element: Element,
Scale: Scale,
Translation: Translation,
Rotation: Rotation,
Circle: Circle,
Group: Group,
Rectangle: Rectangle,
Canvas: Canvas,
Path: Path,
Layout: Layout,
Line: Line,
MarkerBase: MarkerBase,
ArrowMarker: ArrowMarker,
CircleMarker: CircleMarker,
Polyline: Polyline,
CompositeTransform: CompositeTransform,
TextBlock: TextBlock,
Image: Image,
VisualBase: VisualBase
});
}(window.kendo.jQuery));
}, typeof define == 'function' && define.amd ? define : function (a1, a2, a3) {
(a3 || a2)();
}));
(function (f, define) {
define('dataviz/diagram/services', [
'kendo.drawing',
'dataviz/diagram/svg'
], f);
}(function () {
(function ($, undefined) {
var kendo = window.kendo, dataviz = kendo.dataviz, diagram = dataviz.diagram, Class = kendo.Class, Group = diagram.Group, Rect = diagram.Rect, Rectangle = diagram.Rectangle, Utils = diagram.Utils, isUndefined = Utils.isUndefined, Point = diagram.Point, Circle = diagram.Circle, Ticker = diagram.Ticker, deepExtend = kendo.deepExtend, Movable = kendo.ui.Movable, browser = kendo.support.browser, defined = kendo.util.defined, inArray = $.inArray, proxy = $.proxy;
var Cursors = {
arrow: 'default',
grip: 'pointer',
cross: 'pointer',
add: 'pointer',
move: 'move',
select: 'pointer',
south: 's-resize',
east: 'e-resize',
west: 'w-resize',
north: 'n-resize',
rowresize: 'row-resize',
colresize: 'col-resize'
}, HIT_TEST_DISTANCE = 10, AUTO = 'Auto', TOP = 'Top', RIGHT = 'Right', LEFT = 'Left', BOTTOM = 'Bottom', DEFAULT_SNAP_SIZE = 10, DEFAULT_SNAP_ANGLE = 10, DRAG_START = 'dragStart', DRAG = 'drag', DRAG_END = 'dragEnd', ITEMROTATE = 'itemRotate', ITEMBOUNDSCHANGE = 'itemBoundsChange', MIN_SNAP_SIZE = 5, MIN_SNAP_ANGLE = 5, MOUSE_ENTER = 'mouseEnter', MOUSE_LEAVE = 'mouseLeave', ZOOM_START = 'zoomStart', ZOOM_END = 'zoomEnd', SCROLL_MIN = -20000, SCROLL_MAX = 20000, FRICTION = 0.9, FRICTION_MOBILE = 0.93, VELOCITY_MULTIPLIER = 5, TRANSPARENT = 'transparent', PAN = 'pan', ROTATED = 'rotated';
diagram.Cursors = Cursors;
function selectSingle(item, meta) {
if (item.isSelected) {
if (meta.ctrlKey) {
item.select(false);
}
} else {
item.diagram.select(item, { addToSelection: meta.ctrlKey });
}
}
var PositionAdapter = kendo.Class.extend({
init: function (layoutState) {
this.layoutState = layoutState;
this.diagram = layoutState.diagram;
},
initState: function () {
this.froms = [];
this.tos = [];
this.subjects = [];
function pusher(id, bounds) {
var shape = this.diagram.getShapeById(id);
if (shape) {
this.subjects.push(shape);
this.froms.push(shape.bounds().topLeft());
this.tos.push(bounds.topLeft());
}
}
this.layoutState.nodeMap.forEach(pusher, this);
},
update: function (tick) {
if (this.subjects.length <= 0) {
return;
}
for (var i = 0; i < this.subjects.length; i++) {
this.subjects[i].position(new Point(this.froms[i].x + (this.tos[i].x - this.froms[i].x) * tick, this.froms[i].y + (this.tos[i].y - this.froms[i].y) * tick));
}
}
});
var LayoutUndoUnit = Class.extend({
init: function (initialState, finalState, animate) {
if (isUndefined(animate)) {
this.animate = false;
} else {
this.animate = animate;
}
this._initialState = initialState;
this._finalState = finalState;
this.title = 'Diagram layout';
},
undo: function () {
this.setState(this._initialState);
},
redo: function () {
this.setState(this._finalState);
},
setState: function (state) {
var diagram = state.diagram;
if (this.animate) {
state.linkMap.forEach(function (id, points) {
var conn = diagram.getShapeById(id);
conn.visible(false);
if (conn) {
conn.points(points);
}
});
var ticker = new Ticker();
ticker.addAdapter(new PositionAdapter(state));
ticker.onComplete(function () {
state.linkMap.forEach(function (id) {
var conn = diagram.getShapeById(id);
conn.visible(true);
});
});
ticker.play();
} else {
state.nodeMap.forEach(function (id, bounds) {
var shape = diagram.getShapeById(id);
if (shape) {
shape.position(bounds.topLeft());
}
});
state.linkMap.forEach(function (id, points) {
var conn = diagram.getShapeById(id);
if (conn) {
conn.points(points);
}
});
}
}
});
var CompositeUnit = Class.extend({
init: function (unit) {
this.units = [];
this.title = 'Composite unit';
if (unit !== undefined) {
this.units.push(unit);
}
},
add: function (undoUnit) {
this.units.push(undoUnit);
},
undo: function () {
for (var i = 0; i < this.units.length; i++) {
this.units[i].undo();
}
},
redo: function () {
for (var i = 0; i < this.units.length; i++) {
this.units[i].redo();
}
}
});
var ConnectionEditUnit = Class.extend({
init: function (item, redoSource, redoTarget) {
this.item = item;
this._redoSource = redoSource;
this._redoTarget = redoTarget;
if (defined(redoSource)) {
this._undoSource = item.source();
}
if (defined(redoTarget)) {
this._undoTarget = item.target();
}
this.title = 'Connection Editing';
},
undo: function () {
if (this._undoSource !== undefined) {
this.item._updateConnector(this._undoSource, 'source');
}
if (this._undoTarget !== undefined) {
this.item._updateConnector(this._undoTarget, 'target');
}
this.item.updateModel();
},
redo: function () {
if (this._redoSource !== undefined) {
this.item._updateConnector(this._redoSource, 'source');
}
if (this._redoTarget !== undefined) {
this.item._updateConnector(this._redoTarget, 'target');
}
this.item.updateModel();
}
});
var ConnectionEditUndoUnit = Class.extend({
init: function (item, undoSource, undoTarget) {
this.item = item;
this._undoSource = undoSource;
this._undoTarget = undoTarget;
this._redoSource = item.source();
this._redoTarget = item.target();
this.title = 'Connection Editing';
},
undo: function () {
this.item._updateConnector(this._undoSource, 'source');
this.item._updateConnector(this._undoTarget, 'target');
this.item.updateModel();
},
redo: function () {
this.item._updateConnector(this._redoSource, 'source');
this.item._updateConnector(this._redoTarget, 'target');
this.item.updateModel();
}
});
var DeleteConnectionUnit = Class.extend({
init: function (connection) {
this.connection = connection;
this.diagram = connection.diagram;
this.targetConnector = connection.targetConnector;
this.title = 'Delete connection';
},
undo: function () {
this.diagram._addConnection(this.connection, false);
},
redo: function () {
this.diagram.remove(this.connection, false);
}
});
var DeleteShapeUnit = Class.extend({
init: function (shape) {
this.shape = shape;
this.diagram = shape.diagram;
this.title = 'Deletion';
},
undo: function () {
this.diagram._addShape(this.shape, false);
this.shape.select(false);
},
redo: function () {
this.shape.select(false);
this.diagram.remove(this.shape, false);
}
});
var TransformUnit = Class.extend({
init: function (shapes, undoStates, adorner) {
this.shapes = shapes;
this.undoStates = undoStates;
this.title = 'Transformation';
this.redoStates = [];
this.adorner = adorner;
for (var i = 0; i < this.shapes.length; i++) {
var shape = this.shapes[i];
this.redoStates.push(shape.bounds());
}
},
undo: function () {
for (var i = 0; i < this.shapes.length; i++) {
var shape = this.shapes[i];
shape.bounds(this.undoStates[i]);
if (shape.hasOwnProperty('layout')) {
shape.layout(shape, this.redoStates[i], this.undoStates[i]);
}
shape.updateModel();
}
if (this.adorner) {
this.adorner.refreshBounds();
this.adorner.refresh();
}
},
redo: function () {
for (var i = 0; i < this.shapes.length; i++) {
var shape = this.shapes[i];
shape.bounds(this.redoStates[i]);
if (shape.hasOwnProperty('layout')) {
shape.layout(shape, this.undoStates[i], this.redoStates[i]);
}
shape.updateModel();
}
if (this.adorner) {
this.adorner.refreshBounds();
this.adorner.refresh();
}
}
});
var AddConnectionUnit = Class.extend({
init: function (connection, diagram) {
this.connection = connection;
this.diagram = diagram;
this.title = 'New connection';
},
undo: function () {
this.diagram.remove(this.connection, false);
},
redo: function () {
this.diagram._addConnection(this.connection, false);
}
});
var AddShapeUnit = Class.extend({
init: function (shape, diagram) {
this.shape = shape;
this.diagram = diagram;
this.title = 'New shape';
},
undo: function () {
this.diagram.deselect();
this.diagram.remove(this.shape, false);
},
redo: function () {
this.diagram._addShape(this.shape, false);
}
});
var PanUndoUnit = Class.extend({
init: function (initialPosition, finalPosition, diagram) {
this.initial = initialPosition;
this.finalPos = finalPosition;
this.diagram = diagram;
this.title = 'Pan Unit';
},
undo: function () {
this.diagram.pan(this.initial);
},
redo: function () {
this.diagram.pan(this.finalPos);
}
});
var RotateUnit = Class.extend({
init: function (adorner, shapes, undoRotates) {
this.shapes = shapes;
this.undoRotates = undoRotates;
this.title = 'Rotation';
this.redoRotates = [];
this.redoAngle = adorner._angle;
this.adorner = adorner;
this.center = adorner._innerBounds.center();
for (var i = 0; i < this.shapes.length; i++) {
var shape = this.shapes[i];
this.redoRotates.push(shape.rotate().angle);
}
},
undo: function () {
var i, shape;
for (i = 0; i < this.shapes.length; i++) {
shape = this.shapes[i];
shape.rotate(this.undoRotates[i], this.center, false);
if (shape.hasOwnProperty('layout')) {
shape.layout(shape);
}
shape.updateModel();
}
if (this.adorner) {
this.adorner._initialize();
this.adorner.refresh();
}
},
redo: function () {
var i, shape;
for (i = 0; i < this.shapes.length; i++) {
shape = this.shapes[i];
shape.rotate(this.redoRotates[i], this.center, false);
if (shape.hasOwnProperty('layout')) {
shape.layout(shape);
}
shape.updateModel();
}
if (this.adorner) {
this.adorner._initialize();
this.adorner.refresh();
}
}
});
var ToFrontUnit = Class.extend({
init: function (diagram, items, initialIndices) {
this.diagram = diagram;
this.indices = initialIndices;
this.items = items;
this.title = 'Rotate Unit';
},
undo: function () {
this.diagram._toIndex(this.items, this.indices);
},
redo: function () {
this.diagram.toFront(this.items, false);
}
});
var ToBackUnit = Class.extend({
init: function (diagram, items, initialIndices) {
this.diagram = diagram;
this.indices = initialIndices;
this.items = items;
this.title = 'Rotate Unit';
},
undo: function () {
this.diagram._toIndex(this.items, this.indices);
},
redo: function () {
this.diagram.toBack(this.items, false);
}
});
var UndoRedoService = kendo.Observable.extend({
init: function (options) {
kendo.Observable.fn.init.call(this, options);
this.bind(this.events, options);
this.stack = [];
this.index = 0;
this.capacity = 100;
},
events: [
'undone',
'redone'
],
begin: function () {
this.composite = new CompositeUnit();
},
cancel: function () {
this.composite = undefined;
},
commit: function (execute) {
if (this.composite.units.length > 0) {
this._restart(this.composite, execute);
}
this.composite = undefined;
},
addCompositeItem: function (undoUnit) {
if (this.composite) {
this.composite.add(undoUnit);
} else {
this.add(undoUnit);
}
},
add: function (undoUnit, execute) {
this._restart(undoUnit, execute);
},
pop: function () {
if (this.index > 0) {
this.stack.pop();
this.index--;
}
},
count: function () {
return this.stack.length;
},
undo: function () {
if (this.index > 0) {
this.index--;
this.stack[this.index].undo();
this.trigger('undone');
}
},
redo: function () {
if (this.stack.length > 0 && this.index < this.stack.length) {
this.stack[this.index].redo();
this.index++;
this.trigger('redone');
}
},
_restart: function (composite, execute) {
this.stack.splice(this.index, this.stack.length - this.index);
this.stack.push(composite);
if (execute !== false) {
this.redo();
} else {
this.index++;
}
if (this.stack.length > this.capacity) {
this.stack.splice(0, this.stack.length - this.capacity);
this.index = this.capacity;
}
},
clear: function () {
this.stack = [];
this.index = 0;
}
});
var EmptyTool = Class.extend({
init: function (toolService) {
this.toolService = toolService;
},
start: function () {
},
move: function () {
},
end: function () {
},
tryActivate: function () {
return false;
},
getCursor: function () {
return Cursors.arrow;
}
});
function noMeta(meta) {
return meta.ctrlKey === false && meta.altKey === false && meta.shiftKey === false;
}
function tryActivateSelection(options, meta) {
var enabled = options !== false;
if (options.key && options.key != 'none') {
enabled = meta[options.key + 'Key'];
}
return enabled;
}
var ScrollerTool = EmptyTool.extend({
init: function (toolService) {
var tool = this;
var friction = kendo.support.mobileOS ? FRICTION_MOBILE : FRICTION;
EmptyTool.fn.init.call(tool, toolService);
var diagram = tool.toolService.diagram, canvas = diagram.canvas;
var scroller = diagram.scroller = tool.scroller = $(diagram.scrollable).kendoMobileScroller({
friction: friction,
velocityMultiplier: VELOCITY_MULTIPLIER,
mousewheelScrolling: false,
zoom: false,
scroll: proxy(tool._move, tool)
}).data('kendoMobileScroller');
if (canvas.translate) {
tool.movableCanvas = new Movable(canvas.element);
}
var virtualScroll = function (dimension, min, max) {
dimension.makeVirtual();
dimension.virtualSize(min || SCROLL_MIN, max || SCROLL_MAX);
};
virtualScroll(scroller.dimensions.x);
virtualScroll(scroller.dimensions.y);
scroller.disable();
},
tryActivate: function (p, meta) {
var toolService = this.toolService;
var options = toolService.diagram.options.pannable;
var enabled = meta.ctrlKey;
if (defined(options.key)) {
if (!options.key || options.key == 'none') {
enabled = noMeta(meta);
} else {
enabled = meta[options.key + 'Key'] && !(meta.ctrlKey && defined(toolService.hoveredItem));
}
}
return options !== false && enabled && !defined(toolService.hoveredAdorner) && !defined(toolService._hoveredConnector);
},
start: function () {
this.scroller.enable();
},
move: function () {
},
_move: function (args) {
var tool = this, diagram = tool.toolService.diagram, canvas = diagram.canvas, scrollPos = new Point(args.scrollLeft, args.scrollTop);
if (canvas.translate) {
diagram._storePan(scrollPos.times(-1));
tool.movableCanvas.moveTo(scrollPos);
canvas.translate(scrollPos.x, scrollPos.y);
} else {
scrollPos = scrollPos.plus(diagram._pan.times(-1));
}
diagram.trigger(PAN, { pan: scrollPos });
},
end: function () {
this.scroller.disable();
},
getCursor: function () {
return Cursors.move;
}
});
var PointerTool = Class.extend({
init: function (toolService) {
this.toolService = toolService;
},
tryActivate: function () {
return true;
},
start: function (p, meta) {
var toolService = this.toolService, diagram = toolService.diagram, hoveredItem = toolService.hoveredItem, selectable = diagram.options.selectable;
if (hoveredItem) {
if (tryActivateSelection(selectable, meta)) {
selectSingle(hoveredItem, meta);
}
if (hoveredItem.adorner) {
this.adorner = hoveredItem.adorner;
this.handle = this.adorner._hitTest(p);
}
}
if (!this.handle) {
this.handle = diagram._resizingAdorner._hitTest(p);
if (this.handle) {
this.adorner = diagram._resizingAdorner;
}
}
if (this.adorner) {
if (!this.adorner.isDragHandle(this.handle) || !diagram.trigger(DRAG_START, {
shapes: this.adorner.shapes,
connections: []
})) {
this.adorner.start(p);
} else {
toolService.startPoint = p;
toolService.end(p);
}
}
},
move: function (p) {
if (this.adorner) {
this.adorner.move(this.handle, p);
if (this.adorner.isDragHandle(this.handle)) {
this.toolService.diagram.trigger(DRAG, {
shapes: this.adorner.shapes,
connections: []
});
}
}
},
end: function (p, meta) {
var diagram = this.toolService.diagram, service = this.toolService, adorner = this.adorner, unit;
if (adorner) {
if (!adorner.isDragHandle(this.handle) || !diagram.trigger(DRAG_END, {
shapes: adorner.shapes,
connections: []
})) {
unit = adorner.stop();
if (unit) {
diagram.undoRedoService.add(unit, false);
}
} else {
adorner.cancel();
}
}
if (service.hoveredItem) {
this.toolService.triggerClick({
item: service.hoveredItem,
point: p,
meta: meta
});
}
this.adorner = undefined;
this.handle = undefined;
},
getCursor: function (p) {
return this.toolService.hoveredItem ? this.toolService.hoveredItem._getCursor(p) : Cursors.arrow;
}
});
var SelectionTool = Class.extend({
init: function (toolService) {
this.toolService = toolService;
},
tryActivate: function (p, meta) {
var toolService = this.toolService;
var enabled = tryActivateSelection(toolService.diagram.options.selectable, meta);
return enabled && !defined(toolService.hoveredItem) && !defined(toolService.hoveredAdorner);
},
start: function (p) {
var diagram = this.toolService.diagram;
diagram.deselect();
diagram.selector.start(p);
},
move: function (p) {
var diagram = this.toolService.diagram;
diagram.selector.move(p);
},
end: function (p, meta) {
var diagram = this.toolService.diagram, hoveredItem = this.toolService.hoveredItem;
var rect = diagram.selector.bounds();
if ((!hoveredItem || !hoveredItem.isSelected) && !meta.ctrlKey) {
diagram.deselect();
}
if (!rect.isEmpty()) {
diagram.selectArea(rect);
}
diagram.selector.end();
},
getCursor: function () {
return Cursors.arrow;
}
});
var ConnectionTool = Class.extend({
init: function (toolService) {
this.toolService = toolService;
this.type = 'ConnectionTool';
},
tryActivate: function () {
return this.toolService._hoveredConnector;
},
start: function (p, meta) {
var diagram = this.toolService.diagram, connector = this.toolService._hoveredConnector, connection = diagram._createConnection({}, connector._c, p);
if (canDrag(connection) && !diagram.trigger(DRAG_START, {
shapes: [],
connections: [connection]
}) && diagram._addConnection(connection)) {
this.toolService._connectionManipulation(connection, connector._c.shape, true);
this.toolService._removeHover();
selectSingle(this.toolService.activeConnection, meta);
} else {
connection.source(null);
this.toolService.end(p);
}
},
move: function (p) {
var toolService = this.toolService;
var connection = toolService.activeConnection;
connection.target(p);
toolService.diagram.trigger(DRAG, {
shapes: [],
connections: [connection]
});
return true;
},
end: function (p) {
var toolService = this.toolService, d = toolService.diagram, connection = toolService.activeConnection, hoveredItem = toolService.hoveredItem, connector = toolService._hoveredConnector, target;
if (!connection) {
return;
}
if (connector && connector._c != connection.sourceConnector) {
target = connector._c;
} else if (hoveredItem && hoveredItem instanceof diagram.Shape) {
target = hoveredItem.getConnector(AUTO) || hoveredItem.getConnector(p);
} else {
target = p;
}
connection.target(target);
if (!d.trigger(DRAG_END, {
shapes: [],
connections: [connection]
})) {
connection.updateModel();
d._syncConnectionChanges();
} else {
d.remove(connection, false);
d.undoRedoService.pop();
}
toolService._connectionManipulation();
},
getCursor: function () {
return Cursors.arrow;
}
});
var ConnectionEditTool = Class.extend({
init: function (toolService) {
this.toolService = toolService;
this.type = 'ConnectionTool';
},
tryActivate: function (p, meta) {
var toolService = this.toolService, diagram = toolService.diagram, selectable = diagram.options.selectable, item = toolService.hoveredItem, isActive = tryActivateSelection(selectable, meta) && item && item.path && !(item.isSelected && meta.ctrlKey);
if (isActive) {
this._c = item;
}
return isActive;
},
start: function (p, meta) {
var connection = this._c;
selectSingle(connection, meta);
var adorner = connection.adorner;
if (canDrag(connection) && adorner && !this.toolService.diagram.trigger(DRAG_START, {
shapes: [],
connections: [connection]
})) {
this.handle = adorner._hitTest(p);
adorner.start(p);
} else {
this.toolService.startPoint = p;
this.toolService.end(p);
}
},
move: function (p) {
var adorner = this._c.adorner;
if (canDrag(this._c) && adorner) {
adorner.move(this.handle, p);
this.toolService.diagram.trigger(DRAG, {
shapes: [],
connections: [this._c]
});
return true;
}
},
end: function (p, meta) {
var connection = this._c;
var adorner = connection.adorner;
var toolService = this.toolService;
var diagram = toolService.diagram;
if (adorner) {
toolService.triggerClick({
item: connection,
point: p,
meta: meta
});
if (canDrag(connection)) {
var unit = adorner.stop(p);
if (!diagram.trigger(DRAG_END, {
shapes: [],
connections: [connection]
})) {
diagram.undoRedoService.add(unit, false);
connection.updateModel();
diagram._syncConnectionChanges();
} else {
unit.undo();
}
}
}
},
getCursor: function () {
return Cursors.move;
}
});
function testKey(key, str) {
return str.charCodeAt(0) == key || str.toUpperCase().charCodeAt(0) == key;
}
var ToolService = Class.extend({
init: function (diagram) {
this.diagram = diagram;
this.tools = [
new ScrollerTool(this),
new ConnectionEditTool(this),
new ConnectionTool(this),
new SelectionTool(this),
new PointerTool(this)
];
this.activeTool = undefined;
},
start: function (p, meta) {
meta = deepExtend({}, meta);
if (this.activeTool) {
this.activeTool.end(p, meta);
}
this._updateHoveredItem(p);
this._activateTool(p, meta);
this.activeTool.start(p, meta);
this._updateCursor(p);
this.diagram.focus();
this.startPoint = p;
return true;
},
move: function (p, meta) {
meta = deepExtend({}, meta);
var updateHovered = true;
if (this.activeTool) {
updateHovered = this.activeTool.move(p, meta);
}
if (updateHovered) {
this._updateHoveredItem(p);
}
this._updateCursor(p);
return true;
},
end: function (p, meta) {
meta = deepExtend({}, meta);
if (this.activeTool) {
this.activeTool.end(p, meta);
}
this.activeTool = undefined;
this._updateCursor(p);
return true;
},
keyDown: function (key, meta) {
var diagram = this.diagram;
meta = deepExtend({
ctrlKey: false,
metaKey: false,
altKey: false
}, meta);
if ((meta.ctrlKey || meta.metaKey) && !meta.altKey) {
if (testKey(key, 'a')) {
diagram.selectAll();
diagram._destroyToolBar();
return true;
} else if (testKey(key, 'z')) {
diagram.undo();
diagram._destroyToolBar();
return true;
} else if (testKey(key, 'y')) {
diagram.redo();
diagram._destroyToolBar();
return true;
} else if (testKey(key, 'c')) {
diagram.copy();
diagram._destroyToolBar();
} else if (testKey(key, 'x')) {
diagram.cut();
diagram._destroyToolBar();
} else if (testKey(key, 'v')) {
diagram.paste();
diagram._destroyToolBar();
} else if (testKey(key, 'l')) {
diagram.layout();
diagram._destroyToolBar();
} else if (testKey(key, 'd')) {
diagram._destroyToolBar();
diagram.copy();
diagram.paste();
}
} else if (key === 46 || key === 8) {
var toRemove = this.diagram._triggerRemove(diagram.select());
if (toRemove.length) {
this.diagram.remove(toRemove, true);
this.diagram._syncChanges();
this.diagram._destroyToolBar();
}
return true;
} else if (key === 27) {
this._discardNewConnection();
diagram.deselect();
diagram._destroyToolBar();
return true;
}
},
wheel: function (p, meta) {
var diagram = this.diagram, delta = meta.delta, z = diagram.zoom(), options = diagram.options, zoomRate = options.zoomRate, zoomOptions = {
point: p,
meta: meta,
zoom: z
};
if (diagram.trigger(ZOOM_START, zoomOptions)) {
return;
}
if (delta < 0) {
z += zoomRate;
} else {
z -= zoomRate;
}
z = kendo.dataviz.round(Math.max(options.zoomMin, Math.min(options.zoomMax, z)), 2);
zoomOptions.zoom = z;
diagram.zoom(z, zoomOptions);
diagram.trigger(ZOOM_END, zoomOptions);
return true;
},
setTool: function (tool, index) {
tool.toolService = this;
this.tools[index] = tool;
},
triggerClick: function (data) {
if (this.startPoint.equals(data.point)) {
this.diagram.trigger('click', data);
}
},
_discardNewConnection: function () {
if (this.newConnection) {
this.diagram.remove(this.newConnection);
this.newConnection = undefined;
}
},
_activateTool: function (p, meta) {
for (var i = 0; i < this.tools.length; i++) {
var tool = this.tools[i];
if (tool.tryActivate(p, meta)) {
this.activeTool = tool;
break;
}
}
},
_updateCursor: function (p) {
var element = this.diagram.element;
var cursor = this.activeTool ? this.activeTool.getCursor(p) : this.hoveredAdorner ? this.hoveredAdorner._getCursor(p) : this.hoveredItem ? this.hoveredItem._getCursor(p) : Cursors.arrow;
element.css({ cursor: cursor });
if (browser.msie && browser.version == 7) {
element[0].style.cssText = element[0].style.cssText;
}
},
_connectionManipulation: function (connection, disabledShape, isNew) {
this.activeConnection = connection;
this.disabledShape = disabledShape;
if (isNew) {
this.newConnection = this.activeConnection;
} else {
this.newConnection = undefined;
}
},
_updateHoveredItem: function (p) {
var hit = this._hitTest(p);
var diagram = this.diagram;
if (hit != this.hoveredItem && (!this.disabledShape || hit != this.disabledShape)) {
if (this.hoveredItem) {
diagram.trigger(MOUSE_LEAVE, { item: this.hoveredItem });
this.hoveredItem._hover(false);
}
if (hit && hit.options.enable) {
diagram.trigger(MOUSE_ENTER, { item: hit });
this.hoveredItem = hit;
this.hoveredItem._hover(true);
} else {
this.hoveredItem = undefined;
}
}
},
_removeHover: function () {
if (this.hoveredItem) {
this.hoveredItem._hover(false);
this.hoveredItem = undefined;
}
},
_hitTest: function (point) {
var hit, d = this.diagram, item, i;
if (this._hoveredConnector) {
this._hoveredConnector._hover(false);
this._hoveredConnector = undefined;
}
if (d._connectorsAdorner._visible) {
hit = d._connectorsAdorner._hitTest(point);
if (hit) {
return hit;
}
}
hit = this.diagram._resizingAdorner._hitTest(point);
if (hit) {
this.hoveredAdorner = d._resizingAdorner;
if (hit.x !== 0 || hit.y !== 0) {
return;
}
hit = undefined;
} else {
this.hoveredAdorner = undefined;
}
if (!this.activeTool || this.activeTool.type !== 'ConnectionTool') {
var selectedConnections = [];
for (i = 0; i < d._selectedItems.length; i++) {
item = d._selectedItems[i];
if (item instanceof diagram.Connection) {
selectedConnections.push(item);
}
}
hit = this._hitTestItems(selectedConnections, point);
}
return hit || this._hitTestElements(point);
},
_hitTestElements: function (point) {
var diagram = this.diagram;
var shapeHit = this._hitTestItems(diagram.shapes, point);
var connectionHit = this._hitTestItems(diagram.connections, point);
var hit;
if ((!this.activeTool || this.activeTool.type != 'ConnectionTool') && shapeHit && connectionHit && !hitTestShapeConnectors(shapeHit, point)) {
var mainLayer = diagram.mainLayer;
var shapeIdx = inArray(shapeHit.visual, mainLayer.children);
var connectionIdx = inArray(connectionHit.visual, mainLayer.children);
hit = shapeIdx > connectionIdx ? shapeHit : connectionHit;
}
return hit || shapeHit || connectionHit;
},
_hitTestItems: function (array, point) {
var i, item, hit;
for (i = array.length - 1; i >= 0; i--) {
item = array[i];
hit = item._hitTest(point);
if (hit) {
return hit;
}
}
}
});
var ConnectionRouterBase = kendo.Class.extend({
init: function () {
}
});
var LinearConnectionRouter = ConnectionRouterBase.extend({
init: function (connection) {
var that = this;
ConnectionRouterBase.fn.init.call(that);
this.connection = connection;
},
hitTest: function (p) {
var rec = this.getBounds().inflate(HIT_TEST_DISTANCE);
if (!rec.contains(p)) {
return false;
}
return diagram.Geometry.distanceToPolyline(p, this.connection.allPoints()) < HIT_TEST_DISTANCE;
},
getBounds: function () {
var points = this.connection.allPoints(), s = points[0], e = points[points.length - 1], right = Math.max(s.x, e.x), left = Math.min(s.x, e.x), top = Math.min(s.y, e.y), bottom = Math.max(s.y, e.y);
for (var i = 1; i < points.length - 1; ++i) {
right = Math.max(right, points[i].x);
left = Math.min(left, points[i].x);
top = Math.min(top, points[i].y);
bottom = Math.max(bottom, points[i].y);
}
return new Rect(left, top, right - left, bottom - top);
}
});
var PolylineRouter = LinearConnectionRouter.extend({
init: function (connection) {
var that = this;
LinearConnectionRouter.fn.init.call(that);
this.connection = connection;
},
route: function () {
}
});
var CascadingRouter = LinearConnectionRouter.extend({
SAME_SIDE_DISTANCE_RATIO: 5,
init: function (connection) {
var that = this;
LinearConnectionRouter.fn.init.call(that);
this.connection = connection;
},
routePoints: function (start, end, sourceConnector, targetConnector) {
var result;
if (sourceConnector && targetConnector) {
result = this._connectorPoints(start, end, sourceConnector, targetConnector);
} else {
result = this._floatingPoints(start, end, sourceConnector);
}
return result;
},
route: function () {
var sourceConnector = this.connection._resolvedSourceConnector;
var targetConnector = this.connection._resolvedTargetConnector;
var start = this.connection.sourcePoint();
var end = this.connection.targetPoint();
var points = this.routePoints(start, end, sourceConnector, targetConnector);
this.connection.points(points);
},
_connectorSides: [
{
name: 'Top',
axis: 'y',
boundsPoint: 'topLeft',
secondarySign: 1
},
{
name: 'Left',
axis: 'x',
boundsPoint: 'topLeft',
secondarySign: 1
},
{
name: 'Bottom',
axis: 'y',
boundsPoint: 'bottomRight',
secondarySign: -1
},
{
name: 'Right',
axis: 'x',
boundsPoint: 'bottomRight',
secondarySign: -1
}
],
_connectorSide: function (connector, targetPoint) {
var position = connector.position();
var shapeBounds = connector.shape.bounds(ROTATED);
var bounds = {
topLeft: shapeBounds.topLeft(),
bottomRight: shapeBounds.bottomRight()
};
var sides = this._connectorSides;
var min = kendo.util.MAX_NUM;
var sideDistance;
var minSide;
var axis;
var side;
for (var idx = 0; idx < sides.length; idx++) {
side = sides[idx];
axis = side.axis;
sideDistance = Math.round(Math.abs(position[axis] - bounds[side.boundsPoint][axis]));
if (sideDistance < min) {
min = sideDistance;
minSide = side;
} else if (sideDistance === min && (position[axis] - targetPoint[axis]) * side.secondarySign > (position[minSide.axis] - targetPoint[minSide.axis]) * minSide.secondarySign) {
minSide = side;
}
}
return minSide.name;
},
_sameSideDistance: function (connector) {
var bounds = connector.shape.bounds(ROTATED);
return Math.min(bounds.width, bounds.height) / this.SAME_SIDE_DISTANCE_RATIO;
},
_connectorPoints: function (start, end, sourceConnector, targetConnector) {
var sourceConnectorSide = this._connectorSide(sourceConnector, end);
var targetConnectorSide = this._connectorSide(targetConnector, start);
var deltaX = end.x - start.x;
var deltaY = end.y - start.y;
var sameSideDistance = this._sameSideDistance(sourceConnector);
var result = [];
var pointX, pointY;
if (sourceConnectorSide === TOP || sourceConnectorSide == BOTTOM) {
if (targetConnectorSide == TOP || targetConnectorSide == BOTTOM) {
if (sourceConnectorSide == targetConnectorSide) {
if (sourceConnectorSide == TOP) {
pointY = Math.min(start.y, end.y) - sameSideDistance;
} else {
pointY = Math.max(start.y, end.y) + sameSideDistance;
}
result = [
new Point(start.x, pointY),
new Point(end.x, pointY)
];
} else {
result = [
new Point(start.x, start.y + deltaY / 2),
new Point(end.x, start.y + deltaY / 2)
];
}
} else {
result = [new Point(start.x, end.y)];
}
} else {
if (targetConnectorSide == LEFT || targetConnectorSide == RIGHT) {
if (sourceConnectorSide == targetConnectorSide) {
if (sourceConnectorSide == LEFT) {
pointX = Math.min(start.x, end.x) - sameSideDistance;
} else {
pointX = Math.max(start.x, end.x) + sameSideDistance;
}
result = [
new Point(pointX, start.y),
new Point(pointX, end.y)
];
} else {
result = [
new Point(start.x + deltaX / 2, start.y),
new Point(start.x + deltaX / 2, start.y + deltaY)
];
}
} else {
result = [new Point(end.x, start.y)];
}
}
return result;
},
_floatingPoints: function (start, end, sourceConnector) {
var sourceConnectorSide = sourceConnector ? this._connectorSide(sourceConnector, end) : null;
var cascadeStartHorizontal = this._startHorizontal(start, end, sourceConnectorSide);
var points = [
start,
start,
end,
end
];
var deltaX = end.x - start.x;
var deltaY = end.y - start.y;
var length = points.length;
var shiftX;
var shiftY;
for (var idx = 1; idx < length - 1; ++idx) {
if (cascadeStartHorizontal) {
if (idx % 2 !== 0) {
shiftX = deltaX / (length / 2);
shiftY = 0;
} else {
shiftX = 0;
shiftY = deltaY / ((length - 1) / 2);
}
} else {
if (idx % 2 !== 0) {
shiftX = 0;
shiftY = deltaY / (length / 2);
} else {
shiftX = deltaX / ((length - 1) / 2);
shiftY = 0;
}
}
points[idx] = new Point(points[idx - 1].x + shiftX, points[idx - 1].y + shiftY);
}
idx--;
if (cascadeStartHorizontal && idx % 2 !== 0 || !cascadeStartHorizontal && idx % 2 === 0) {
points[length - 2] = new Point(points[length - 1].x, points[length - 2].y);
} else {
points[length - 2] = new Point(points[length - 2].x, points[length - 1].y);
}
return [
points[1],
points[2]
];
},
_startHorizontal: function (start, end, sourceSide) {
var horizontal;
if (sourceSide !== null && (sourceSide === RIGHT || sourceSide === LEFT)) {
horizontal = true;
} else {
horizontal = Math.abs(start.x - end.x) > Math.abs(start.y - end.y);
}
return horizontal;
}
});
var AdornerBase = Class.extend({
init: function (diagram, options) {
var that = this;
that.diagram = diagram;
that.options = deepExtend({}, that.options, options);
that.visual = new Group();
that.diagram._adorners.push(that);
},
refresh: function () {
}
});
var ConnectionEditAdorner = AdornerBase.extend({
init: function (connection, options) {
var that = this, diagram;
that.connection = connection;
diagram = that.connection.diagram;
that._ts = diagram.toolService;
AdornerBase.fn.init.call(that, diagram, options);
var sp = that.connection.sourcePoint();
var tp = that.connection.targetPoint();
that.spVisual = new Circle(deepExtend(that.options.handles, { center: sp }));
that.epVisual = new Circle(deepExtend(that.options.handles, { center: tp }));
that.visual.append(that.spVisual);
that.visual.append(that.epVisual);
},
options: { handles: {} },
_getCursor: function () {
return Cursors.move;
},
start: function (p) {
this.handle = this._hitTest(p);
this.startPoint = p;
this._initialSource = this.connection.source();
this._initialTarget = this.connection.target();
switch (this.handle) {
case -1:
if (this.connection.targetConnector) {
this._ts._connectionManipulation(this.connection, this.connection.targetConnector.shape);
}
break;
case 1:
if (this.connection.sourceConnector) {
this._ts._connectionManipulation(this.connection, this.connection.sourceConnector.shape);
}
break;
}
},
move: function (handle, p) {
switch (handle) {
case -1:
this.connection.source(p);
break;
case 1:
this.connection.target(p);
break;
default:
var delta = p.minus(this.startPoint);
this.startPoint = p;
if (!this.connection.sourceConnector) {
this.connection.source(this.connection.sourcePoint().plus(delta));
}
if (!this.connection.targetConnector) {
this.connection.target(this.connection.targetPoint().plus(delta));
}
break;
}
this.refresh();
return true;
},
stop: function (p) {
var ts = this.diagram.toolService, item = ts.hoveredItem, target;
if (ts._hoveredConnector) {
target = ts._hoveredConnector._c;
} else if (item && item instanceof diagram.Shape) {
target = item.getConnector(AUTO) || item.getConnector(p);
} else {
target = p;
}
if (this.handle === -1) {
this.connection.source(target);
} else if (this.handle === 1) {
this.connection.target(target);
}
this.handle = undefined;
this._ts._connectionManipulation();
return new ConnectionEditUndoUnit(this.connection, this._initialSource, this._initialTarget);
},
_hitTest: function (point) {
var sourcePoint = this.connection.sourcePoint();
var targetPoint = this.connection.targetPoint();
var radiusX = this.options.handles.width / 2 + HIT_TEST_DISTANCE;
var radiusY = this.options.handles.height / 2 + HIT_TEST_DISTANCE;
var sourcePointDistance = sourcePoint.distanceTo(point);
var targetPointDistance = targetPoint.distanceTo(point);
var sourceHandle = new Rect(sourcePoint.x, sourcePoint.y).inflate(radiusX, radiusY).contains(point);
var targetHandle = new Rect(targetPoint.x, targetPoint.y).inflate(radiusX, radiusY).contains(point);
var handle = 0;
if (sourceHandle && (!targetHandle || sourcePointDistance < targetPointDistance)) {
handle = -1;
} else if (targetHandle && (!sourceHandle || targetPointDistance < sourcePointDistance)) {
handle = 1;
}
return handle;
},
refresh: function () {
this.spVisual.redraw({ center: this.diagram.modelToLayer(this.connection.sourcePoint()) });
this.epVisual.redraw({ center: this.diagram.modelToLayer(this.connection.targetPoint()) });
}
});
var ConnectorsAdorner = AdornerBase.extend({
init: function (diagram, options) {
var that = this;
AdornerBase.fn.init.call(that, diagram, options);
that._refreshHandler = function (e) {
if (e.item == that.shape) {
that.refresh();
}
};
},
show: function (shape) {
var that = this, len, i, ctr;
that._visible = true;
that.shape = shape;
that.diagram.bind(ITEMBOUNDSCHANGE, that._refreshHandler);
len = shape.connectors.length;
that.connectors = [];
that.visual.clear();
for (i = 0; i < len; i++) {
ctr = new ConnectorVisual(shape.connectors[i]);
that.connectors.push(ctr);
that.visual.append(ctr.visual);
}
that.visual.visible(true);
that.refresh();
},
destroy: function () {
var that = this;
that.diagram.unbind(ITEMBOUNDSCHANGE, that._refreshHandler);
that.shape = undefined;
that._visible = undefined;
that.visual.visible(false);
},
_hitTest: function (p) {
var ctr, i;
for (i = 0; i < this.connectors.length; i++) {
ctr = this.connectors[i];
if (ctr._hitTest(p)) {
ctr._hover(true);
this.diagram.toolService._hoveredConnector = ctr;
break;
}
}
},
refresh: function () {
if (this.shape) {
var bounds = this.shape.bounds();
bounds = this.diagram.modelToLayer(bounds);
this.visual.position(bounds.topLeft());
$.each(this.connectors, function () {
this.refresh();
});
}
}
});
function hitToOppositeSide(hit, bounds) {
var result;
if (hit.x == -1 && hit.y == -1) {
result = bounds.bottomRight();
} else if (hit.x == 1 && hit.y == 1) {
result = bounds.topLeft();
} else if (hit.x == -1 && hit.y == 1) {
result = bounds.topRight();
} else if (hit.x == 1 && hit.y == -1) {
result = bounds.bottomLeft();
} else if (hit.x === 0 && hit.y == -1) {
result = bounds.bottom();
} else if (hit.x === 0 && hit.y == 1) {
result = bounds.top();
} else if (hit.x == 1 && hit.y === 0) {
result = bounds.left();
} else if (hit.x == -1 && hit.y === 0) {
result = bounds.right();
}
return result;
}
var ResizingAdorner = AdornerBase.extend({
init: function (diagram, options) {
var that = this;
AdornerBase.fn.init.call(that, diagram, options);
that._manipulating = false;
that.map = [];
that.shapes = [];
that._initSelection();
that._createHandles();
that.redraw();
that.diagram.bind('select', function (e) {
that._initialize(e.selected);
});
that._refreshHandler = function () {
if (!that._internalChange) {
that.refreshBounds();
that.refresh();
}
};
that._rotatedHandler = function () {
if (that.shapes.length == 1) {
that._angle = that.shapes[0].rotate().angle;
}
that._refreshHandler();
};
that.diagram.bind(ITEMBOUNDSCHANGE, that._refreshHandler).bind(ITEMROTATE, that._rotatedHandler);
that.refreshBounds();
that.refresh();
},
options: {
handles: {
fill: { color: '#fff' },
stroke: { color: '#282828' },
height: 7,
width: 7,
hover: {
fill: { color: '#282828' },
stroke: { color: '#282828' }
}
},
selectable: {
stroke: {
color: '#778899',
width: 1,
dashType: 'dash'
},
fill: { color: TRANSPARENT }
},
offset: 10
},
_initSelection: function () {
var that = this;
var diagram = that.diagram;
var selectable = diagram.options.selectable;
var options = deepExtend({}, that.options.selectable, selectable);
that.rect = new Rectangle(options);
that.visual.append(that.rect);
},
_resizable: function () {
return this.options.editable && this.options.editable.resize !== false;
},
_handleOptions: function () {
return (this.options.editable.resize || {}).handles || this.options.handles;
},
_createHandles: function () {
var handles, item, y, x;
if (this._resizable()) {
handles = this._handleOptions();
for (x = -1; x <= 1; x++) {
for (y = -1; y <= 1; y++) {
if (x !== 0 || y !== 0) {
item = new Rectangle(handles);
item.drawingElement._hover = proxy(this._hover, this);
this.map.push({
x: x,
y: y,
visual: item
});
this.visual.append(item);
}
}
}
}
},
bounds: function (value) {
if (value) {
this._innerBounds = value.clone();
this._bounds = this.diagram.modelToLayer(value).inflate(this.options.offset, this.options.offset);
} else {
return this._bounds;
}
},
_hitTest: function (p) {
var tp = this.diagram.modelToLayer(p), i, hit, handleBounds, handlesCount = this.map.length, handle;
if (this._angle) {
tp = tp.clone().rotate(this._bounds.center(), this._angle);
}
if (this._resizable()) {
for (i = 0; i < handlesCount; i++) {
handle = this.map[i];
hit = new Point(handle.x, handle.y);
handleBounds = this._getHandleBounds(hit);
handleBounds.offset(this._bounds.x, this._bounds.y);
if (handleBounds.contains(tp)) {
return hit;
}
}
}
if (this._bounds.contains(tp)) {
return new Point(0, 0);
}
},
_getHandleBounds: function (p) {
if (this._resizable()) {
var handles = this._handleOptions(), w = handles.width, h = handles.height, r = new Rect(0, 0, w, h);
if (p.x < 0) {
r.x = -w / 2;
} else if (p.x === 0) {
r.x = Math.floor(this._bounds.width / 2) - w / 2;
} else if (p.x > 0) {
r.x = this._bounds.width + 1 - w / 2;
}
if (p.y < 0) {
r.y = -h / 2;
} else if (p.y === 0) {
r.y = Math.floor(this._bounds.height / 2) - h / 2;
} else if (p.y > 0) {
r.y = this._bounds.height + 1 - h / 2;
}
return r;
}
},
_getCursor: function (point) {
var hit = this._hitTest(point);
if (hit && hit.x >= -1 && hit.x <= 1 && hit.y >= -1 && hit.y <= 1 && this._resizable()) {
var angle = this._angle;
if (angle) {
angle = 360 - angle;
hit.rotate(new Point(0, 0), angle);
hit = new Point(Math.round(hit.x), Math.round(hit.y));
}
if (hit.x == -1 && hit.y == -1) {
return 'nw-resize';
}
if (hit.x == 1 && hit.y == 1) {
return 'se-resize';
}
if (hit.x == -1 && hit.y == 1) {
return 'sw-resize';
}
if (hit.x == 1 && hit.y == -1) {
return 'ne-resize';
}
if (hit.x === 0 && hit.y == -1) {
return 'n-resize';
}
if (hit.x === 0 && hit.y == 1) {
return 's-resize';
}
if (hit.x == 1 && hit.y === 0) {
return 'e-resize';
}
if (hit.x == -1 && hit.y === 0) {
return 'w-resize';
}
}
return this._manipulating ? Cursors.move : Cursors.select;
},
_initialize: function () {
var that = this, i, item, items = that.diagram.select();
that.shapes = [];
for (i = 0; i < items.length; i++) {
item = items[i];
if (item instanceof diagram.Shape) {
that.shapes.push(item);
item._rotationOffset = new Point();
}
}
that._angle = that.shapes.length == 1 ? that.shapes[0].rotate().angle : 0;
that._startAngle = that._angle;
that._rotates();
that._positions();
that.refreshBounds();
that.refresh();
that.redraw();
},
_rotates: function () {
var that = this, i, shape;
that.initialRotates = [];
for (i = 0; i < that.shapes.length; i++) {
shape = that.shapes[i];
that.initialRotates.push(shape.rotate().angle);
}
},
_positions: function () {
var that = this, i, shape;
that.initialStates = [];
for (i = 0; i < that.shapes.length; i++) {
shape = that.shapes[i];
that.initialStates.push(shape.bounds());
}
},
_hover: function (value, element) {
if (this._resizable()) {
var handleOptions = this._handleOptions(), hover = handleOptions.hover, stroke = handleOptions.stroke, fill = handleOptions.fill;
if (value && Utils.isDefined(hover.stroke)) {
stroke = deepExtend({}, stroke, hover.stroke);
}
if (value && Utils.isDefined(hover.fill)) {
fill = hover.fill;
}
element.stroke(stroke.color, stroke.width, stroke.opacity);
element.fill(fill.color, fill.opacity);
}
},
start: function (p) {
this._sp = p;
this._cp = p;
this._lp = p;
this._manipulating = true;
this._internalChange = true;
this.shapeStates = [];
for (var i = 0; i < this.shapes.length; i++) {
var shape = this.shapes[i];
this.shapeStates.push(shape.bounds());
}
},
redraw: function () {
var i, handle, visibleHandles = this._resizable();
for (i = 0; i < this.map.length; i++) {
handle = this.map[i];
handle.visual.visible(visibleHandles);
}
},
angle: function (value) {
if (defined(value)) {
this._angle = value;
}
return this._angle;
},
rotate: function () {
var center = this._innerBounds.center();
var currentAngle = this.angle();
this._internalChange = true;
for (var i = 0; i < this.shapes.length; i++) {
var shape = this.shapes[i];
currentAngle = (currentAngle + this.initialRotates[i] - this._startAngle) % 360;
shape.rotate(currentAngle, center);
}
this.refresh();
},
move: function (handle, p) {
var delta, dragging, dtl = new Point(), dbr = new Point(), bounds, center, shape, i, angle, newBounds, changed = 0, staticPoint, scaleX, scaleY;
if (handle.y === -2 && handle.x === -1) {
center = this._innerBounds.center();
this._angle = this._truncateAngle(Utils.findAngle(center, p));
for (i = 0; i < this.shapes.length; i++) {
shape = this.shapes[i];
angle = (this._angle + this.initialRotates[i] - this._startAngle) % 360;
shape.rotate(angle, center);
if (shape.hasOwnProperty('layout')) {
shape.layout(shape);
}
this._rotating = true;
}
this.refresh();
} else {
if (this.shouldSnap()) {
var thr = this._truncateDistance(p.minus(this._lp));
if (thr.x === 0 && thr.y === 0) {
this._cp = p;
return;
}
delta = thr;
this._lp = new Point(this._lp.x + thr.x, this._lp.y + thr.y);
} else {
delta = p.minus(this._cp);
}
if (this.isDragHandle(handle)) {
dbr = dtl = delta;
dragging = true;
} else {
if (this._angle) {
delta.rotate(new Point(0, 0), this._angle);
}
if (handle.x == -1) {
dtl.x = delta.x;
} else if (handle.x == 1) {
dbr.x = delta.x;
}
if (handle.y == -1) {
dtl.y = delta.y;
} else if (handle.y == 1) {
dbr.y = delta.y;
}
}
if (!dragging) {
staticPoint = hitToOppositeSide(handle, this._innerBounds);
scaleX = (this._innerBounds.width + delta.x * handle.x) / this._innerBounds.width;
scaleY = (this._innerBounds.height + delta.y * handle.y) / this._innerBounds.height;
}
for (i = 0; i < this.shapes.length; i++) {
shape = this.shapes[i];
bounds = shape.bounds();
if (dragging) {
if (!canDrag(shape)) {
continue;
}
newBounds = this._displaceBounds(bounds, dtl, dbr, dragging);
} else {
newBounds = bounds.clone();
newBounds.scale(scaleX, scaleY, staticPoint, this._innerBounds.center(), shape.rotate().angle);
var newCenter = newBounds.center();
newCenter.rotate(bounds.center(), -this._angle);
newBounds = new Rect(newCenter.x - newBounds.width / 2, newCenter.y - newBounds.height / 2, newBounds.width, newBounds.height);
}
if (newBounds.width >= shape.options.minWidth && newBounds.height >= shape.options.minHeight) {
var oldBounds = bounds;
shape.bounds(newBounds);
if (shape.hasOwnProperty('layout')) {
shape.layout(shape, oldBounds, newBounds);
}
if (oldBounds.width !== newBounds.width || oldBounds.height !== newBounds.height) {
shape.rotate(shape.rotate().angle);
}
changed += 1;
}
}
if (changed) {
if (changed == i) {
newBounds = this._displaceBounds(this._innerBounds, dtl, dbr, dragging);
this.bounds(newBounds);
} else {
this.refreshBounds();
}
this.refresh();
}
this._positions();
}
this._cp = p;
},
isDragHandle: function (handle) {
return handle.x === 0 && handle.y === 0;
},
cancel: function () {
var shapes = this.shapes;
var states = this.shapeStates;
for (var idx = 0; idx < shapes.length; idx++) {
shapes[idx].bounds(states[idx]);
}
this.refreshBounds();
this.refresh();
this._manipulating = undefined;
this._internalChange = undefined;
this._rotating = undefined;
},
_truncatePositionToGuides: function (bounds) {
if (this.diagram.ruler) {
return this.diagram.ruler.truncatePositionToGuides(bounds);
}
return bounds;
},
_truncateSizeToGuides: function (bounds) {
if (this.diagram.ruler) {
return this.diagram.ruler.truncateSizeToGuides(bounds);
}
return bounds;
},
_truncateAngle: function (a) {
var snap = this.snapOptions();
var snapAngle = Math.max(snap.angle || DEFAULT_SNAP_ANGLE, MIN_SNAP_ANGLE);
return snap ? Math.floor(a % 360 / snapAngle) * snapAngle : a % 360;
},
_truncateDistance: function (d) {
if (d instanceof diagram.Point) {
return new diagram.Point(this._truncateDistance(d.x), this._truncateDistance(d.y));
} else {
var snap = this.snapOptions() || {};
var snapSize = Math.max(snap.size || DEFAULT_SNAP_SIZE, MIN_SNAP_SIZE);
return snap ? Math.floor(d / snapSize) * snapSize : d;
}
},
snapOptions: function () {
var editable = this.diagram.options.editable;
var snap = ((editable || {}).drag || {}).snap || {};
return snap;
},
shouldSnap: function () {
var editable = this.diagram.options.editable;
var drag = (editable || {}).drag;
var snap = (drag || {}).snap;
return editable !== false && drag !== false && snap !== false;
},
_displaceBounds: function (bounds, dtl, dbr, dragging) {
var tl = bounds.topLeft().plus(dtl), br = bounds.bottomRight().plus(dbr), newBounds = Rect.fromPoints(tl, br), newCenter;
if (!dragging) {
newCenter = newBounds.center();
newCenter.rotate(bounds.center(), -this._angle);
newBounds = new Rect(newCenter.x - newBounds.width / 2, newCenter.y - newBounds.height / 2, newBounds.width, newBounds.height);
}
return newBounds;
},
stop: function () {
var unit, i, shape;
if (this._cp != this._sp) {
if (this._rotating) {
unit = new RotateUnit(this, this.shapes, this.initialRotates);
this._rotating = false;
} else if (this._diffStates()) {
if (this.diagram.ruler) {
for (i = 0; i < this.shapes.length; i++) {
shape = this.shapes[i];
var bounds = shape.bounds();
bounds = this._truncateSizeToGuides(this._truncatePositionToGuides(bounds));
shape.bounds(bounds);
this.refreshBounds();
this.refresh();
}
}
for (i = 0; i < this.shapes.length; i++) {
shape = this.shapes[i];
shape.updateModel();
}
unit = new TransformUnit(this.shapes, this.shapeStates, this);
this.diagram._syncShapeChanges();
}
}
this._manipulating = undefined;
this._internalChange = undefined;
this._rotating = undefined;
return unit;
},
_diffStates: function () {
var shapes = this.shapes;
var states = this.shapeStates;
for (var idx = 0; idx < shapes.length; idx++) {
if (!shapes[idx].bounds().equals(states[idx])) {
return true;
}
}
return false;
},
refreshBounds: function () {
var bounds = this.shapes.length == 1 ? this.shapes[0].bounds().clone() : this.diagram.boundingBox(this.shapes, true);
this.bounds(bounds);
},
refresh: function () {
var that = this, b, bounds;
if (this.shapes.length > 0) {
bounds = this.bounds();
this.visual.visible(true);
this.visual.position(bounds.topLeft());
$.each(this.map, function () {
b = that._getHandleBounds(new Point(this.x, this.y));
this.visual.position(b.topLeft());
});
this.visual.position(bounds.topLeft());
var center = new Point(bounds.width / 2, bounds.height / 2);
this.visual.rotate(this._angle, center);
this.rect.redraw({
width: bounds.width,
height: bounds.height
});
if (this.rotationThumb) {
var thumb = this.options.editable.rotate.thumb;
this._rotationThumbBounds = new Rect(bounds.center().x, bounds.y + thumb.y, 0, 0).inflate(thumb.width);
this.rotationThumb.redraw({ x: bounds.width / 2 - thumb.width / 2 });
}
} else {
this.visual.visible(false);
}
}
});
var Selector = Class.extend({
init: function (diagram) {
var selectable = diagram.options.selectable;
this.options = deepExtend({}, this.options, selectable);
this.visual = new Rectangle(this.options);
this.diagram = diagram;
},
options: {
stroke: {
color: '#778899',
width: 1,
dashType: 'dash'
},
fill: { color: TRANSPARENT }
},
start: function (p) {
this._sp = this._ep = p;
this.refresh();
this.diagram._adorn(this, true);
},
end: function () {
this._sp = this._ep = undefined;
this.diagram._adorn(this, false);
},
bounds: function (value) {
if (value) {
this._bounds = value;
}
return this._bounds;
},
move: function (p) {
this._ep = p;
this.refresh();
},
refresh: function () {
if (this._sp) {
var visualBounds = Rect.fromPoints(this.diagram.modelToLayer(this._sp), this.diagram.modelToLayer(this._ep));
this.bounds(Rect.fromPoints(this._sp, this._ep));
this.visual.position(visualBounds.topLeft());
this.visual.redraw({
height: visualBounds.height + 1,
width: visualBounds.width + 1
});
}
}
});
var ConnectorVisual = Class.extend({
init: function (connector) {
this.options = deepExtend({}, connector.options);
this._c = connector;
this.visual = new Circle(this.options);
this.refresh();
},
_hover: function (value) {
var options = this.options, hover = options.hover, stroke = options.stroke, fill = options.fill;
if (value && Utils.isDefined(hover.stroke)) {
stroke = deepExtend({}, stroke, hover.stroke);
}
if (value && Utils.isDefined(hover.fill)) {
fill = hover.fill;
}
this.visual.redraw({
stroke: stroke,
fill: fill
});
},
refresh: function () {
var p = this._c.shape.diagram.modelToView(this._c.position()), relative = p.minus(this._c.shape.bounds('transformed').topLeft()), value = new Rect(p.x, p.y, 0, 0);
value.inflate(this.options.width / 2, this.options.height / 2);
this._visualBounds = value;
this.visual.redraw({ center: new Point(relative.x, relative.y) });
},
_hitTest: function (p) {
var tp = this._c.shape.diagram.modelToView(p);
return this._visualBounds.contains(tp);
}
});
function canDrag(element) {
var editable = element.options.editable;
return editable && editable.drag !== false;
}
function hitTestShapeConnectors(shape, point) {
var connector, position, rect;
for (var idx = 0; idx < shape.connectors.length; idx++) {
connector = shape.connectors[idx];
position = connector.position();
rect = new Rect(position.x, position.y);
rect.inflate(HIT_TEST_DISTANCE, HIT_TEST_DISTANCE);
if (rect.contains(point)) {
return connector;
}
}
}
deepExtend(diagram, {
CompositeUnit: CompositeUnit,
TransformUnit: TransformUnit,
PanUndoUnit: PanUndoUnit,
AddShapeUnit: AddShapeUnit,
AddConnectionUnit: AddConnectionUnit,
DeleteShapeUnit: DeleteShapeUnit,
DeleteConnectionUnit: DeleteConnectionUnit,
ConnectionEditAdorner: ConnectionEditAdorner,
ConnectionTool: ConnectionTool,
ConnectorVisual: ConnectorVisual,
UndoRedoService: UndoRedoService,
ResizingAdorner: ResizingAdorner,
Selector: Selector,
ToolService: ToolService,
ConnectorsAdorner: ConnectorsAdorner,
LayoutUndoUnit: LayoutUndoUnit,
ConnectionEditUnit: ConnectionEditUnit,
ToFrontUnit: ToFrontUnit,
ToBackUnit: ToBackUnit,
ConnectionRouterBase: ConnectionRouterBase,
PolylineRouter: PolylineRouter,
CascadingRouter: CascadingRouter,
SelectionTool: SelectionTool,
ScrollerTool: ScrollerTool,
PointerTool: PointerTool,
ConnectionEditTool: ConnectionEditTool,
RotateUnit: RotateUnit
});
}(window.kendo.jQuery));
}, typeof define == 'function' && define.amd ? define : function (a1, a2, a3) {
(a3 || a2)();
}));
(function (f, define) {
define('dataviz/diagram/layout', ['dataviz/diagram/math'], f);
}(function () {
(function ($, undefined) {
var kendo = window.kendo, diagram = kendo.dataviz.diagram, Graph = diagram.Graph, Node = diagram.Node, Link = diagram.Link, deepExtend = kendo.deepExtend, Size = diagram.Size, Rect = diagram.Rect, Dictionary = diagram.Dictionary, Set = diagram.Set, HyperTree = diagram.Graph, Utils = diagram.Utils, Point = diagram.Point, EPSILON = 0.000001, DEG_TO_RAD = Math.PI / 180, contains = Utils.contains, grep = $.grep;
var LayoutBase = kendo.Class.extend({
defaultOptions: {
type: 'Tree',
subtype: 'Down',
roots: null,
animate: false,
limitToView: false,
friction: 0.9,
nodeDistance: 50,
iterations: 300,
horizontalSeparation: 90,
verticalSeparation: 50,
underneathVerticalTopOffset: 15,
underneathHorizontalOffset: 15,
underneathVerticalSeparation: 15,
grid: {
width: 1500,
offsetX: 50,
offsetY: 50,
componentSpacingX: 20,
componentSpacingY: 20
},
layerSeparation: 50,
layeredIterations: 2,
startRadialAngle: 0,
endRadialAngle: 360,
radialSeparation: 150,
radialFirstLevelSeparation: 200,
keepComponentsInOneRadialLayout: false,
ignoreContainers: true,
layoutContainerChildren: false,
ignoreInvisible: true,
animateTransitions: false
},
init: function () {
},
gridLayoutComponents: function (components) {
if (!components) {
throw 'No components supplied.';
}
Utils.forEach(components, function (c) {
c.calcBounds();
});
components.sort(function (a, b) {
return b.bounds.width - a.bounds.width;
});
var maxWidth = this.options.grid.width, offsetX = this.options.grid.componentSpacingX, offsetY = this.options.grid.componentSpacingY, height = 0, startX = this.options.grid.offsetX, startY = this.options.grid.offsetY, x = startX, y = startY, i, resultLinkSet = [], resultNodeSet = [];
while (components.length > 0) {
if (x >= maxWidth) {
x = startX;
y += height + offsetY;
height = 0;
}
var component = components.pop();
this.moveToOffset(component, new Point(x, y));
for (i = 0; i < component.nodes.length; i++) {
resultNodeSet.push(component.nodes[i]);
}
for (i = 0; i < component.links.length; i++) {
resultLinkSet.push(component.links[i]);
}
var boundingRect = component.bounds;
var currentHeight = boundingRect.height;
if (currentHeight <= 0 || isNaN(currentHeight)) {
currentHeight = 0;
}
var currentWidth = boundingRect.width;
if (currentWidth <= 0 || isNaN(currentWidth)) {
currentWidth = 0;
}
if (currentHeight >= height) {
height = currentHeight;
}
x += currentWidth + offsetX;
}
return {
nodes: resultNodeSet,
links: resultLinkSet
};
},
moveToOffset: function (component, p) {
var i, j, bounds = component.bounds, deltax = p.x - bounds.x, deltay = p.y - bounds.y;
for (i = 0; i < component.nodes.length; i++) {
var node = component.nodes[i];
var nodeBounds = node.bounds();
if (nodeBounds.width === 0 && nodeBounds.height === 0 && nodeBounds.x === 0 && nodeBounds.y === 0) {
nodeBounds = new Rect(0, 0, 0, 0);
}
nodeBounds.x += deltax;
nodeBounds.y += deltay;
node.bounds(nodeBounds);
}
for (i = 0; i < component.links.length; i++) {
var link = component.links[i];
if (link.points) {
var newpoints = [];
var points = link.points;
for (j = 0; j < points.length; j++) {
var pt = points[j];
pt.x += deltax;
pt.y += deltay;
newpoints.push(pt);
}
link.points = newpoints;
}
}
this.currentHorizontalOffset += bounds.width + this.options.grid.offsetX;
return new Point(deltax, deltay);
},
transferOptions: function (options) {
this.options = kendo.deepExtend({}, this.defaultOptions);
if (Utils.isUndefined(options)) {
return;
}
this.options = kendo.deepExtend(this.options, options || {});
}
});
var DiagramToHyperTreeAdapter = kendo.Class.extend({
init: function (diagram) {
this.nodeMap = new Dictionary();
this.shapeMap = new Dictionary();
this.nodes = [];
this.edges = [];
this.edgeMap = new Dictionary();
this.finalNodes = [];
this.finalLinks = [];
this.ignoredConnections = [];
this.ignoredShapes = [];
this.hyperMap = new Dictionary();
this.hyperTree = new Graph();
this.finalGraph = null;
this.diagram = diagram;
},
convert: function (options) {
if (Utils.isUndefined(this.diagram)) {
throw 'No diagram to convert.';
}
this.options = kendo.deepExtend({
ignoreInvisible: true,
ignoreContainers: true,
layoutContainerChildren: false
}, options || {});
this.clear();
this._renormalizeShapes();
this._renormalizeConnections();
this.finalNodes = new Dictionary(this.nodes);
this.finalLinks = new Dictionary(this.edges);
this.finalGraph = new Graph();
this.finalNodes.forEach(function (n) {
this.finalGraph.addNode(n);
}, this);
this.finalLinks.forEach(function (l) {
this.finalGraph.addExistingLink(l);
}, this);
return this.finalGraph;
},
mapConnection: function (connection) {
return this.edgeMap.get(connection.id);
},
mapShape: function (shape) {
return this.nodeMap.get(shape.id);
},
getEdge: function (a, b) {
return Utils.first(a.links, function (link) {
return link.getComplement(a) === b;
});
},
clear: function () {
this.finalGraph = null;
this.hyperTree = !this.options.ignoreContainers && this.options.layoutContainerChildren ? new HyperTree() : null;
this.hyperMap = !this.options.ignoreContainers && this.options.layoutContainerChildren ? new Dictionary() : null;
this.nodeMap = new Dictionary();
this.shapeMap = new Dictionary();
this.nodes = [];
this.edges = [];
this.edgeMap = new Dictionary();
this.ignoredConnections = [];
this.ignoredShapes = [];
this.finalNodes = [];
this.finalLinks = [];
},
listToRoot: function (containerGraph) {
var list = [];
var s = containerGraph.container;
if (!s) {
return list;
}
list.push(s);
while (s.parentContainer) {
s = s.parentContainer;
list.push(s);
}
list.reverse();
return list;
},
firstNonIgnorableContainer: function (shape) {
if (shape.isContainer && !this._isIgnorableItem(shape)) {
return shape;
}
return !shape.parentContainer ? null : this.firstNonIgnorableContainer(shape.parentContainer);
},
isContainerConnection: function (a, b) {
if (a.isContainer && this.isDescendantOf(a, b)) {
return true;
}
return b.isContainer && this.isDescendantOf(b, a);
},
isDescendantOf: function (scope, a) {
if (!scope.isContainer) {
throw 'Expecting a container.';
}
if (scope === a) {
return false;
}
if (contains(scope.children, a)) {
return true;
}
var containers = [];
for (var i = 0, len = scope.children.length; i < len; i++) {
var c = scope.children[i];
if (c.isContainer && this.isDescendantOf(c, a)) {
containers.push(c);
}
}
return containers.length > 0;
},
isIgnorableItem: function (shape) {
if (this.options.ignoreInvisible) {
if (shape.isCollapsed && this._isVisible(shape)) {
return false;
}
if (!shape.isCollapsed && this._isVisible(shape)) {
return false;
}
return true;
} else {
return shape.isCollapsed && !this._isTop(shape);
}
},
isShapeMapped: function (shape) {
return shape.isCollapsed && !this._isVisible(shape) && !this._isTop(shape);
},
leastCommonAncestor: function (a, b) {
if (!a) {
throw 'Parameter should not be null.';
}
if (!b) {
throw 'Parameter should not be null.';
}
if (!this.hyperTree) {
throw 'No hypertree available.';
}
var al = this.listToRoot(a);
var bl = this.listToRoot(b);
var found = null;
if (Utils.isEmpty(al) || Utils.isEmpty(bl)) {
return this.hyperTree.root.data;
}
var xa = al[0];
var xb = bl[0];
var i = 0;
while (xa === xb) {
found = al[i];
i++;
if (i >= al.length || i >= bl.length) {
break;
}
xa = al[i];
xb = bl[i];
}
if (!found) {
return this.hyperTree.root.data;
} else {
return grep(this.hyperTree.nodes, function (n) {
return n.data.container === found;
});
}
},
_isTop: function (item) {
return !item.parentContainer;
},
_isVisible: function (shape) {
if (!shape.visible()) {
return false;
}
return !shape.parentContainer ? shape.visible() : this._isVisible(shape.parentContainer);
},
_isCollapsed: function (shape) {
if (shape.isContainer && shape.isCollapsed) {
return true;
}
return shape.parentContainer && this._isCollapsed(shape.parentContainer);
},
_renormalizeShapes: function () {
if (this.options.ignoreContainers) {
for (var i = 0, len = this.diagram.shapes.length; i < len; i++) {
var shape = this.diagram.shapes[i];
if (this.options.ignoreInvisible && !this._isVisible(shape) || shape.isContainer) {
this.ignoredShapes.push(shape);
continue;
}
var node = new Node(shape.id, shape);
node.isVirtual = false;
this.nodeMap.add(shape.id, node);
this.nodes.push(node);
}
} else {
throw 'Containers are not supported yet, but stay tuned.';
}
},
_renormalizeConnections: function () {
if (this.diagram.connections.length === 0) {
return;
}
for (var i = 0, len = this.diagram.connections.length; i < len; i++) {
var conn = this.diagram.connections[i];
if (this.isIgnorableItem(conn)) {
this.ignoredConnections.push(conn);
continue;
}
var source = !conn.sourceConnector ? null : conn.sourceConnector.shape;
var sink = !conn.targetConnector ? null : conn.targetConnector.shape;
if (!source || !sink) {
this.ignoredConnections.push(conn);
continue;
}
if (contains(this.ignoredShapes, source) && !this.shapeMap.containsKey(source)) {
this.ignoredConnections.push(conn);
continue;
}
if (contains(this.ignoredShapes, sink) && !this.shapeMap.containsKey(sink)) {
this.ignoredConnections.push(conn);
continue;
}
if (this.shapeMap.containsKey(source)) {
source = this.shapeMap[source];
}
if (this.shapeMap.containsKey(sink)) {
sink = this.shapeMap[sink];
}
var sourceNode = this.mapShape(source);
var sinkNode = this.mapShape(sink);
if (sourceNode === sinkNode || this.areConnectedAlready(sourceNode, sinkNode)) {
this.ignoredConnections.push(conn);
continue;
}
if (sourceNode === null || sinkNode === null) {
throw 'A shape was not mapped to a node.';
}
if (this.options.ignoreContainers) {
if (sourceNode.isVirtual || sinkNode.isVirtual) {
this.ignoredConnections.push(conn);
continue;
}
var newEdge = new Link(sourceNode, sinkNode, conn.id, conn);
this.edgeMap.add(conn.id, newEdge);
this.edges.push(newEdge);
} else {
throw 'Containers are not supported yet, but stay tuned.';
}
}
},
areConnectedAlready: function (n, m) {
return Utils.any(this.edges, function (l) {
return l.source === n && l.target === m || l.source === m && l.target === n;
});
}
});
var SpringLayout = LayoutBase.extend({
init: function (diagram) {
var that = this;
LayoutBase.fn.init.call(that);
if (Utils.isUndefined(diagram)) {
throw 'Diagram is not specified.';
}
this.diagram = diagram;
},
layout: function (options) {
this.transferOptions(options);
var adapter = new DiagramToHyperTreeAdapter(this.diagram);
var graph = adapter.convert(options);
if (graph.isEmpty()) {
return;
}
var components = graph.getConnectedComponents();
if (Utils.isEmpty(components)) {
return;
}
for (var i = 0; i < components.length; i++) {
var component = components[i];
this.layoutGraph(component, options);
}
var finalNodeSet = this.gridLayoutComponents(components);
return new diagram.LayoutState(this.diagram, finalNodeSet);
},
layoutGraph: function (graph, options) {
if (Utils.isDefined(options)) {
this.transferOptions(options);
}
this.graph = graph;
var initialTemperature = this.options.nodeDistance * 9;
this.temperature = initialTemperature;
var guessBounds = this._expectedBounds();
this.width = guessBounds.width;
this.height = guessBounds.height;
for (var step = 0; step < this.options.iterations; step++) {
this.refineStage = step >= this.options.iterations * 5 / 6;
this.tick();
this.temperature = this.refineStage ? initialTemperature / 30 : initialTemperature * (1 - step / (2 * this.options.iterations));
}
},
tick: function () {
var i;
for (i = 0; i < this.graph.nodes.length; i++) {
this._repulsion(this.graph.nodes[i]);
}
for (i = 0; i < this.graph.links.length; i++) {
this._attraction(this.graph.links[i]);
}
for (i = 0; i < this.graph.nodes.length; i++) {
var node = this.graph.nodes[i];
var offset = Math.sqrt(node.dx * node.dx + node.dy * node.dy);
if (offset === 0) {
return;
}
node.x += Math.min(offset, this.temperature) * node.dx / offset;
node.y += Math.min(offset, this.temperature) * node.dy / offset;
if (this.options.limitToView) {
node.x = Math.min(this.width, Math.max(node.width / 2, node.x));
node.y = Math.min(this.height, Math.max(node.height / 2, node.y));
}
}
},
_shake: function (node) {
var rho = Math.random() * this.options.nodeDistance / 4;
var alpha = Math.random() * 2 * Math.PI;
node.x += rho * Math.cos(alpha);
node.y -= rho * Math.sin(alpha);
},
_InverseSquareForce: function (d, n, m) {
var force;
if (!this.refineStage) {
force = Math.pow(d, 2) / Math.pow(this.options.nodeDistance, 2);
} else {
var deltax = n.x - m.x;
var deltay = n.y - m.y;
var wn = n.width / 2;
var hn = n.height / 2;
var wm = m.width / 2;
var hm = m.height / 2;
force = Math.pow(deltax, 2) / Math.pow(wn + wm + this.options.nodeDistance, 2) + Math.pow(deltay, 2) / Math.pow(hn + hm + this.options.nodeDistance, 2);
}
return force * 4 / 3;
},
_SquareForce: function (d, n, m) {
return 1 / this._InverseSquareForce(d, n, m);
},
_repulsion: function (n) {
n.dx = 0;
n.dy = 0;
Utils.forEach(this.graph.nodes, function (m) {
if (m === n) {
return;
}
while (n.x === m.x && n.y === m.y) {
this._shake(m);
}
var vx = n.x - m.x;
var vy = n.y - m.y;
var distance = Math.sqrt(vx * vx + vy * vy);
var r = this._SquareForce(distance, n, m) * 2;
n.dx += vx / distance * r;
n.dy += vy / distance * r;
}, this);
},
_attraction: function (link) {
var t = link.target;
var s = link.source;
if (s === t) {
return;
}
while (s.x === t.x && s.y === t.y) {
this._shake(t);
}
var vx = s.x - t.x;
var vy = s.y - t.y;
var distance = Math.sqrt(vx * vx + vy * vy);
var a = this._InverseSquareForce(distance, s, t) * 5;
var dx = vx / distance * a;
var dy = vy / distance * a;
t.dx += dx;
t.dy += dy;
s.dx -= dx;
s.dy -= dy;
},
_expectedBounds: function () {
var size, N = this.graph.nodes.length, ratio = 1.5, multiplier = 4;
if (N === 0) {
return size;
}
size = Utils.fold(this.graph.nodes, function (s, node) {
var area = node.width * node.height;
if (area > 0) {
s += Math.sqrt(area);
return s;
}
return 0;
}, 0, this);
var av = size / N;
var squareSize = av * Math.ceil(Math.sqrt(N));
var width = squareSize * Math.sqrt(ratio);
var height = squareSize / Math.sqrt(ratio);
return {
width: width * multiplier,
height: height * multiplier
};
}
});
var TreeLayoutProcessor = kendo.Class.extend({
init: function (options) {
this.center = null;
this.options = options;
},
layout: function (treeGraph, root) {
this.graph = treeGraph;
if (!this.graph.nodes || this.graph.nodes.length === 0) {
return;
}
if (!contains(this.graph.nodes, root)) {
throw 'The given root is not in the graph.';
}
this.center = root;
this.graph.cacheRelationships();
this.layoutSwitch();
},
layoutLeft: function (left) {
this.setChildrenDirection(this.center, 'Left', false);
this.setChildrenLayout(this.center, 'Default', false);
var h = 0, w = 0, y, i, node;
for (i = 0; i < left.length; i++) {
node = left[i];
node.TreeDirection = 'Left';
var s = this.measure(node, Size.Empty);
w = Math.max(w, s.Width);
h += s.height + this.options.verticalSeparation;
}
h -= this.options.verticalSeparation;
var x = this.center.x - this.options.horizontalSeparation;
y = this.center.y + (this.center.height - h) / 2;
for (i = 0; i < left.length; i++) {
node = left[i];
var p = new Point(x - node.Size.width, y);
this.arrange(node, p);
y += node.Size.height + this.options.verticalSeparation;
}
},
layoutRight: function (right) {
this.setChildrenDirection(this.center, 'Right', false);
this.setChildrenLayout(this.center, 'Default', false);
var h = 0, w = 0, y, i, node;
for (i = 0; i < right.length; i++) {
node = right[i];
node.TreeDirection = 'Right';
var s = this.measure(node, Size.Empty);
w = Math.max(w, s.Width);
h += s.height + this.options.verticalSeparation;
}
h -= this.options.verticalSeparation;
var x = this.center.x + this.options.horizontalSeparation + this.center.width;
y = this.center.y + (this.center.height - h) / 2;
for (i = 0; i < right.length; i++) {
node = right[i];
var p = new Point(x, y);
this.arrange(node, p);
y += node.Size.height + this.options.verticalSeparation;
}
},
layoutUp: function (up) {
this.setChildrenDirection(this.center, 'Up', false);
this.setChildrenLayout(this.center, 'Default', false);
var w = 0, y, node, i;
for (i = 0; i < up.length; i++) {
node = up[i];
node.TreeDirection = 'Up';
var s = this.measure(node, Size.Empty);
w += s.width + this.options.horizontalSeparation;
}
w -= this.options.horizontalSeparation;
var x = this.center.x + this.center.width / 2 - w / 2;
for (i = 0; i < up.length; i++) {
node = up[i];
y = this.center.y - this.options.verticalSeparation - node.Size.height;
var p = new Point(x, y);
this.arrange(node, p);
x += node.Size.width + this.options.horizontalSeparation;
}
},
layoutDown: function (down) {
var node, i;
this.setChildrenDirection(this.center, 'Down', false);
this.setChildrenLayout(this.center, 'Default', false);
var w = 0, y;
for (i = 0; i < down.length; i++) {
node = down[i];
node.treeDirection = 'Down';
var s = this.measure(node, Size.Empty);
w += s.width + this.options.horizontalSeparation;
}
w -= this.options.horizontalSeparation;
var x = this.center.x + this.center.width / 2 - w / 2;
y = this.center.y + this.options.verticalSeparation + this.center.height;
for (i = 0; i < down.length; i++) {
node = down[i];
var p = new Point(x, y);
this.arrange(node, p);
x += node.Size.width + this.options.horizontalSeparation;
}
},
layoutRadialTree: function () {
this.setChildrenDirection(this.center, 'Radial', false);
this.setChildrenLayout(this.center, 'Default', false);
this.previousRoot = null;
var startAngle = this.options.startRadialAngle * DEG_TO_RAD;
var endAngle = this.options.endRadialAngle * DEG_TO_RAD;
if (endAngle <= startAngle) {
throw 'Final angle should not be less than the start angle.';
}
this.maxDepth = 0;
this.origin = new Point(this.center.x, this.center.y);
this.calculateAngularWidth(this.center, 0);
if (this.maxDepth > 0) {
this.radialLayout(this.center, this.options.radialFirstLevelSeparation, startAngle, endAngle);
}
this.center.Angle = endAngle - startAngle;
},
tipOverTree: function (down, startFromLevel) {
if (Utils.isUndefined(startFromLevel)) {
startFromLevel = 0;
}
this.setChildrenDirection(this.center, 'Down', false);
this.setChildrenLayout(this.center, 'Default', false);
this.setChildrenLayout(this.center, 'Underneath', false, startFromLevel);
var w = 0, y, node, i;
for (i = 0; i < down.length; i++) {
node = down[i];
node.TreeDirection = 'Down';
var s = this.measure(node, Size.Empty);
w += s.width + this.options.horizontalSeparation;
}
w -= this.options.horizontalSeparation;
w -= down[down.length - 1].width;
w += down[down.length - 1].associatedShape.bounds().width;
var x = this.center.x + this.center.width / 2 - w / 2;
y = this.center.y + this.options.verticalSeparation + this.center.height;
for (i = 0; i < down.length; i++) {
node = down[i];
var p = new Point(x, y);
this.arrange(node, p);
x += node.Size.width + this.options.horizontalSeparation;
}
},
calculateAngularWidth: function (n, d) {
if (d > this.maxDepth) {
this.maxDepth = d;
}
var aw = 0, w = 1000, h = 1000, diameter = d === 0 ? 0 : Math.sqrt(w * w + h * h) / d;
if (n.children.length > 0) {
for (var i = 0, len = n.children.length; i < len; i++) {
var child = n.children[i];
aw += this.calculateAngularWidth(child, d + 1);
}
aw = Math.max(diameter, aw);
} else {
aw = diameter;
}
n.sectorAngle = aw;
return aw;
},
sortChildren: function (n) {
var basevalue = 0, i;
if (n.parents.length > 1) {
throw 'Node is not part of a tree.';
}
var p = n.parents[0];
if (p) {
var pl = new Point(p.x, p.y);
var nl = new Point(n.x, n.y);
basevalue = this.normalizeAngle(Math.atan2(pl.y - nl.y, pl.x - nl.x));
}
var count = n.children.length;
if (count === 0) {
return null;
}
var angle = [];
var idx = [];
for (i = 0; i < count; ++i) {
var c = n.children[i];
var l = new Point(c.x, c.y);
idx[i] = i;
angle[i] = this.normalizeAngle(-basevalue + Math.atan2(l.y - l.y, l.x - l.x));
}
Utils.bisort(angle, idx);
var col = [];
var children = n.children;
for (i = 0; i < count; ++i) {
col.push(children[idx[i]]);
}
return col;
},
normalizeAngle: function (angle) {
while (angle > Math.PI * 2) {
angle -= 2 * Math.PI;
}
while (angle < 0) {
angle += Math.PI * 2;
}
return angle;
},
radialLayout: function (node, radius, startAngle, endAngle) {
var deltaTheta = endAngle - startAngle;
var deltaThetaHalf = deltaTheta / 2;
var parentSector = node.sectorAngle;
var fraction = 0;
var sorted = this.sortChildren(node);
for (var i = 0, len = sorted.length; i < len; i++) {
var childNode = sorted[i];
var cp = childNode;
var childAngleFraction = cp.sectorAngle / parentSector;
if (childNode.children.length > 0) {
this.radialLayout(childNode, radius + this.options.radialSeparation, startAngle + fraction * deltaTheta, startAngle + (fraction + childAngleFraction) * deltaTheta);
}
this.setPolarLocation(childNode, radius, startAngle + fraction * deltaTheta + childAngleFraction * deltaThetaHalf);
cp.angle = childAngleFraction * deltaTheta;
fraction += childAngleFraction;
}
},
setPolarLocation: function (node, radius, angle) {
node.x = this.origin.x + radius * Math.cos(angle);
node.y = this.origin.y + radius * Math.sin(angle);
node.BoundingRectangle = new Rect(node.x, node.y, node.width, node.height);
},
setChildrenDirection: function (node, direction, includeStart) {
var rootDirection = node.treeDirection;
this.graph.depthFirstTraversal(node, function (n) {
n.treeDirection = direction;
});
if (!includeStart) {
node.treeDirection = rootDirection;
}
},
setChildrenLayout: function (node, layout, includeStart, startFromLevel) {
if (Utils.isUndefined(startFromLevel)) {
startFromLevel = 0;
}
var rootLayout = node.childrenLayout;
if (startFromLevel > 0) {
this.graph.assignLevels(node);
this.graph.depthFirstTraversal(node, function (s) {
if (s.level >= startFromLevel + 1) {
s.childrenLayout = layout;
}
});
} else {
this.graph.depthFirstTraversal(node, function (s) {
s.childrenLayout = layout;
});
if (!includeStart) {
node.childrenLayout = rootLayout;
}
}
},
measure: function (node, givenSize) {
var w = 0, h = 0, s;
var result = new Size(0, 0);
if (!node) {
throw '';
}
var b = node.associatedShape.bounds();
var shapeWidth = b.width;
var shapeHeight = b.height;
if (node.parents.length !== 1) {
throw 'Node not in a spanning tree.';
}
var parent = node.parents[0];
if (node.treeDirection === 'Undefined') {
node.treeDirection = parent.treeDirection;
}
if (Utils.isEmpty(node.children)) {
result = new Size(Math.abs(shapeWidth) < EPSILON ? 50 : shapeWidth, Math.abs(shapeHeight) < EPSILON ? 25 : shapeHeight);
} else if (node.children.length === 1) {
switch (node.treeDirection) {
case 'Radial':
s = this.measure(node.children[0], givenSize);
w = shapeWidth + this.options.radialSeparation * Math.cos(node.AngleToParent) + s.width;
h = shapeHeight + Math.abs(this.options.radialSeparation * Math.sin(node.AngleToParent)) + s.height;
break;
case 'Left':
case 'Right':
switch (node.childrenLayout) {
case 'TopAlignedWithParent':
break;
case 'BottomAlignedWithParent':
break;
case 'Underneath':
s = this.measure(node.children[0], givenSize);
w = shapeWidth + s.width + this.options.underneathHorizontalOffset;
h = shapeHeight + this.options.underneathVerticalTopOffset + s.height;
break;
case 'Default':
s = this.measure(node.children[0], givenSize);
w = shapeWidth + this.options.horizontalSeparation + s.width;
h = Math.max(shapeHeight, s.height);
break;
default:
throw 'Unhandled TreeDirection in the Radial layout measuring.';
}
break;
case 'Up':
case 'Down':
switch (node.childrenLayout) {
case 'TopAlignedWithParent':
case 'BottomAlignedWithParent':
break;
case 'Underneath':
s = this.measure(node.children[0], givenSize);
w = Math.max(shapeWidth, s.width + this.options.underneathHorizontalOffset);
h = shapeHeight + this.options.underneathVerticalTopOffset + s.height;
break;
case 'Default':
s = this.measure(node.children[0], givenSize);
h = shapeHeight + this.options.verticalSeparation + s.height;
w = Math.max(shapeWidth, s.width);
break;
default:
throw 'Unhandled TreeDirection in the Down layout measuring.';
}
break;
default:
throw 'Unhandled TreeDirection in the layout measuring.';
}
result = new Size(w, h);
} else {
var i, childNode;
switch (node.treeDirection) {
case 'Left':
case 'Right':
switch (node.childrenLayout) {
case 'TopAlignedWithParent':
case 'BottomAlignedWithParent':
break;
case 'Underneath':
w = shapeWidth;
h = shapeHeight + this.options.underneathVerticalTopOffset;
for (i = 0; i < node.children.length; i++) {
childNode = node.children[i];
s = this.measure(childNode, givenSize);
w = Math.max(w, s.width + this.options.underneathHorizontalOffset);
h += s.height + this.options.underneathVerticalSeparation;
}
h -= this.options.underneathVerticalSeparation;
break;
case 'Default':
w = shapeWidth;
h = 0;
for (i = 0; i < node.children.length; i++) {
childNode = node.children[i];
s = this.measure(childNode, givenSize);
w = Math.max(w, shapeWidth + this.options.horizontalSeparation + s.width);
h += s.height + this.options.verticalSeparation;
}
h -= this.options.verticalSeparation;
break;
default:
throw 'Unhandled TreeDirection in the Right layout measuring.';
}
break;
case 'Up':
case 'Down':
switch (node.childrenLayout) {
case 'TopAlignedWithParent':
case 'BottomAlignedWithParent':
break;
case 'Underneath':
w = shapeWidth;
h = shapeHeight + this.options.underneathVerticalTopOffset;
for (i = 0; i < node.children.length; i++) {
childNode = node.children[i];
s = this.measure(childNode, givenSize);
w = Math.max(w, s.width + this.options.underneathHorizontalOffset);
h += s.height + this.options.underneathVerticalSeparation;
}
h -= this.options.underneathVerticalSeparation;
break;
case 'Default':
w = 0;
h = 0;
for (i = 0; i < node.children.length; i++) {
childNode = node.children[i];
s = this.measure(childNode, givenSize);
w += s.width + this.options.horizontalSeparation;
h = Math.max(h, s.height + this.options.verticalSeparation + shapeHeight);
}
w -= this.options.horizontalSeparation;
break;
default:
throw 'Unhandled TreeDirection in the Down layout measuring.';
}
break;
default:
throw 'Unhandled TreeDirection in the layout measuring.';
}
result = new Size(w, h);
}
node.SectorAngle = Math.sqrt(w * w / 4 + h * h / 4);
node.Size = result;
return result;
},
arrange: function (n, p) {
var i, pp, child, node, childrenwidth, b = n.associatedShape.bounds();
var shapeWidth = b.width;
var shapeHeight = b.height;
if (Utils.isEmpty(n.children)) {
n.x = p.x;
n.y = p.y;
n.BoundingRectangle = new Rect(p.x, p.y, shapeWidth, shapeHeight);
} else {
var x, y;
var selfLocation;
switch (n.treeDirection) {
case 'Left':
switch (n.childrenLayout) {
case 'TopAlignedWithParent':
case 'BottomAlignedWithParent':
break;
case 'Underneath':
selfLocation = p;
n.x = selfLocation.x;
n.y = selfLocation.y;
n.BoundingRectangle = new Rect(n.x, n.y, n.width, n.height);
y = p.y + shapeHeight + this.options.underneathVerticalTopOffset;
for (i = 0; i < node.children.length; i++) {
node = node.children[i];
x = selfLocation.x - node.associatedShape.width - this.options.underneathHorizontalOffset;
pp = new Point(x, y);
this.arrange(node, pp);
y += node.Size.height + this.options.underneathVerticalSeparation;
}
break;
case 'Default':
selfLocation = new Point(p.x + n.Size.width - shapeWidth, p.y + (n.Size.height - shapeHeight) / 2);
n.x = selfLocation.x;
n.y = selfLocation.y;
n.BoundingRectangle = new Rect(n.x, n.y, n.width, n.height);
x = selfLocation.x - this.options.horizontalSeparation;
y = p.y;
for (i = 0; i < n.children.length; i++) {
node = n.children[i];
pp = new Point(x - node.Size.width, y);
this.arrange(node, pp);
y += node.Size.height + this.options.verticalSeparation;
}
break;
default:
throw 'Unsupported TreeDirection';
}
break;
case 'Right':
switch (n.childrenLayout) {
case 'TopAlignedWithParent':
case 'BottomAlignedWithParent':
break;
case 'Underneath':
selfLocation = p;
n.x = selfLocation.x;
n.y = selfLocation.y;
n.BoundingRectangle = new Rect(n.x, n.y, n.width, n.height);
x = p.x + shapeWidth + this.options.underneathHorizontalOffset;
y = p.y + shapeHeight + this.options.underneathVerticalTopOffset;
for (i = 0; i < n.children.length; i++) {
node = n.children[i];
pp = new Point(x, y);
this.arrange(node, pp);
y += node.Size.height + this.options.underneathVerticalSeparation;
}
break;
case 'Default':
selfLocation = new Point(p.x, p.y + (n.Size.height - shapeHeight) / 2);
n.x = selfLocation.x;
n.y = selfLocation.y;
n.BoundingRectangle = new Rect(n.x, n.y, n.width, n.height);
x = p.x + shapeWidth + this.options.horizontalSeparation;
y = p.y;
for (i = 0; i < n.children.length; i++) {
node = n.children[i];
pp = new Point(x, y);
this.arrange(node, pp);
y += node.Size.height + this.options.verticalSeparation;
}
break;
default:
throw 'Unsupported TreeDirection';
}
break;
case 'Up':
selfLocation = new Point(p.x + (n.Size.width - shapeWidth) / 2, p.y + n.Size.height - shapeHeight);
n.x = selfLocation.x;
n.y = selfLocation.y;
n.BoundingRectangle = new Rect(n.x, n.y, n.width, n.height);
if (Math.abs(selfLocation.x - p.x) < EPSILON) {
childrenwidth = 0;
for (i = 0; i < n.children.length; i++) {
child = n.children[i];
childrenwidth += child.Size.width + this.options.horizontalSeparation;
}
childrenwidth -= this.options.horizontalSeparation;
x = p.x + (shapeWidth - childrenwidth) / 2;
} else {
x = p.x;
}
for (i = 0; i < n.children.length; i++) {
node = n.children[i];
y = selfLocation.y - this.options.verticalSeparation - node.Size.height;
pp = new Point(x, y);
this.arrange(node, pp);
x += node.Size.width + this.options.horizontalSeparation;
}
break;
case 'Down':
switch (n.childrenLayout) {
case 'TopAlignedWithParent':
case 'BottomAlignedWithParent':
break;
case 'Underneath':
selfLocation = p;
n.x = selfLocation.x;
n.y = selfLocation.y;
n.BoundingRectangle = new Rect(n.x, n.y, n.width, n.height);
x = p.x + this.options.underneathHorizontalOffset;
y = p.y + shapeHeight + this.options.underneathVerticalTopOffset;
for (i = 0; i < n.children.length; i++) {
node = n.children[i];
pp = new Point(x, y);
this.arrange(node, pp);
y += node.Size.height + this.options.underneathVerticalSeparation;
}
break;
case 'Default':
selfLocation = new Point(p.x + (n.Size.width - shapeWidth) / 2, p.y);
n.x = selfLocation.x;
n.y = selfLocation.y;
n.BoundingRectangle = new Rect(n.x, n.y, n.width, n.height);
if (Math.abs(selfLocation.x - p.x) < EPSILON) {
childrenwidth = 0;
for (i = 0; i < n.children.length; i++) {
child = n.children[i];
childrenwidth += child.Size.width + this.options.horizontalSeparation;
}
childrenwidth -= this.options.horizontalSeparation;
x = p.x + (shapeWidth - childrenwidth) / 2;
} else {
x = p.x;
}
for (i = 0; i < n.children.length; i++) {
node = n.children[i];
y = selfLocation.y + this.options.verticalSeparation + shapeHeight;
pp = new Point(x, y);
this.arrange(node, pp);
x += node.Size.width + this.options.horizontalSeparation;
}
break;
default:
throw 'Unsupported TreeDirection';
}
break;
case 'None':
break;
default:
throw 'Unsupported TreeDirection';
}
}
},
layoutSwitch: function () {
if (!this.center) {
return;
}
if (Utils.isEmpty(this.center.children)) {
return;
}
var type = this.options.subtype;
if (Utils.isUndefined(type)) {
type = 'Down';
}
var single, male, female, leftcount;
var children = this.center.children;
switch (type.toLowerCase()) {
case 'radial':
case 'radialtree':
this.layoutRadialTree();
break;
case 'mindmaphorizontal':
case 'mindmap':
single = this.center.children;
if (this.center.children.length === 1) {
this.layoutRight(single);
} else {
leftcount = children.length / 2;
male = grep(this.center.children, function (n) {
return Utils.indexOf(children, n) < leftcount;
});
female = grep(this.center.children, function (n) {
return Utils.indexOf(children, n) >= leftcount;
});
this.layoutLeft(male);
this.layoutRight(female);
}
break;
case 'mindmapvertical':
single = this.center.children;
if (this.center.children.length === 1) {
this.layoutDown(single);
} else {
leftcount = children.length / 2;
male = grep(this.center.children, function (n) {
return Utils.indexOf(children, n) < leftcount;
});
female = grep(this.center.children, function (n) {
return Utils.indexOf(children, n) >= leftcount;
});
this.layoutUp(male);
this.layoutDown(female);
}
break;
case 'right':
this.layoutRight(this.center.children);
break;
case 'left':
this.layoutLeft(this.center.children);
break;
case 'up':
case 'bottom':
this.layoutUp(this.center.children);
break;
case 'down':
case 'top':
this.layoutDown(this.center.children);
break;
case 'tipover':
case 'tipovertree':
if (this.options.tipOverTreeStartLevel < 0) {
throw 'The tip-over level should be a positive integer.';
}
this.tipOverTree(this.center.children, this.options.tipOverTreeStartLevel);
break;
case 'undefined':
case 'none':
break;
}
}
});
var TreeLayout = LayoutBase.extend({
init: function (diagram) {
var that = this;
LayoutBase.fn.init.call(that);
if (Utils.isUndefined(diagram)) {
throw 'No diagram specified.';
}
this.diagram = diagram;
},
layout: function (options) {
this.transferOptions(options);
var adapter = new DiagramToHyperTreeAdapter(this.diagram);
this.graph = adapter.convert();
var finalNodeSet = this.layoutComponents();
return new diagram.LayoutState(this.diagram, finalNodeSet);
},
layoutComponents: function () {
if (this.graph.isEmpty()) {
return;
}
var components = this.graph.getConnectedComponents();
if (Utils.isEmpty(components)) {
return;
}
var layout = new TreeLayoutProcessor(this.options);
var trees = [];
for (var i = 0; i < components.length; i++) {
var component = components[i];
var treeGraph = this.getTree(component);
if (!treeGraph) {
throw 'Failed to find a spanning tree for the component.';
}
var root = treeGraph.root;
var tree = treeGraph.tree;
layout.layout(tree, root);
trees.push(tree);
}
return this.gridLayoutComponents(trees);
},
getTree: function (graph) {
var root = null;
if (this.options.roots && this.options.roots.length > 0) {
for (var i = 0, len = graph.nodes.length; i < len; i++) {
var node = graph.nodes[i];
for (var j = 0; j < this.options.roots.length; j++) {
var givenRootShape = this.options.roots[j];
if (givenRootShape === node.associatedShape) {
root = node;
break;
}
}
}
}
if (!root) {
root = graph.root();
if (!root) {
throw 'Unable to find a root for the tree.';
}
}
return this.getTreeForRoot(graph, root);
},
getTreeForRoot: function (graph, root) {
var tree = graph.getSpanningTree(root);
if (Utils.isUndefined(tree) || tree.isEmpty()) {
return null;
}
return {
tree: tree,
root: tree.root
};
}
});
var LayeredLayout = LayoutBase.extend({
init: function (diagram) {
var that = this;
LayoutBase.fn.init.call(that);
if (Utils.isUndefined(diagram)) {
throw 'Diagram is not specified.';
}
this.diagram = diagram;
},
layout: function (options) {
this.transferOptions(options);
var adapter = new DiagramToHyperTreeAdapter(this.diagram);
var graph = adapter.convert(options);
if (graph.isEmpty()) {
return;
}
var components = graph.getConnectedComponents();
if (Utils.isEmpty(components)) {
return;
}
for (var i = 0; i < components.length; i++) {
var component = components[i];
this.layoutGraph(component, options);
}
var finalNodeSet = this.gridLayoutComponents(components);
return new diagram.LayoutState(this.diagram, finalNodeSet);
},
_initRuntimeProperties: function () {
for (var k = 0; k < this.graph.nodes.length; k++) {
var node = this.graph.nodes[k];
node.layer = -1;
node.downstreamLinkCount = 0;
node.upstreamLinkCount = 0;
node.isVirtual = false;
node.uBaryCenter = 0;
node.dBaryCenter = 0;
node.upstreamPriority = 0;
node.downstreamPriority = 0;
node.gridPosition = 0;
}
},
_prepare: function (graph) {
var current = [], i, l, link;
var layerMap = new Dictionary();
var layerCount = 0;
var targetLayer, next, target;
Utils.forEach(graph.nodes, function (node) {
if (node.incoming.length === 0) {
layerMap.set(node, 0);
current.push(node);
}
});
while (current.length > 0) {
next = current.shift();
for (i = 0; i < next.outgoing.length; i++) {
link = next.outgoing[i];
target = link.target;
if (layerMap.containsKey(target)) {
targetLayer = Math.max(layerMap.get(next) + 1, layerMap.get(target));
} else {
targetLayer = layerMap.get(next) + 1;
}
layerMap.set(target, targetLayer);
if (targetLayer > layerCount) {
layerCount = targetLayer;
}
if (!contains(current, target)) {
current.push(target);
}
}
}
var sortedNodes = layerMap.keys();
sortedNodes.sort(function (o1, o2) {
var o1layer = layerMap.get(o1);
var o2layer = layerMap.get(o2);
return Utils.sign(o2layer - o1layer);
});
for (var n = 0; n < sortedNodes.length; ++n) {
var node = sortedNodes[n];
var minLayer = Number.MAX_VALUE;
if (node.outgoing.length === 0) {
continue;
}
for (l = 0; l < node.outgoing.length; ++l) {
link = node.outgoing[l];
minLayer = Math.min(minLayer, layerMap.get(link.target));
}
if (minLayer > 1) {
layerMap.set(node, minLayer - 1);
}
}
this.layers = [];
var layer;
for (i = 0; i < layerCount + 1; i++) {
layer = [];
layer.linksTo = {};
this.layers.push(layer);
}
layerMap.forEach(function (node, layer) {
node.layer = layer;
this.layers[layer].push(node);
}, this);
for (l = 0; l < this.layers.length; l++) {
layer = this.layers[l];
for (i = 0; i < layer.length; i++) {
layer[i].gridPosition = i;
}
}
},
layoutGraph: function (graph, options) {
if (Utils.isUndefined(graph)) {
throw 'No graph given or graph analysis of the diagram failed.';
}
if (Utils.isDefined(options)) {
this.transferOptions(options);
}
this.graph = graph;
graph.setItemIndices();
var reversedEdges = graph.makeAcyclic();
this._initRuntimeProperties();
this._prepare(graph, options);
this._dummify();
this._optimizeCrossings();
this._swapPairs();
this.arrangeNodes();
this._moveThingsAround();
this._dedummify();
Utils.forEach(reversedEdges, function (e) {
if (e.points) {
e.points.reverse();
}
});
},
setMinDist: function (m, n, minDist) {
var l = m.layer;
var i = m.layerIndex;
this.minDistances[l][i] = minDist;
},
getMinDist: function (m, n) {
var dist = 0, i1 = m.layerIndex, i2 = n.layerIndex, l = m.layer, min = Math.min(i1, i2), max = Math.max(i1, i2);
for (var k = min; k < max; ++k) {
dist += this.minDistances[l][k];
}
return dist;
},
placeLeftToRight: function (leftClasses) {
var leftPos = new Dictionary(), n, node;
for (var c = 0; c < this.layers.length; ++c) {
var classNodes = leftClasses[c];
if (!classNodes) {
continue;
}
for (n = 0; n < classNodes.length; n++) {
node = classNodes[n];
if (!leftPos.containsKey(node)) {
this.placeLeft(node, leftPos, c);
}
}
var d = Number.POSITIVE_INFINITY;
for (n = 0; n < classNodes.length; n++) {
node = classNodes[n];
var rightSibling = this.rightSibling(node);
if (rightSibling && this.nodeLeftClass.get(rightSibling) !== c) {
d = Math.min(d, leftPos.get(rightSibling) - leftPos.get(node) - this.getMinDist(node, rightSibling));
}
}
if (d === Number.POSITIVE_INFINITY) {
var D = [];
for (n = 0; n < classNodes.length; n++) {
node = classNodes[n];
var neighbors = [];
Utils.addRange(neighbors, this.upNodes.get(node));
Utils.addRange(neighbors, this.downNodes.get(node));
for (var e = 0; e < neighbors.length; e++) {
var neighbor = neighbors[e];
if (this.nodeLeftClass.get(neighbor) < c) {
D.push(leftPos.get(neighbor) - leftPos.get(node));
}
}
}
D.sort();
if (D.length === 0) {
d = 0;
} else if (D.length % 2 === 1) {
d = D[this.intDiv(D.length, 2)];
} else {
d = (D[this.intDiv(D.length, 2) - 1] + D[this.intDiv(D.length, 2)]) / 2;
}
}
for (n = 0; n < classNodes.length; n++) {
node = classNodes[n];
leftPos.set(node, leftPos.get(node) + d);
}
}
return leftPos;
},
placeRightToLeft: function (rightClasses) {
var rightPos = new Dictionary(), n, node;
for (var c = 0; c < this.layers.length; ++c) {
var classNodes = rightClasses[c];
if (!classNodes) {
continue;
}
for (n = 0; n < classNodes.length; n++) {
node = classNodes[n];
if (!rightPos.containsKey(node)) {
this.placeRight(node, rightPos, c);
}
}
var d = Number.NEGATIVE_INFINITY;
for (n = 0; n < classNodes.length; n++) {
node = classNodes[n];
var leftSibling = this.leftSibling(node);
if (leftSibling && this.nodeRightClass.get(leftSibling) !== c) {
d = Math.max(d, rightPos.get(leftSibling) - rightPos.get(node) + this.getMinDist(leftSibling, node));
}
}
if (d === Number.NEGATIVE_INFINITY) {
var D = [];
for (n = 0; n < classNodes.length; n++) {
node = classNodes[n];
var neighbors = [];
Utils.addRange(neighbors, this.upNodes.get(node));
Utils.addRange(neighbors, this.downNodes.get(node));
for (var e = 0; e < neighbors.length; e++) {
var neighbor = neighbors[e];
if (this.nodeRightClass.get(neighbor) < c) {
D.push(rightPos.get(node) - rightPos.get(neighbor));
}
}
}
D.sort();
if (D.length === 0) {
d = 0;
} else if (D.length % 2 === 1) {
d = D[this.intDiv(D.length, 2)];
} else {
d = (D[this.intDiv(D.length, 2) - 1] + D[this.intDiv(D.length, 2)]) / 2;
}
}
for (n = 0; n < classNodes.length; n++) {
node = classNodes[n];
rightPos.set(node, rightPos.get(node) + d);
}
}
return rightPos;
},
_getLeftWing: function () {
var leftWing = { value: null };
var result = this.computeClasses(leftWing, 1);
this.nodeLeftClass = leftWing.value;
return result;
},
_getRightWing: function () {
var rightWing = { value: null };
var result = this.computeClasses(rightWing, -1);
this.nodeRightClass = rightWing.value;
return result;
},
computeClasses: function (wingPair, d) {
var currentWing = 0, wing = wingPair.value = new Dictionary();
for (var l = 0; l < this.layers.length; ++l) {
currentWing = l;
var layer = this.layers[l];
for (var n = d === 1 ? 0 : layer.length - 1; 0 <= n && n < layer.length; n += d) {
var node = layer[n];
if (!wing.containsKey(node)) {
wing.set(node, currentWing);
if (node.isVirtual) {
var ndsinl = this._nodesInLink(node);
for (var kk = 0; kk < ndsinl.length; kk++) {
var vnode = ndsinl[kk];
wing.set(vnode, currentWing);
}
}
} else {
currentWing = wing.get(node);
}
}
}
var wings = [];
for (var i = 0; i < this.layers.length; i++) {
wings.push(null);
}
wing.forEach(function (node, classIndex) {
if (wings[classIndex] === null) {
wings[classIndex] = [];
}
wings[classIndex].push(node);
});
return wings;
},
_isVerticalLayout: function () {
return this.options.subtype.toLowerCase() === 'up' || this.options.subtype.toLowerCase() === 'down' || this.options.subtype.toLowerCase() === 'vertical';
},
_isHorizontalLayout: function () {
return this.options.subtype.toLowerCase() === 'right' || this.options.subtype.toLowerCase() === 'left' || this.options.subtype.toLowerCase() === 'horizontal';
},
_isIncreasingLayout: function () {
return this.options.subtype.toLowerCase() === 'right' || this.options.subtype.toLowerCase() === 'down';
},
_moveThingsAround: function () {
var i, l, node, layer, n, w;
for (l = 0; l < this.layers.length; ++l) {
layer = this.layers[l];
layer.sort(this._gridPositionComparer);
}
this.minDistances = [];
for (l = 0; l < this.layers.length; ++l) {
layer = this.layers[l];
this.minDistances[l] = [];
for (n = 0; n < layer.length; ++n) {
node = layer[n];
node.layerIndex = n;
this.minDistances[l][n] = this.options.nodeDistance;
if (n < layer.length - 1) {
if (this._isVerticalLayout()) {
this.minDistances[l][n] += (node.width + layer[n + 1].width) / 2;
} else {
this.minDistances[l][n] += (node.height + layer[n + 1].height) / 2;
}
}
}
}
this.downNodes = new Dictionary();
this.upNodes = new Dictionary();
Utils.forEach(this.graph.nodes, function (node) {
this.downNodes.set(node, []);
this.upNodes.set(node, []);
}, this);
Utils.forEach(this.graph.links, function (link) {
var origin = link.source;
var dest = link.target;
var down = null, up = null;
if (origin.layer > dest.layer) {
down = link.source;
up = link.target;
} else {
up = link.source;
down = link.target;
}
this.downNodes.get(up).push(down);
this.upNodes.get(down).push(up);
}, this);
this.downNodes.forEachValue(function (list) {
list.sort(this._gridPositionComparer);
}, this);
this.upNodes.forEachValue(function (list) {
list.sort(this._gridPositionComparer);
}, this);
for (l = 0; l < this.layers.length - 1; ++l) {
layer = this.layers[l];
for (w = 0; w < layer.length - 1; w++) {
var currentNode = layer[w];
if (!currentNode.isVirtual) {
continue;
}
var currDown = this.downNodes.get(currentNode)[0];
if (!currDown.isVirtual) {
continue;
}
for (n = w + 1; n < layer.length; ++n) {
node = layer[n];
if (!node.isVirtual) {
continue;
}
var downNode = this.downNodes.get(node)[0];
if (!downNode.isVirtual) {
continue;
}
if (currDown.gridPosition > downNode.gridPosition) {
var pos = currDown.gridPosition;
currDown.gridPosition = downNode.gridPosition;
downNode.gridPosition = pos;
var i1 = currDown.layerIndex;
var i2 = downNode.layerIndex;
this.layers[l + 1][i1] = downNode;
this.layers[l + 1][i2] = currDown;
currDown.layerIndex = i2;
downNode.layerIndex = i1;
}
}
}
}
var leftClasses = this._getLeftWing();
var rightClasses = this._getRightWing();
var leftPos = this.placeLeftToRight(leftClasses);
var rightPos = this.placeRightToLeft(rightClasses);
var x = new Dictionary();
Utils.forEach(this.graph.nodes, function (node) {
x.set(node, (leftPos.get(node) + rightPos.get(node)) / 2);
});
var order = new Dictionary();
var placed = new Dictionary();
for (l = 0; l < this.layers.length; ++l) {
layer = this.layers[l];
var sequenceStart = -1, sequenceEnd = -1;
for (n = 0; n < layer.length; ++n) {
node = layer[n];
order.set(node, 0);
placed.set(node, false);
if (node.isVirtual) {
if (sequenceStart === -1) {
sequenceStart = n;
} else if (sequenceStart === n - 1) {
sequenceStart = n;
} else {
sequenceEnd = n;
order.set(layer[sequenceStart], 0);
if (x.get(node) - x.get(layer[sequenceStart]) === this.getMinDist(layer[sequenceStart], node)) {
placed.set(layer[sequenceStart], true);
} else {
placed.set(layer[sequenceStart], false);
}
sequenceStart = n;
}
}
}
}
var directions = [
1,
-1
];
Utils.forEach(directions, function (d) {
var start = d === 1 ? 0 : this.layers.length - 1;
for (var l = start; 0 <= l && l < this.layers.length; l += d) {
var layer = this.layers[l];
var virtualStartIndex = this._firstVirtualNode(layer);
var virtualStart = null;
var sequence = null;
if (virtualStartIndex !== -1) {
virtualStart = layer[virtualStartIndex];
sequence = [];
for (i = 0; i < virtualStartIndex; i++) {
sequence.push(layer[i]);
}
} else {
virtualStart = null;
sequence = layer;
}
if (sequence.length > 0) {
this._sequencer(x, null, virtualStart, d, sequence);
for (i = 0; i < sequence.length - 1; ++i) {
this.setMinDist(sequence[i], sequence[i + 1], x.get(sequence[i + 1]) - x.get(sequence[i]));
}
if (virtualStart) {
this.setMinDist(sequence[sequence.length - 1], virtualStart, x.get(virtualStart) - x.get(sequence[sequence.length - 1]));
}
}
while (virtualStart) {
var virtualEnd = this.nextVirtualNode(layer, virtualStart);
if (!virtualEnd) {
virtualStartIndex = virtualStart.layerIndex;
sequence = [];
for (i = virtualStartIndex + 1; i < layer.length; i++) {
sequence.push(layer[i]);
}
if (sequence.length > 0) {
this._sequencer(x, virtualStart, null, d, sequence);
for (i = 0; i < sequence.length - 1; ++i) {
this.setMinDist(sequence[i], sequence[i + 1], x.get(sequence[i + 1]) - x.get(sequence[i]));
}
this.setMinDist(virtualStart, sequence[0], x.get(sequence[0]) - x.get(virtualStart));
}
} else if (order.get(virtualStart) === d) {
virtualStartIndex = virtualStart.layerIndex;
var virtualEndIndex = virtualEnd.layerIndex;
sequence = [];
for (i = virtualStartIndex + 1; i < virtualEndIndex; i++) {
sequence.push(layer[i]);
}
if (sequence.length > 0) {
this._sequencer(x, virtualStart, virtualEnd, d, sequence);
}
placed.set(virtualStart, true);
}
virtualStart = virtualEnd;
}
this.adjustDirections(l, d, order, placed);
}
}, this);
var fromLayerIndex = this._isIncreasingLayout() ? 0 : this.layers.length - 1;
var reachedFinalLayerIndex = function (k, ctx) {
if (ctx._isIncreasingLayout()) {
return k < ctx.layers.length;
} else {
return k >= 0;
}
};
var layerIncrement = this._isIncreasingLayout() ? +1 : -1, offset = 0;
function maximumHeight(layer, ctx) {
var height = Number.MIN_VALUE;
for (var n = 0; n < layer.length; ++n) {
var node = layer[n];
if (ctx._isVerticalLayout()) {
height = Math.max(height, node.height);
} else {
height = Math.max(height, node.width);
}
}
return height;
}
for (i = fromLayerIndex; reachedFinalLayerIndex(i, this); i += layerIncrement) {
layer = this.layers[i];
var height = maximumHeight(layer, this);
for (n = 0; n < layer.length; ++n) {
node = layer[n];
if (this._isVerticalLayout()) {
node.x = x.get(node);
node.y = offset + height / 2;
} else {
node.x = offset + height / 2;
node.y = x.get(node);
}
}
offset += this.options.layerSeparation + height;
}
},
adjustDirections: function (l, d, order, placed) {
if (l + d < 0 || l + d >= this.layers.length) {
return;
}
var prevBridge = null, prevBridgeTarget = null;
var layer = this.layers[l + d];
for (var n = 0; n < layer.length; ++n) {
var nextBridge = layer[n];
if (nextBridge.isVirtual) {
var nextBridgeTarget = this.getNeighborOnLayer(nextBridge, l);
if (nextBridgeTarget.isVirtual) {
if (prevBridge) {
var p = placed.get(prevBridgeTarget);
var clayer = this.layers[l];
var i1 = prevBridgeTarget.layerIndex;
var i2 = nextBridgeTarget.layerIndex;
for (var i = i1 + 1; i < i2; ++i) {
if (clayer[i].isVirtual) {
p = p && placed.get(clayer[i]);
}
}
if (p) {
order.set(prevBridge, d);
var j1 = prevBridge.layerIndex;
var j2 = nextBridge.layerIndex;
for (var j = j1 + 1; j < j2; ++j) {
if (layer[j].isVirtual) {
order.set(layer[j], d);
}
}
}
}
prevBridge = nextBridge;
prevBridgeTarget = nextBridgeTarget;
}
}
}
},
getNeighborOnLayer: function (node, l) {
var neighbor = this.upNodes.get(node)[0];
if (neighbor.layer === l) {
return neighbor;
}
neighbor = this.downNodes.get(node)[0];
if (neighbor.layer === l) {
return neighbor;
}
return null;
},
_sequencer: function (x, virtualStart, virtualEnd, dir, sequence) {
if (sequence.length === 1) {
this._sequenceSingle(x, virtualStart, virtualEnd, dir, sequence[0]);
}
if (sequence.length > 1) {
var r = sequence.length, t = this.intDiv(r, 2);
this._sequencer(x, virtualStart, virtualEnd, dir, sequence.slice(0, t));
this._sequencer(x, virtualStart, virtualEnd, dir, sequence.slice(t));
this.combineSequences(x, virtualStart, virtualEnd, dir, sequence);
}
},
_sequenceSingle: function (x, virtualStart, virtualEnd, dir, node) {
var neighbors = dir === -1 ? this.downNodes.get(node) : this.upNodes.get(node);
var n = neighbors.length;
if (n !== 0) {
if (n % 2 === 1) {
x.set(node, x.get(neighbors[this.intDiv(n, 2)]));
} else {
x.set(node, (x.get(neighbors[this.intDiv(n, 2) - 1]) + x.get(neighbors[this.intDiv(n, 2)])) / 2);
}
if (virtualStart) {
x.set(node, Math.max(x.get(node), x.get(virtualStart) + this.getMinDist(virtualStart, node)));
}
if (virtualEnd) {
x.set(node, Math.min(x.get(node), x.get(virtualEnd) - this.getMinDist(node, virtualEnd)));
}
}
},
combineSequences: function (x, virtualStart, virtualEnd, dir, sequence) {
var r = sequence.length, t = this.intDiv(r, 2);
var leftHeap = [], i, c, n, neighbors, neighbor, pair;
for (i = 0; i < t; ++i) {
c = 0;
neighbors = dir === -1 ? this.downNodes.get(sequence[i]) : this.upNodes.get(sequence[i]);
for (n = 0; n < neighbors.length; ++n) {
neighbor = neighbors[n];
if (x.get(neighbor) >= x.get(sequence[i])) {
c++;
} else {
c--;
leftHeap.push({
k: x.get(neighbor) + this.getMinDist(sequence[i], sequence[t - 1]),
v: 2
});
}
}
leftHeap.push({
k: x.get(sequence[i]) + this.getMinDist(sequence[i], sequence[t - 1]),
v: c
});
}
if (virtualStart) {
leftHeap.push({
k: x.get(virtualStart) + this.getMinDist(virtualStart, sequence[t - 1]),
v: Number.MAX_VALUE
});
}
leftHeap.sort(this._positionDescendingComparer);
var rightHeap = [];
for (i = t; i < r; ++i) {
c = 0;
neighbors = dir === -1 ? this.downNodes.get(sequence[i]) : this.upNodes.get(sequence[i]);
for (n = 0; n < neighbors.length; ++n) {
neighbor = neighbors[n];
if (x.get(neighbor) <= x.get(sequence[i])) {
c++;
} else {
c--;
rightHeap.push({
k: x.get(neighbor) - this.getMinDist(sequence[i], sequence[t]),
v: 2
});
}
}
rightHeap.push({
k: x.get(sequence[i]) - this.getMinDist(sequence[i], sequence[t]),
v: c
});
}
if (virtualEnd) {
rightHeap.push({
k: x.get(virtualEnd) - this.getMinDist(virtualEnd, sequence[t]),
v: Number.MAX_VALUE
});
}
rightHeap.sort(this._positionAscendingComparer);
var leftRes = 0, rightRes = 0;
var m = this.getMinDist(sequence[t - 1], sequence[t]);
while (x.get(sequence[t]) - x.get(sequence[t - 1]) < m) {
if (leftRes < rightRes) {
if (leftHeap.length === 0) {
x.set(sequence[t - 1], x.get(sequence[t]) - m);
break;
} else {
pair = leftHeap.shift();
leftRes = leftRes + pair.v;
x.set(sequence[t - 1], pair.k);
x.set(sequence[t - 1], Math.max(x.get(sequence[t - 1]), x.get(sequence[t]) - m));
}
} else {
if (rightHeap.length === 0) {
x.set(sequence[t], x.get(sequence[t - 1]) + m);
break;
} else {
pair = rightHeap.shift();
rightRes = rightRes + pair.v;
x.set(sequence[t], pair.k);
x.set(sequence[t], Math.min(x.get(sequence[t]), x.get(sequence[t - 1]) + m));
}
}
}
for (i = t - 2; i >= 0; i--) {
x.set(sequence[i], Math.min(x.get(sequence[i]), x.get(sequence[t - 1]) - this.getMinDist(sequence[i], sequence[t - 1])));
}
for (i = t + 1; i < r; i++) {
x.set(sequence[i], Math.max(x.get(sequence[i]), x.get(sequence[t]) + this.getMinDist(sequence[i], sequence[t])));
}
},
placeLeft: function (node, leftPos, leftClass) {
var pos = Number.NEGATIVE_INFINITY;
Utils.forEach(this._getComposite(node), function (v) {
var leftSibling = this.leftSibling(v);
if (leftSibling && this.nodeLeftClass.get(leftSibling) === this.nodeLeftClass.get(v)) {
if (!leftPos.containsKey(leftSibling)) {
this.placeLeft(leftSibling, leftPos, leftClass);
}
pos = Math.max(pos, leftPos.get(leftSibling) + this.getMinDist(leftSibling, v));
}
}, this);
if (pos === Number.NEGATIVE_INFINITY) {
pos = 0;
}
Utils.forEach(this._getComposite(node), function (v) {
leftPos.set(v, pos);
});
},
placeRight: function (node, rightPos, rightClass) {
var pos = Number.POSITIVE_INFINITY;
Utils.forEach(this._getComposite(node), function (v) {
var rightSibling = this.rightSibling(v);
if (rightSibling && this.nodeRightClass.get(rightSibling) === this.nodeRightClass.get(v)) {
if (!rightPos.containsKey(rightSibling)) {
this.placeRight(rightSibling, rightPos, rightClass);
}
pos = Math.min(pos, rightPos.get(rightSibling) - this.getMinDist(v, rightSibling));
}
}, this);
if (pos === Number.POSITIVE_INFINITY) {
pos = 0;
}
Utils.forEach(this._getComposite(node), function (v) {
rightPos.set(v, pos);
});
},
leftSibling: function (node) {
var layer = this.layers[node.layer], layerIndex = node.layerIndex;
return layerIndex === 0 ? null : layer[layerIndex - 1];
},
rightSibling: function (node) {
var layer = this.layers[node.layer];
var layerIndex = node.layerIndex;
return layerIndex === layer.length - 1 ? null : layer[layerIndex + 1];
},
_getComposite: function (node) {
return node.isVirtual ? this._nodesInLink(node) : [node];
},
arrangeNodes: function () {
var i, l, ni, layer, node;
for (l = 0; l < this.layers.length; l++) {
layer = this.layers[l];
for (ni = 0; ni < layer.length; ni++) {
node = layer[ni];
node.upstreamPriority = node.upstreamLinkCount;
node.downstreamPriority = node.downstreamLinkCount;
}
}
var maxLayoutIterations = 2;
for (var it = 0; it < maxLayoutIterations; it++) {
for (i = this.layers.length - 1; i >= 1; i--) {
this.layoutLayer(false, i);
}
for (i = 0; i < this.layers.length - 1; i++) {
this.layoutLayer(true, i);
}
}
var gridPos = Number.MAX_VALUE;
for (l = 0; l < this.layers.length; l++) {
layer = this.layers[l];
for (ni = 0; ni < layer.length; ni++) {
node = layer[ni];
gridPos = Math.min(gridPos, node.gridPosition);
}
}
if (gridPos < 0) {
for (l = 0; l < this.layers.length; l++) {
layer = this.layers[l];
for (ni = 0; ni < layer.length; ni++) {
node = layer[ni];
node.gridPosition = node.gridPosition - gridPos;
}
}
}
},
layoutLayer: function (down, layer) {
var iconsidered;
var considered;
if (down) {
considered = this.layers[iconsidered = layer + 1];
} else {
considered = this.layers[iconsidered = layer - 1];
}
var sorted = [];
for (var n = 0; n < considered.length; n++) {
sorted.push(considered[n]);
}
sorted.sort(function (n1, n2) {
var n1Priority = (n1.upstreamPriority + n1.downstreamPriority) / 2;
var n2Priority = (n2.upstreamPriority + n2.downstreamPriority) / 2;
if (Math.abs(n1Priority - n2Priority) < 0.0001) {
return 0;
}
if (n1Priority < n2Priority) {
return 1;
}
return -1;
});
Utils.forEach(sorted, function (node) {
var nodeGridPos = node.gridPosition;
var nodeBaryCenter = this.calcBaryCenter(node);
var nodePriority = (node.upstreamPriority + node.downstreamPriority) / 2;
if (Math.abs(nodeGridPos - nodeBaryCenter) < 0.0001) {
return;
}
if (Math.abs(nodeGridPos - nodeBaryCenter) < 0.25 + 0.0001) {
return;
}
if (nodeGridPos < nodeBaryCenter) {
while (nodeGridPos < nodeBaryCenter) {
if (!this.moveRight(node, considered, nodePriority)) {
break;
}
nodeGridPos = node.gridPosition;
}
} else {
while (nodeGridPos > nodeBaryCenter) {
if (!this.moveLeft(node, considered, nodePriority)) {
break;
}
nodeGridPos = node.gridPosition;
}
}
}, this);
if (iconsidered > 0) {
this.calcDownData(iconsidered - 1);
}
if (iconsidered < this.layers.length - 1) {
this.calcUpData(iconsidered + 1);
}
},
moveRight: function (node, layer, priority) {
var index = Utils.indexOf(layer, node);
if (index === layer.length - 1) {
node.gridPosition = node.gridPosition + 0.5;
return true;
}
var rightNode = layer[index + 1];
var rightNodePriority = (rightNode.upstreamPriority + rightNode.downstreamPriority) / 2;
if (rightNode.gridPosition > node.gridPosition + 1) {
node.gridPosition = node.gridPosition + 0.5;
return true;
}
if (rightNodePriority > priority || Math.abs(rightNodePriority - priority) < 0.0001) {
return false;
}
if (this.moveRight(rightNode, layer, priority)) {
node.gridPosition = node.gridPosition + 0.5;
return true;
}
return false;
},
moveLeft: function (node, layer, priority) {
var index = Utils.indexOf(layer, node);
if (index === 0) {
node.gridPosition = node.gridPosition - 0.5;
return true;
}
var leftNode = layer[index - 1];
var leftNodePriority = (leftNode.upstreamPriority + leftNode.downstreamPriority) / 2;
if (leftNode.gridPosition < node.gridPosition - 1) {
node.gridPosition = node.gridPosition - 0.5;
return true;
}
if (leftNodePriority > priority || Math.abs(leftNodePriority - priority) < 0.0001) {
return false;
}
if (this.moveLeft(leftNode, layer, priority)) {
node.gridPosition = node.gridPosition - 0.5;
return true;
}
return false;
},
mapVirtualNode: function (node, link) {
this.nodeToLinkMap.set(node, link);
if (!this.linkToNodeMap.containsKey(link)) {
this.linkToNodeMap.set(link, []);
}
this.linkToNodeMap.get(link).push(node);
},
_nodesInLink: function (node) {
return this.linkToNodeMap.get(this.nodeToLinkMap.get(node));
},
_dummify: function () {
this.linkToNodeMap = new Dictionary();
this.nodeToLinkMap = new Dictionary();
var layer, pos, newNode, node, r, newLink, i, l, links = this.graph.links.slice(0);
var layers = this.layers;
var addLinkBetweenLayers = function (upLayer, downLayer, link) {
layers[upLayer].linksTo[downLayer] = layers[upLayer].linksTo[downLayer] || [];
layers[upLayer].linksTo[downLayer].push(link);
};
for (l = 0; l < links.length; l++) {
var link = links[l];
var o = link.source;
var d = link.target;
var oLayer = o.layer;
var dLayer = d.layer;
var oPos = o.gridPosition;
var dPos = d.gridPosition;
var step = (dPos - oPos) / Math.abs(dLayer - oLayer);
var p = o;
if (oLayer - dLayer > 1) {
for (i = oLayer - 1; i > dLayer; i--) {
newNode = new Node();
newNode.x = o.x;
newNode.y = o.y;
newNode.width = o.width / 100;
newNode.height = o.height / 100;
layer = layers[i];
pos = (i - dLayer) * step + oPos;
if (pos > layer.length) {
pos = layer.length;
}
if (oPos >= layers[oLayer].length - 1 && dPos >= layers[dLayer].length - 1) {
pos = layer.length;
} else if (oPos === 0 && dPos === 0) {
pos = 0;
}
newNode.layer = i;
newNode.uBaryCenter = 0;
newNode.dBaryCenter = 0;
newNode.upstreamLinkCount = 0;
newNode.downstreamLinkCount = 0;
newNode.gridPosition = pos;
newNode.isVirtual = true;
Utils.insert(layer, newNode, pos);
for (r = pos + 1; r < layer.length; r++) {
node = layer[r];
node.gridPosition = node.gridPosition + 1;
}
newLink = new Link(p, newNode);
newLink.depthOfDumminess = 0;
addLinkBetweenLayers(i - 1, i, newLink);
p = newNode;
this.graph._addNode(newNode);
this.graph.addLink(newLink);
newNode.index = this.graph.nodes.length - 1;
this.mapVirtualNode(newNode, link);
}
addLinkBetweenLayers(dLayer - 1, dLayer, newLink);
link.changeSource(p);
link.depthOfDumminess = oLayer - dLayer - 1;
} else if (oLayer - dLayer < -1) {
for (i = oLayer + 1; i < dLayer; i++) {
newNode = new Node();
newNode.x = o.x;
newNode.y = o.y;
newNode.width = o.width / 100;
newNode.height = o.height / 100;
layer = layers[i];
pos = (i - oLayer) * step + oPos;
if (pos > layer.length) {
pos = layer.length;
}
if (oPos >= layers[oLayer].length - 1 && dPos >= layers[dLayer].length - 1) {
pos = layer.length;
} else if (oPos === 0 && dPos === 0) {
pos = 0;
}
newNode.layer = i;
newNode.uBaryCenter = 0;
newNode.dBaryCenter = 0;
newNode.upstreamLinkCount = 0;
newNode.downstreamLinkCount = 0;
newNode.gridPosition = pos;
newNode.isVirtual = true;
pos &= pos;
Utils.insert(layer, newNode, pos);
for (r = pos + 1; r < layer.length; r++) {
node = layer[r];
node.gridPosition = node.gridPosition + 1;
}
newLink = new Link(p, newNode);
newLink.depthOfDumminess = 0;
addLinkBetweenLayers(i - 1, i, newLink);
p = newNode;
this.graph._addNode(newNode);
this.graph.addLink(newLink);
newNode.index = this.graph.nodes.length - 1;
this.mapVirtualNode(newNode, link);
}
addLinkBetweenLayers(dLayer - 1, dLayer, link);
link.changeSource(p);
link.depthOfDumminess = dLayer - oLayer - 1;
} else {
addLinkBetweenLayers(oLayer, dLayer, link);
}
}
},
_dedummify: function () {
var dedum = true;
while (dedum) {
dedum = false;
for (var l = 0; l < this.graph.links.length; l++) {
var link = this.graph.links[l];
if (!link.depthOfDumminess) {
continue;
}
var points = [];
points.unshift({
x: link.target.x,
y: link.target.y
});
points.unshift({
x: link.source.x,
y: link.source.y
});
var temp = link;
var depthOfDumminess = link.depthOfDumminess;
for (var d = 0; d < depthOfDumminess; d++) {
var node = temp.source;
var prevLink = node.incoming[0];
points.unshift({
x: prevLink.source.x,
y: prevLink.source.y
});
temp = prevLink;
}
link.changeSource(temp.source);
link.depthOfDumminess = 0;
if (points.length > 2) {
points.splice(0, 1);
points.splice(points.length - 1);
link.points = points;
} else {
link.points = [];
}
dedum = true;
break;
}
}
},
_optimizeCrossings: function () {
var moves = -1, i;
var maxIterations = 3;
var iter = 0;
while (moves !== 0) {
if (iter++ > maxIterations) {
break;
}
moves = 0;
for (i = this.layers.length - 1; i >= 1; i--) {
moves += this.optimizeLayerCrossings(false, i);
}
for (i = 0; i < this.layers.length - 1; i++) {
moves += this.optimizeLayerCrossings(true, i);
}
}
},
calcUpData: function (layer) {
if (layer === 0) {
return;
}
var considered = this.layers[layer], i, l, link;
var upLayer = new Set();
var temp = this.layers[layer - 1];
for (i = 0; i < temp.length; i++) {
upLayer.add(temp[i]);
}
for (i = 0; i < considered.length; i++) {
var node = considered[i];
var sum = 0;
var total = 0;
for (l = 0; l < node.incoming.length; l++) {
link = node.incoming[l];
if (upLayer.contains(link.source)) {
total++;
sum += link.source.gridPosition;
}
}
for (l = 0; l < node.outgoing.length; l++) {
link = node.outgoing[l];
if (upLayer.contains(link.target)) {
total++;
sum += link.target.gridPosition;
}
}
if (total > 0) {
node.uBaryCenter = sum / total;
node.upstreamLinkCount = total;
} else {
node.uBaryCenter = i;
node.upstreamLinkCount = 0;
}
}
},
calcDownData: function (layer) {
if (layer === this.layers.length - 1) {
return;
}
var considered = this.layers[layer], i, l, link;
var downLayer = new Set();
var temp = this.layers[layer + 1];
for (i = 0; i < temp.length; i++) {
downLayer.add(temp[i]);
}
for (i = 0; i < considered.length; i++) {
var node = considered[i];
var sum = 0;
var total = 0;
for (l = 0; l < node.incoming.length; l++) {
link = node.incoming[l];
if (downLayer.contains(link.source)) {
total++;
sum += link.source.gridPosition;
}
}
for (l = 0; l < node.outgoing.length; l++) {
link = node.outgoing[l];
if (downLayer.contains(link.target)) {
total++;
sum += link.target.gridPosition;
}
}
if (total > 0) {
node.dBaryCenter = sum / total;
node.downstreamLinkCount = total;
} else {
node.dBaryCenter = i;
node.downstreamLinkCount = 0;
}
}
},
optimizeLayerCrossings: function (down, layer) {
var iconsidered;
var considered;
if (down) {
considered = this.layers[iconsidered = layer + 1];
} else {
considered = this.layers[iconsidered = layer - 1];
}
var presorted = considered.slice(0);
if (down) {
this.calcUpData(iconsidered);
} else {
this.calcDownData(iconsidered);
}
var that = this;
considered.sort(function (n1, n2) {
var n1BaryCenter = that.calcBaryCenter(n1), n2BaryCenter = that.calcBaryCenter(n2);
if (Math.abs(n1BaryCenter - n2BaryCenter) < 0.0001) {
if (n1.degree() === n2.degree()) {
return that.compareByIndex(n1, n2);
} else if (n1.degree() < n2.degree()) {
return 1;
}
return -1;
}
var compareValue = (n2BaryCenter - n1BaryCenter) * 1000;
if (compareValue > 0) {
return -1;
} else if (compareValue < 0) {
return 1;
}
return that.compareByIndex(n1, n2);
});
var i, moves = 0;
for (i = 0; i < considered.length; i++) {
if (considered[i] !== presorted[i]) {
moves++;
}
}
if (moves > 0) {
var inode = 0;
for (i = 0; i < considered.length; i++) {
var node = considered[i];
node.gridPosition = inode++;
}
}
return moves;
},
_swapPairs: function () {
var maxIterations = this.options.layeredIterations;
var iter = 0;
while (true) {
if (iter++ > maxIterations) {
break;
}
var downwards = iter % 4 <= 1;
var secondPass = iter % 4 === 1;
for (var l = downwards ? 0 : this.layers.length - 1; downwards ? l <= this.layers.length - 1 : l >= 0; l += downwards ? 1 : -1) {
var layer = this.layers[l];
var hasSwapped = false;
var calcCrossings = true;
var memCrossings = 0;
for (var n = 0; n < layer.length - 1; n++) {
var up = 0;
var down = 0;
var crossBefore = 0;
if (calcCrossings) {
if (l !== 0) {
up = this.countLinksCrossingBetweenTwoLayers(l - 1, l);
}
if (l !== this.layers.length - 1) {
down = this.countLinksCrossingBetweenTwoLayers(l, l + 1);
}
if (downwards) {
up *= 2;
} else {
down *= 2;
}
crossBefore = up + down;
} else {
crossBefore = memCrossings;
}
if (crossBefore === 0) {
continue;
}
var node1 = layer[n];
var node2 = layer[n + 1];
var node1GridPos = node1.gridPosition;
var node2GridPos = node2.gridPosition;
layer[n] = node2;
layer[n + 1] = node1;
node1.gridPosition = node2GridPos;
node2.gridPosition = node1GridPos;
up = 0;
if (l !== 0) {
up = this.countLinksCrossingBetweenTwoLayers(l - 1, l);
}
down = 0;
if (l !== this.layers.length - 1) {
down = this.countLinksCrossingBetweenTwoLayers(l, l + 1);
}
if (downwards) {
up *= 2;
} else {
down *= 2;
}
var crossAfter = up + down;
var revert = false;
if (secondPass) {
revert = crossAfter >= crossBefore;
} else {
revert = crossAfter > crossBefore;
}
if (revert) {
node1 = layer[n];
node2 = layer[n + 1];
node1GridPos = node1.gridPosition;
node2GridPos = node2.gridPosition;
layer[n] = node2;
layer[n + 1] = node1;
node1.gridPosition = node2GridPos;
node2.gridPosition = node1GridPos;
memCrossings = crossBefore;
calcCrossings = false;
} else {
hasSwapped = true;
calcCrossings = true;
}
}
if (hasSwapped) {
if (l !== this.layers.length - 1) {
this.calcUpData(l + 1);
}
if (l !== 0) {
this.calcDownData(l - 1);
}
}
}
}
},
countLinksCrossingBetweenTwoLayers: function (ulayer, dlayer) {
var links = this.layers[ulayer].linksTo[dlayer];
var link1, link2, n11, n12, n21, n22, l1, l2;
var crossings = 0;
var length = links.length;
for (l1 = 0; l1 < length; l1++) {
link1 = links[l1];
for (l2 = l1 + 1; l2 < length; l2++) {
link2 = links[l2];
if (link1.target.layer === dlayer) {
n11 = link1.source;
n12 = link1.target;
} else {
n11 = link1.target;
n12 = link1.source;
}
if (link2.target.layer === dlayer) {
n21 = link2.source;
n22 = link2.target;
} else {
n21 = link2.target;
n22 = link2.source;
}
var n11gp = n11.gridPosition;
var n12gp = n12.gridPosition;
var n21gp = n21.gridPosition;
var n22gp = n22.gridPosition;
if ((n11gp - n21gp) * (n12gp - n22gp) < 0) {
crossings++;
}
}
}
return crossings;
},
calcBaryCenter: function (node) {
var upstreamLinkCount = node.upstreamLinkCount;
var downstreamLinkCount = node.downstreamLinkCount;
var uBaryCenter = node.uBaryCenter;
var dBaryCenter = node.dBaryCenter;
if (upstreamLinkCount > 0 && downstreamLinkCount > 0) {
return (uBaryCenter + dBaryCenter) / 2;
}
if (upstreamLinkCount > 0) {
return uBaryCenter;
}
if (downstreamLinkCount > 0) {
return dBaryCenter;
}
return 0;
},
_gridPositionComparer: function (x, y) {
if (x.gridPosition < y.gridPosition) {
return -1;
}
if (x.gridPosition > y.gridPosition) {
return 1;
}
return 0;
},
_positionAscendingComparer: function (x, y) {
return x.k < y.k ? -1 : x.k > y.k ? 1 : 0;
},
_positionDescendingComparer: function (x, y) {
return x.k < y.k ? 1 : x.k > y.k ? -1 : 0;
},
_firstVirtualNode: function (layer) {
for (var c = 0; c < layer.length; c++) {
if (layer[c].isVirtual) {
return c;
}
}
return -1;
},
compareByIndex: function (o1, o2) {
var i1 = o1.index;
var i2 = o2.index;
if (i1 < i2) {
return 1;
}
if (i1 > i2) {
return -1;
}
return 0;
},
intDiv: function (numerator, denominator) {
return (numerator - numerator % denominator) / denominator;
},
nextVirtualNode: function (layer, node) {
var nodeIndex = node.layerIndex;
for (var i = nodeIndex + 1; i < layer.length; ++i) {
if (layer[i].isVirtual) {
return layer[i];
}
}
return null;
}
});
var LayoutState = kendo.Class.extend({
init: function (diagram, graphOrNodes) {
if (Utils.isUndefined(diagram)) {
throw 'No diagram given';
}
this.diagram = diagram;
this.nodeMap = new Dictionary();
this.linkMap = new Dictionary();
this.capture(graphOrNodes ? graphOrNodes : diagram);
},
capture: function (diagramOrGraphOrNodes) {
var node, nodes, shape, i, conn, link, links;
if (diagramOrGraphOrNodes instanceof diagram.Graph) {
for (i = 0; i < diagramOrGraphOrNodes.nodes.length; i++) {
node = diagramOrGraphOrNodes.nodes[i];
shape = node.associatedShape;
this.nodeMap.set(shape.visual.id, new Rect(node.x, node.y, node.width, node.height));
}
for (i = 0; i < diagramOrGraphOrNodes.links.length; i++) {
link = diagramOrGraphOrNodes.links[i];
conn = link.associatedConnection;
this.linkMap.set(conn.visual.id, link.points());
}
} else if (diagramOrGraphOrNodes instanceof Array) {
nodes = diagramOrGraphOrNodes;
for (i = 0; i < nodes.length; i++) {
node = nodes[i];
shape = node.associatedShape;
if (shape) {
this.nodeMap.set(shape.visual.id, new Rect(node.x, node.y, node.width, node.height));
}
}
} else if (diagramOrGraphOrNodes.hasOwnProperty('links') && diagramOrGraphOrNodes.hasOwnProperty('nodes')) {
nodes = diagramOrGraphOrNodes.nodes;
links = diagramOrGraphOrNodes.links;
for (i = 0; i < nodes.length; i++) {
node = nodes[i];
shape = node.associatedShape;
if (shape) {
this.nodeMap.set(shape.visual.id, new Rect(node.x, node.y, node.width, node.height));
}
}
for (i = 0; i < links.length; i++) {
link = links[i];
conn = link.associatedConnection;
if (conn) {
this.linkMap.set(conn.visual.id, link.points);
}
}
} else {
var shapes = this.diagram.shapes;
var connections = this.diagram.connections;
for (i = 0; i < shapes.length; i++) {
shape = shapes[i];
this.nodeMap.set(shape.visual.id, shape.bounds());
}
for (i = 0; i < connections.length; i++) {
conn = connections[i];
this.linkMap.set(conn.visual.id, conn.points());
}
}
}
});
deepExtend(diagram, {
init: function (element) {
kendo.init(element, diagram.ui);
},
SpringLayout: SpringLayout,
TreeLayout: TreeLayout,
GraphAdapter: DiagramToHyperTreeAdapter,
LayeredLayout: LayeredLayout,
LayoutBase: LayoutBase,
LayoutState: LayoutState
});
}(window.kendo.jQuery));
}, typeof define == 'function' && define.amd ? define : function (a1, a2, a3) {
(a3 || a2)();
}));
(function (f, define) {
define('dataviz/diagram/dom', [
'kendo.data',
'kendo.draganddrop',
'kendo.toolbar',
'kendo.editable',
'kendo.window',
'kendo.dropdownlist',
'kendo.dataviz.core',
'kendo.dataviz.themes',
'dataviz/diagram/svg',
'dataviz/diagram/services',
'dataviz/diagram/layout'
], f);
}(function () {
(function ($, undefined) {
var dataviz = kendo.dataviz, draw = kendo.drawing, geom = kendo.geometry, diagram = dataviz.diagram, Widget = kendo.ui.Widget, Class = kendo.Class, proxy = $.proxy, deepExtend = kendo.deepExtend, extend = $.extend, HierarchicalDataSource = kendo.data.HierarchicalDataSource, Canvas = diagram.Canvas, Group = diagram.Group, Rectangle = diagram.Rectangle, Circle = diagram.Circle, CompositeTransform = diagram.CompositeTransform, Rect = diagram.Rect, Path = diagram.Path, DeleteShapeUnit = diagram.DeleteShapeUnit, DeleteConnectionUnit = diagram.DeleteConnectionUnit, TextBlock = diagram.TextBlock, Image = diagram.Image, Point = diagram.Point, Intersect = diagram.Intersect, ConnectionEditAdorner = diagram.ConnectionEditAdorner, UndoRedoService = diagram.UndoRedoService, ToolService = diagram.ToolService, Selector = diagram.Selector, ResizingAdorner = diagram.ResizingAdorner, ConnectorsAdorner = diagram.ConnectorsAdorner, Cursors = diagram.Cursors, Utils = diagram.Utils, Observable = kendo.Observable, ToBackUnit = diagram.ToBackUnit, ToFrontUnit = diagram.ToFrontUnit, PolylineRouter = diagram.PolylineRouter, CascadingRouter = diagram.CascadingRouter, isUndefined = Utils.isUndefined, isDefined = Utils.isDefined, defined = kendo.util.defined, isArray = $.isArray, isFunction = kendo.isFunction, isString = Utils.isString, isPlainObject = $.isPlainObject, math = Math;
var NS = '.kendoDiagram', CASCADING = 'cascading', ITEMBOUNDSCHANGE = 'itemBoundsChange', CHANGE = 'change', CLICK = 'click', DRAG = 'drag', DRAG_END = 'dragEnd', DRAG_START = 'dragStart', MOUSE_ENTER = 'mouseEnter', MOUSE_LEAVE = 'mouseLeave', ERROR = 'error', AUTO = 'Auto', TOP = 'Top', RIGHT = 'Right', LEFT = 'Left', BOTTOM = 'Bottom', MAXINT = 9007199254740992, SELECT = 'select', ITEMROTATE = 'itemRotate', PAN = 'pan', ZOOM_START = 'zoomStart', ZOOM_END = 'zoomEnd', NONE = 'none', DEFAULT_CANVAS_WIDTH = 600, DEFAULT_CANVAS_HEIGHT = 600, DEFAULT_SHAPE_TYPE = 'rectangle', DEFAULT_SHAPE_WIDTH = 100, DEFAULT_SHAPE_HEIGHT = 100, DEFAULT_SHAPE_MINWIDTH = 20, DEFAULT_SHAPE_MINHEIGHT = 20, DEFAULT_SHAPE_POSITION = 0, DEFAULT_CONNECTION_BACKGROUND = 'Yellow', MAX_VALUE = Number.MAX_VALUE, MIN_VALUE = -Number.MAX_VALUE, ABSOLUTE = 'absolute', TRANSFORMED = 'transformed', ROTATED = 'rotated', TRANSPARENT = 'transparent', WIDTH = 'width', HEIGHT = 'height', X = 'x', Y = 'y', MOUSEWHEEL_NS = 'DOMMouseScroll' + NS + ' mousewheel' + NS, MOBILE_ZOOM_RATE = 0.05, MOBILE_PAN_DISTANCE = 5, BUTTON_TEMPLATE = '
#=text#', CONNECTION_CONTENT_OFFSET = 5;
diagram.DefaultConnectors = [
{ name: TOP },
{ name: BOTTOM },
{ name: LEFT },
{ name: RIGHT },
{
name: AUTO,
position: function (shape) {
return shape.getPosition('center');
}
}
];
var defaultButtons = {
cancel: {
text: 'Cancel',
imageClass: 'k-cancel',
className: 'k-diagram-cancel',
iconClass: 'k-icon'
},
update: {
text: 'Update',
imageClass: 'k-update',
className: 'k-diagram-update',
iconClass: 'k-icon'
}
};
diagram.shapeDefaults = function (extra) {
var defaults = {
type: DEFAULT_SHAPE_TYPE,
path: '',
autoSize: true,
visual: null,
x: DEFAULT_SHAPE_POSITION,
y: DEFAULT_SHAPE_POSITION,
minWidth: DEFAULT_SHAPE_MINWIDTH,
minHeight: DEFAULT_SHAPE_MINHEIGHT,
width: DEFAULT_SHAPE_WIDTH,
height: DEFAULT_SHAPE_HEIGHT,
hover: {},
editable: {
connect: true,
tools: []
},
connectors: diagram.DefaultConnectors,
rotation: { angle: 0 }
};
Utils.simpleExtend(defaults, extra);
return defaults;
};
function mwDelta(e) {
var origEvent = e.originalEvent, delta = 0;
if (origEvent.wheelDelta) {
delta = -origEvent.wheelDelta / 40;
delta = delta > 0 ? math.ceil(delta) : math.floor(delta);
} else if (origEvent.detail) {
delta = origEvent.detail;
}
return delta;
}
function isAutoConnector(connector) {
return connector.options.name.toLowerCase() === AUTO.toLowerCase();
}
function closestConnector(point, shape) {
var minimumDistance = MAXINT, resCtr, ctrs = shape.connectors;
for (var i = 0; i < ctrs.length; i++) {
var ctr = ctrs[i];
if (!isAutoConnector(ctr)) {
var dist = point.distanceTo(ctr.position());
if (dist < minimumDistance) {
minimumDistance = dist;
resCtr = ctr;
}
}
}
return resCtr;
}
function indicesOfItems(group, visuals) {
var i, indices = [], visual;
var children = group.drawingContainer().children;
var length = children.length;
for (i = 0; i < visuals.length; i++) {
visual = visuals[i];
for (var j = 0; j < length; j++) {
if (children[j] == visual.drawingContainer()) {
indices.push(j);
break;
}
}
}
return indices;
}
var DiagramElement = Observable.extend({
init: function (options) {
var that = this;
that.dataItem = (options || {}).dataItem;
Observable.fn.init.call(that);
that.options = deepExtend({ id: diagram.randomId() }, that.options, options);
that.isSelected = false;
that.visual = new Group({
id: that.options.id,
autoSize: that.options.autoSize
});
that.id = that.options.id;
that._template();
},
options: {
hover: {},
cursor: Cursors.grip,
content: { align: 'center middle' },
selectable: true,
serializable: true,
enable: true
},
_getCursor: function (point) {
if (this.adorner) {
return this.adorner._getCursor(point);
}
return this.options.cursor;
},
visible: function (value) {
if (isUndefined(value)) {
return this.visual.visible();
} else {
this.visual.visible(value);
}
},
bounds: function () {
},
refresh: function () {
this.visual.redraw();
},
position: function (point) {
this.options.x = point.x;
this.options.y = point.y;
this.visual.position(point);
},
toString: function () {
return this.options.id;
},
serialize: function () {
var json = deepExtend({}, { options: this.options });
if (this.dataItem) {
json.dataItem = this.dataItem.toString();
}
return json;
},
_content: function (content) {
if (content !== undefined) {
var options = this.options;
if (diagram.Utils.isString(content)) {
options.content.text = content;
} else {
deepExtend(options.content, content);
}
var contentOptions = options.content;
var contentVisual = this._contentVisual;
if (!contentVisual) {
this._createContentVisual(contentOptions);
} else {
this._updateContentVisual(contentOptions);
}
}
return this.options.content.text;
},
_createContentVisual: function (options) {
if (options.text) {
this._contentVisual = new TextBlock(options);
this._contentVisual._includeInBBox = false;
this.visual.append(this._contentVisual);
}
},
_updateContentVisual: function (options) {
this._contentVisual.redraw(options);
},
_hitTest: function (point) {
var bounds = this.bounds();
return this.visible() && bounds.contains(point) && this.options.enable;
},
_template: function () {
var that = this;
if (that.options.content.template) {
var data = that.dataItem || {}, elementTemplate = kendo.template(that.options.content.template, { paramName: 'dataItem' });
that.options.content.text = elementTemplate(data);
}
},
_canSelect: function () {
return this.options.selectable !== false;
},
toJSON: function () {
return { id: this.options.id };
}
});
var Connector = Class.extend({
init: function (shape, options) {
this.options = deepExtend({}, this.options, options);
this.connections = [];
this.shape = shape;
},
options: {
width: 7,
height: 7,
fill: { color: DEFAULT_CONNECTION_BACKGROUND },
hover: {}
},
position: function () {
if (this.options.position) {
return this.options.position(this.shape);
} else {
return this.shape.getPosition(this.options.name);
}
},
toJSON: function () {
return {
shapeId: this.shape.toString(),
connector: this.options.name
};
}
});
Connector.parse = function (diagram, str) {
var tempStr = str.split(':'), id = tempStr[0], name = tempStr[1] || AUTO;
for (var i = 0; i < diagram.shapes.length; i++) {
var shape = diagram.shapes[i];
if (shape.options.id == id) {
return shape.getConnector(name.trim());
}
}
};
var Shape = DiagramElement.extend({
init: function (options, diagram) {
var that = this;
DiagramElement.fn.init.call(that, options);
this.diagram = diagram;
this.updateOptionsFromModel();
options = that.options;
that.connectors = [];
that.type = options.type;
that.createShapeVisual();
that.updateBounds();
that.content(that.content());
that._createConnectors();
},
options: diagram.shapeDefaults(),
_setOptionsFromModel: function (model) {
var modelOptions = filterShapeDataItem(model || this.dataItem);
this.options = deepExtend({}, this.options, modelOptions);
this.redrawVisual();
if (this.options.content) {
this._template();
this.content(this.options.content);
}
},
updateOptionsFromModel: function (model, field) {
if (this.diagram && this.diagram._isEditable) {
var modelOptions = filterShapeDataItem(model || this.dataItem);
if (model && field) {
if (!dataviz.inArray(field, [
'x',
'y',
'width',
'height'
])) {
if (this.options.visual) {
this.redrawVisual();
} else if (modelOptions.type) {
this.options = deepExtend({}, this.options, modelOptions);
this.redrawVisual();
}
if (this.options.content) {
this._template();
this.content(this.options.content);
}
} else {
var bounds = this.bounds();
bounds[field] = model[field];
this.bounds(bounds);
}
} else {
this.options = deepExtend({}, this.options, modelOptions);
}
}
},
redrawVisual: function () {
this.visual.clear();
this._contentVisual = null;
this.options.dataItem = this.dataItem;
this.createShapeVisual();
this.updateBounds();
},
updateModel: function (syncChanges) {
var diagram = this.diagram;
if (diagram && diagram._isEditable) {
var bounds = this._bounds;
var model = this.dataItem;
if (model) {
diagram._suspendModelRefresh();
if (defined(model.x) && bounds.x !== model.x) {
model.set('x', bounds.x);
}
if (defined(model.y) && bounds.y !== model.y) {
model.set('y', bounds.y);
}
if (defined(model.width) && bounds.width !== model.width) {
model.set('width', bounds.width);
}
if (defined(model.height) && bounds.height !== model.height) {
model.set('height', bounds.height);
}
this.dataItem = model;
diagram._resumeModelRefresh();
if (syncChanges) {
diagram._syncShapeChanges();
}
}
}
},
updateBounds: function () {
var bounds = this.visual._measure(true);
var options = this.options;
this.bounds(new Rect(options.x, options.y, bounds.width, bounds.height));
this._rotate();
this._alignContent();
},
content: function (content) {
var result = this._content(content);
this._alignContent();
return result;
},
_alignContent: function () {
var contentOptions = this.options.content || {};
var contentVisual = this._contentVisual;
if (contentVisual && contentOptions.align) {
var containerRect = this.visual._measure();
var aligner = new diagram.RectAlign(containerRect);
var contentBounds = contentVisual.drawingElement.bbox(null);
var contentRect = new Rect(0, 0, contentBounds.width(), contentBounds.height());
var alignedBounds = aligner.align(contentRect, contentOptions.align);
contentVisual.position(alignedBounds.topLeft());
}
},
_createConnectors: function () {
var options = this.options, length = options.connectors.length, connectorDefaults = options.connectorDefaults, connector, i;
for (i = 0; i < length; i++) {
connector = new Connector(this, deepExtend({}, connectorDefaults, options.connectors[i]));
this.connectors.push(connector);
}
},
bounds: function (value) {
var bounds;
if (value) {
if (isString(value)) {
switch (value) {
case TRANSFORMED:
bounds = this._transformedBounds();
break;
case ABSOLUTE:
bounds = this._transformedBounds();
var pan = this.diagram._pan;
bounds.x += pan.x;
bounds.y += pan.y;
break;
case ROTATED:
bounds = this._rotatedBounds();
break;
default:
bounds = this._bounds;
}
} else {
this._setBounds(value);
this._triggerBoundsChange();
if (!(this.diagram && this.diagram._layouting)) {
this.refreshConnections();
}
}
} else {
bounds = this._bounds;
}
return bounds;
},
_setBounds: function (rect) {
var options = this.options;
var topLeft = rect.topLeft();
var x = options.x = topLeft.x;
var y = options.y = topLeft.y;
var width = options.width = math.max(rect.width, options.minWidth);
var height = options.height = math.max(rect.height, options.minHeight);
this._bounds = new Rect(x, y, width, height);
this.visual.redraw({
x: x,
y: y,
width: width,
height: height
});
},
position: function (point) {
if (point) {
this.bounds(new Rect(point.x, point.y, this._bounds.width, this._bounds.height));
} else {
return this._bounds.topLeft();
}
},
clone: function () {
var json = this.serialize();
json.options.id = diagram.randomId();
if (this.diagram && this.diagram._isEditable && defined(this.dataItem)) {
json.options.dataItem = cloneDataItem(this.dataItem);
}
return new Shape(json.options);
},
select: function (value) {
var diagram = this.diagram, selected, deselected;
if (isUndefined(value)) {
value = true;
}
if (this._canSelect()) {
if (this.isSelected != value) {
selected = [];
deselected = [];
this.isSelected = value;
if (this.isSelected) {
diagram._selectedItems.push(this);
selected.push(this);
} else {
Utils.remove(diagram._selectedItems, this);
deselected.push(this);
}
if (!diagram._internalSelection) {
diagram._selectionChanged(selected, deselected);
}
return true;
}
}
},
rotate: function (angle, center, undoable) {
var rotate = this.visual.rotate();
if (angle !== undefined) {
if (undoable !== false && this.diagram && this.diagram.undoRedoService && angle !== rotate.angle) {
this.diagram.undoRedoService.add(new diagram.RotateUnit(this.diagram._resizingAdorner, [this], [rotate.angle]), false);
}
var b = this.bounds(), sc = new Point(b.width / 2, b.height / 2), deltaAngle, newPosition;
if (center) {
deltaAngle = angle - rotate.angle;
newPosition = b.center().rotate(center, 360 - deltaAngle).minus(sc);
this._rotationOffset = this._rotationOffset.plus(newPosition.minus(b.topLeft()));
this.position(newPosition);
}
this.visual.rotate(angle, sc);
this.options.rotation.angle = angle;
if (this.diagram && this.diagram._connectorsAdorner) {
this.diagram._connectorsAdorner.refresh();
}
this.refreshConnections();
if (this.diagram) {
this.diagram.trigger(ITEMROTATE, { item: this });
}
}
return rotate;
},
connections: function (type) {
var result = [], i, j, con, cons, ctr;
for (i = 0; i < this.connectors.length; i++) {
ctr = this.connectors[i];
cons = ctr.connections;
for (j = 0, cons; j < cons.length; j++) {
con = cons[j];
if (type == 'out') {
var source = con.source();
if (source.shape && source.shape == this) {
result.push(con);
}
} else if (type == 'in') {
var target = con.target();
if (target.shape && target.shape == this) {
result.push(con);
}
} else {
result.push(con);
}
}
}
return result;
},
refreshConnections: function () {
$.each(this.connections(), function () {
this.refresh();
});
},
getConnector: function (nameOrPoint) {
var i, ctr;
if (isString(nameOrPoint)) {
nameOrPoint = nameOrPoint.toLocaleLowerCase();
for (i = 0; i < this.connectors.length; i++) {
ctr = this.connectors[i];
if (ctr.options.name.toLocaleLowerCase() == nameOrPoint) {
return ctr;
}
}
} else if (nameOrPoint instanceof Point) {
return closestConnector(nameOrPoint, this);
} else {
return this.connectors.length ? this.connectors[0] : null;
}
},
getPosition: function (side) {
var b = this.bounds(), fnName = side.charAt(0).toLowerCase() + side.slice(1);
if (isFunction(b[fnName])) {
return this._transformPoint(b[fnName]());
}
return b.center();
},
redraw: function (options) {
if (options) {
var shapeOptions = this.options;
var boundsChange;
this.shapeVisual.redraw(this._visualOptions(options));
if (this._diffNumericOptions(options, [
WIDTH,
HEIGHT,
X,
Y
])) {
this.bounds(new Rect(shapeOptions.x, shapeOptions.y, shapeOptions.width, shapeOptions.height));
boundsChange = true;
}
if (options.connectors) {
shapeOptions.connectors = options.connectors;
this._updateConnectors();
}
shapeOptions = deepExtend(shapeOptions, options);
if (options.rotation || boundsChange) {
this._rotate();
}
if (shapeOptions.content) {
this.content(shapeOptions.content);
}
}
},
_updateConnectors: function () {
var connections = this.connections();
this.connectors = [];
this._createConnectors();
var connection;
var source;
var target;
for (var idx = 0; idx < connections.length; idx++) {
connection = connections[idx];
source = connection.source();
target = connection.target();
if (source.shape && source.shape === this) {
connection.source(this.getConnector(source.options.name) || null);
} else if (target.shape && target.shape === this) {
connection.target(this.getConnector(target.options.name) || null);
}
connection.updateModel();
}
},
_diffNumericOptions: diagram.diffNumericOptions,
_visualOptions: function (options) {
return {
data: options.path,
source: options.source,
hover: options.hover,
fill: options.fill,
stroke: options.stroke
};
},
_triggerBoundsChange: function () {
if (this.diagram) {
this.diagram.trigger(ITEMBOUNDSCHANGE, {
item: this,
bounds: this._bounds.clone()
});
}
},
_transformPoint: function (point) {
var rotate = this.rotate(), bounds = this.bounds(), tl = bounds.topLeft();
if (rotate.angle) {
point.rotate(rotate.center().plus(tl), 360 - rotate.angle);
}
return point;
},
_transformedBounds: function () {
var bounds = this.bounds(), tl = bounds.topLeft(), br = bounds.bottomRight();
return Rect.fromPoints(this.diagram.modelToView(tl), this.diagram.modelToView(br));
},
_rotatedBounds: function () {
var bounds = this.bounds().rotatedBounds(this.rotate().angle), tl = bounds.topLeft(), br = bounds.bottomRight();
return Rect.fromPoints(tl, br);
},
_rotate: function () {
var rotation = this.options.rotation;
if (rotation && rotation.angle) {
this.rotate(rotation.angle);
}
this._rotationOffset = new Point();
},
_hover: function (value) {
var options = this.options, hover = options.hover, stroke = options.stroke, fill = options.fill;
if (value && isDefined(hover.stroke)) {
stroke = deepExtend({}, stroke, hover.stroke);
}
if (value && isDefined(hover.fill)) {
fill = hover.fill;
}
this.shapeVisual.redraw({
stroke: stroke,
fill: fill
});
if (options.editable && options.editable.connect) {
this.diagram._showConnectors(this, value);
}
},
_hitTest: function (value) {
if (this.visible()) {
var bounds = this.bounds(), rotatedPoint, angle = this.rotate().angle;
if (value.isEmpty && !value.isEmpty()) {
return Intersect.rects(value, bounds, angle ? angle : 0);
} else {
rotatedPoint = value.clone().rotate(bounds.center(), angle);
if (bounds.contains(rotatedPoint)) {
return this;
}
}
}
},
toJSON: function () {
return { shapeId: this.options.id };
},
createShapeVisual: function () {
var options = this.options;
var visualOptions = this._visualOptions(options);
var visualTemplate = options.visual;
var type = (options.type + '').toLocaleLowerCase();
var shapeVisual;
visualOptions.width = options.width;
visualOptions.height = options.height;
if (isFunction(visualTemplate)) {
shapeVisual = visualTemplate.call(this, options);
} else if (visualOptions.data) {
shapeVisual = new Path(visualOptions);
translateToOrigin(shapeVisual);
} else if (type == 'rectangle') {
shapeVisual = new Rectangle(visualOptions);
} else if (type == 'circle') {
shapeVisual = new Circle(visualOptions);
} else if (type == 'text') {
shapeVisual = new TextBlock(visualOptions);
} else if (type == 'image') {
shapeVisual = new Image(visualOptions);
} else {
shapeVisual = new Path(visualOptions);
}
this.shapeVisual = shapeVisual;
this.visual.append(this.shapeVisual);
}
});
var Connection = DiagramElement.extend({
init: function (from, to, options) {
var that = this;
DiagramElement.fn.init.call(that, options);
this.updateOptionsFromModel();
this._initRouter();
that.path = new diagram.Polyline(that.options);
that.path.fill(TRANSPARENT);
that.visual.append(that.path);
that._sourcePoint = that._targetPoint = new Point();
that._setSource(from);
that._setTarget(to);
that.content(that.options.content);
that.definers = [];
if (defined(options) && options.points) {
that.points(options.points);
}
},
options: {
hover: { stroke: {} },
startCap: NONE,
endCap: NONE,
points: [],
selectable: true,
fromConnector: AUTO,
toConenctor: AUTO
},
_setOptionsFromModel: function (model) {
this.updateOptionsFromModel(model || this.dataItem);
},
updateOptionsFromModel: function (model) {
if (this.diagram && this.diagram._isEditable) {
var dataMap = this.diagram._dataMap;
var options = filterConnectionDataItem(model || this.dataItem);
if (model) {
if (defined(options.from)) {
var from = dataMap[options.from];
if (from && defined(options.fromConnector)) {
from = from.getConnector(options.fromConnector);
}
this.source(from);
} else if (defined(options.fromX) && defined(options.fromY)) {
this.source(new Point(options.fromX, options.fromY));
}
if (defined(options.to)) {
var to = dataMap[options.to];
if (to && defined(options.toConnector)) {
to = to.getConnector(options.toConnector);
}
this.target(to);
} else if (defined(options.toX) && defined(options.toY)) {
this.target(new Point(options.toX, options.toY));
}
if (defined(options.type) && this.type() !== options.type) {
this.points([]);
this.type(options.type);
}
this.dataItem = model;
this._template();
this.redraw(this.options);
} else {
this.options = deepExtend({}, options, this.options);
}
}
},
updateModel: function (syncChanges) {
if (this.diagram && this.diagram._isEditable) {
if (this.diagram.connectionsDataSource) {
var model = this.diagram.connectionsDataSource.getByUid(this.dataItem.uid);
if (model) {
this.diagram._suspendModelRefresh();
if (defined(this.options.fromX) && this.options.fromX !== null) {
clearField('from', model);
clearField('fromConnector', model);
model.set('fromX', this.options.fromX);
model.set('fromY', this.options.fromY);
} else {
model.set('from', this.options.from);
if (defined(model.fromConnector)) {
model.set('fromConnector', this.sourceConnector ? this.sourceConnector.options.name : null);
}
clearField('fromX', model);
clearField('fromY', model);
}
if (defined(this.options.toX) && this.options.toX !== null) {
clearField('to', model);
clearField('toConnector', model);
model.set('toX', this.options.toX);
model.set('toY', this.options.toY);
} else {
model.set('to', this.options.to);
if (defined(model.toConnector)) {
model.set('toConnector', this.targetConnector ? this.targetConnector.options.name : null);
}
clearField('toX', model);
clearField('toY', model);
}
if (defined(this.options.type) && defined(model.type)) {
model.set('type', this.options.type);
}
this.dataItem = model;
this.diagram._resumeModelRefresh();
if (syncChanges) {
this.diagram._syncConnectionChanges();
}
}
}
}
},
sourcePoint: function () {
return this._resolvedSourceConnector ? this._resolvedSourceConnector.position() : this._sourcePoint;
},
_setSource: function (source) {
var shapeSource = source instanceof Shape;
var defaultConnector = this.options.fromConnector || AUTO;
var dataItem;
if (shapeSource && !source.getConnector(defaultConnector)) {
return;
}
if (source !== undefined) {
this.from = source;
}
this._removeFromSourceConnector();
if (source === null) {
if (this.sourceConnector) {
this._sourcePoint = (this._resolvedSourceConnector || this.sourceConnector).position();
this._clearSourceConnector();
this._setFromOptions(null, this._sourcePoint);
}
} else if (source instanceof Connector) {
dataItem = source.shape.dataItem;
if (dataItem) {
this._setFromOptions(dataItem.id);
}
this.sourceConnector = source;
this.sourceConnector.connections.push(this);
} else if (source instanceof Point) {
this._setFromOptions(null, source);
this._sourcePoint = source;
if (this.sourceConnector) {
this._clearSourceConnector();
}
} else if (shapeSource) {
dataItem = source.dataItem;
if (dataItem) {
this._setFromOptions(dataItem.id);
}
this.sourceConnector = source.getConnector(defaultConnector);
this.sourceConnector.connections.push(this);
}
},
source: function (source, undoable) {
if (isDefined(source)) {
if (undoable && this.diagram) {
this.diagram.undoRedoService.addCompositeItem(new diagram.ConnectionEditUnit(this, source));
}
this._setSource(source);
this.refresh();
}
return this.sourceConnector ? this.sourceConnector : this._sourcePoint;
},
_setFromOptions: function (from, fromPoint) {
this.options.from = from;
if (fromPoint) {
this.options.fromX = fromPoint.x;
this.options.fromY = fromPoint.y;
} else {
this.options.fromX = null;
this.options.fromY = null;
}
},
sourceDefiner: function (value) {
if (value) {
if (value instanceof diagram.PathDefiner) {
value.left = null;
this._sourceDefiner = value;
this.source(value.point);
} else {
throw 'The sourceDefiner needs to be a PathDefiner.';
}
} else {
if (!this._sourceDefiner) {
this._sourceDefiner = new diagram.PathDefiner(this.sourcePoint(), null, null);
}
return this._sourceDefiner;
}
},
targetPoint: function () {
return this._resolvedTargetConnector ? this._resolvedTargetConnector.position() : this._targetPoint;
},
_setTarget: function (target) {
var shapeTarget = target instanceof Shape;
var defaultConnector = this.options.toConnector || AUTO;
var dataItem;
if (shapeTarget && !target.getConnector(defaultConnector)) {
return;
}
if (target !== undefined) {
this.to = target;
}
this._removeFromTargetConnector();
if (target === null) {
if (this.targetConnector) {
this._targetPoint = (this._resolvedTargetConnector || this.targetConnector).position();
this._clearTargetConnector();
this._setToOptions(null, this._targetPoint);
}
} else if (target instanceof Connector) {
dataItem = target.shape.dataItem;
if (dataItem) {
this._setToOptions(dataItem.id);
}
this.targetConnector = target;
this.targetConnector.connections.push(this);
} else if (target instanceof Point) {
this._setToOptions(null, target);
this._targetPoint = target;
if (this.targetConnector) {
this._clearTargetConnector();
}
} else if (shapeTarget) {
dataItem = target.dataItem;
if (dataItem) {
this._setToOptions(dataItem.id);
}
this.targetConnector = target.getConnector(defaultConnector);
this.targetConnector.connections.push(this);
}
},
target: function (target, undoable) {
if (isDefined(target)) {
if (undoable && this.diagram) {
this.diagram.undoRedoService.addCompositeItem(new diagram.ConnectionEditUnit(this, undefined, target));
}
this._setTarget(target);
this.refresh();
}
return this.targetConnector ? this.targetConnector : this._targetPoint;
},
_setToOptions: function (to, toPoint) {
this.options.to = to;
if (toPoint) {
this.options.toX = toPoint.x;
this.options.toY = toPoint.y;
} else {
this.options.toX = null;
this.options.toY = null;
}
},
targetDefiner: function (value) {
if (value) {
if (value instanceof diagram.PathDefiner) {
value.right = null;
this._targetDefiner = value;
this.target(value.point);
} else {
throw 'The sourceDefiner needs to be a PathDefiner.';
}
} else {
if (!this._targetDefiner) {
this._targetDefiner = new diagram.PathDefiner(this.targetPoint(), null, null);
}
return this._targetDefiner;
}
},
_updateConnectors: function () {
this._updateConnector(this.source(), 'source');
this._updateConnector(this.target(), 'target');
},
_updateConnector: function (instance, name) {
var that = this;
var diagram = that.diagram;
if (instance instanceof Connector && !diagram.getShapeById(instance.shape.id)) {
var dataItem = instance.shape.dataItem;
var connectorName = instance.options.name;
var setNewTarget = function () {
var shape = diagram._dataMap[dataItem.id];
instance = shape.getConnector(connectorName);
that[name](instance, false);
that.updateModel();
};
if (diagram._dataMap[dataItem.id]) {
setNewTarget();
} else {
var inactiveItem = diagram._inactiveShapeItems.getByUid(dataItem.uid);
if (inactiveItem) {
diagram._deferredConnectionUpdates.push(inactiveItem.onActivate(setNewTarget));
}
}
} else {
that[name](instance, false);
}
},
content: function (content) {
var result = this._content(content);
if (defined(content)) {
this._alignContent();
}
return result;
},
_createContentVisual: function (options) {
var visual;
if (isFunction(options.visual)) {
visual = options.visual.call(this, options);
} else if (options.text) {
visual = new TextBlock(options);
}
if (visual) {
this._contentVisual = visual;
visual._includeInBBox = false;
this.visual.append(visual);
}
return visual;
},
_updateContentVisual: function (options) {
if (isFunction(options.visual)) {
this.visual.remove(this._contentVisual);
this._createContentVisual(options);
} else {
this._contentVisual.redraw(options);
}
},
_alignContent: function () {
if (this._contentVisual) {
var offset = CONNECTION_CONTENT_OFFSET;
var points = this.allPoints();
var endIdx = math.floor(points.length / 2);
var startIdx = endIdx - 1;
while (startIdx > 0 && points[startIdx].equals(points[endIdx])) {
startIdx--;
endIdx++;
}
var endPoint = points[endIdx];
var startPoint = points[startIdx];
var boundingBox = this._contentVisual._measure();
var width = boundingBox.width;
var height = boundingBox.height;
var alignToPath = points.length % 2 === 0;
var distance = startPoint.distanceTo(endPoint);
if (alignToPath && points.length > 2 && distance > 0 && (startPoint.y === endPoint.y && distance < width || startPoint.x === endPoint.x && distance < height)) {
alignToPath = false;
offset = 0;
}
var point;
if (alignToPath) {
var angle = kendo.util.deg(math.atan2(endPoint.y - startPoint.y, endPoint.x - startPoint.x));
point = new Point((endPoint.x - startPoint.x) / 2 + startPoint.x, (endPoint.y - startPoint.y) / 2 + startPoint.y);
if (math.abs(angle) === 90) {
point.x += offset;
point.y -= height / 2;
} else if (angle % 180 === 0) {
point.x -= width / 2;
point.y -= height + offset;
} else if (angle < -90 || 0 < angle && angle < 90) {
point.y -= height;
} else if (angle < 0 || angle > 90) {
point.x -= width;
point.y -= height;
}
} else {
var midIdx = math.floor(points.length / 2);
point = points[midIdx].clone();
startPoint = points[midIdx - 1];
endPoint = points[midIdx + 1];
var offsetX = startPoint.x <= point.x && endPoint.x <= point.x ? offset : -boundingBox.width - offset;
var offsetY = startPoint.y <= point.y && endPoint.y <= point.y ? offset : -boundingBox.height - offset;
point.x += offsetX;
point.y += offsetY;
}
this._contentVisual.position(point);
}
},
select: function (value) {
var diagram = this.diagram, selected, deselected;
if (this._canSelect()) {
if (this.isSelected !== value) {
this.isSelected = value;
selected = [];
deselected = [];
if (this.isSelected) {
this.adorner = new ConnectionEditAdorner(this, this.options.selection);
diagram._adorn(this.adorner, true);
diagram._selectedItems.push(this);
selected.push(this);
} else {
if (this.adorner) {
diagram._adorn(this.adorner, false);
Utils.remove(diagram._selectedItems, this);
this.adorner = undefined;
deselected.push(this);
}
}
if (this.adorner) {
this.adorner.refresh();
}
if (!diagram._internalSelection) {
diagram._selectionChanged(selected, deselected);
}
return true;
}
}
},
bounds: function (value) {
if (value && !isString(value)) {
this._bounds = value;
} else {
return this._bounds;
}
},
type: function (value) {
var options = this.options;
if (value) {
if (value !== options.type) {
options.type = value;
this._initRouter();
this.refresh();
}
} else {
return options.type;
}
},
_initRouter: function () {
var type = (this.options.type || '').toLowerCase();
if (type == CASCADING) {
this._router = new CascadingRouter(this);
} else {
this._router = new PolylineRouter(this);
}
},
points: function (value) {
if (value) {
this.definers = [];
for (var i = 0; i < value.length; i++) {
var definition = value[i];
if (definition instanceof diagram.Point) {
this.definers.push(new diagram.PathDefiner(definition));
} else if (definition.hasOwnProperty('x') && definition.hasOwnProperty('y')) {
this.definers.push(new diagram.PathDefiner(new Point(definition.x, definition.y)));
} else {
throw 'A Connection point needs to be a Point or an object with x and y properties.';
}
}
} else {
var pts = [];
if (isDefined(this.definers)) {
for (var k = 0; k < this.definers.length; k++) {
pts.push(this.definers[k].point);
}
}
return pts;
}
},
allPoints: function () {
var pts = [this.sourcePoint()];
if (this.definers) {
for (var k = 0; k < this.definers.length; k++) {
pts.push(this.definers[k].point);
}
}
pts.push(this.targetPoint());
return pts;
},
refresh: function () {
this._resolveConnectors();
this._refreshPath();
this._alignContent();
if (this.adorner) {
this.adorner.refresh();
}
},
_resolveConnectors: function () {
var connection = this, sourcePoint, targetPoint, source = connection.source(), target = connection.target(), autoSourceShape, autoTargetShape;
if (source instanceof Point) {
sourcePoint = source;
} else if (source instanceof Connector) {
if (isAutoConnector(source)) {
autoSourceShape = source.shape;
} else {
connection._resolvedSourceConnector = source;
sourcePoint = source.position();
}
}
if (target instanceof Point) {
targetPoint = target;
} else if (target instanceof Connector) {
if (isAutoConnector(target)) {
autoTargetShape = target.shape;
} else {
connection._resolvedTargetConnector = target;
targetPoint = target.position();
}
}
if (sourcePoint) {
if (autoTargetShape) {
connection._resolvedTargetConnector = closestConnector(sourcePoint, autoTargetShape);
}
} else if (autoSourceShape) {
if (targetPoint) {
connection._resolvedSourceConnector = closestConnector(targetPoint, autoSourceShape);
} else if (autoTargetShape) {
this._resolveAutoConnectors(autoSourceShape, autoTargetShape);
}
}
},
_resolveAutoConnectors: function (autoSourceShape, autoTargetShape) {
var minNonConflict = MAXINT;
var minDist = MAXINT;
var sourceConnectors = autoSourceShape.connectors;
var targetConnectors;
var minNonConflictSource, minNonConflictTarget;
var sourcePoint, targetPoint;
var minSource, minTarget;
var sourceConnector, targetConnector;
var sourceIdx, targetIdx;
var dist;
for (sourceIdx = 0; sourceIdx < sourceConnectors.length; sourceIdx++) {
sourceConnector = sourceConnectors[sourceIdx];
if (!isAutoConnector(sourceConnector)) {
sourcePoint = sourceConnector.position();
targetConnectors = autoTargetShape.connectors;
for (targetIdx = 0; targetIdx < targetConnectors.length; targetIdx++) {
targetConnector = targetConnectors[targetIdx];
if (!isAutoConnector(targetConnector)) {
targetPoint = targetConnector.position();
dist = math.round(sourcePoint.distanceTo(targetPoint));
if (dist < minNonConflict && this.diagram && this._testRoutePoints(sourcePoint, targetPoint, sourceConnector, targetConnector)) {
minNonConflict = dist;
minNonConflictSource = sourceConnector;
minNonConflictTarget = targetConnector;
}
if (dist < minDist) {
minSource = sourceConnector;
minTarget = targetConnector;
minDist = dist;
}
}
}
}
}
if (minNonConflictSource) {
minSource = minNonConflictSource;
minTarget = minNonConflictTarget;
}
this._resolvedSourceConnector = minSource;
this._resolvedTargetConnector = minTarget;
},
_testRoutePoints: function (sourcePoint, targetPoint, sourceConnector, targetConnector) {
var router = this._router;
var passRoute = true;
if (router instanceof CascadingRouter) {
var points = router.routePoints(sourcePoint, targetPoint, sourceConnector, targetConnector), start, end, rect;
points.unshift(sourcePoint);
points.push(targetPoint);
for (var idx = 1; idx < points.length; idx++) {
start = points[idx - 1];
end = points[idx];
rect = new Rect(math.min(start.x, end.x), math.min(start.y, end.y), math.abs(start.x - end.x), math.abs(start.y - end.y));
if (rect.width > 0) {
rect.x++;
rect.width -= 2;
}
if (rect.height > 0) {
rect.y++;
rect.height -= 2;
}
if (!rect.isEmpty() && this.diagram._shapesQuadTree.hitTestRect(rect)) {
passRoute = false;
break;
}
}
}
return passRoute;
},
redraw: function (options) {
if (options) {
this.options = deepExtend({}, this.options, options);
var points = this.options.points;
if (defined(points) && points.length > 0) {
this.points(points);
this._refreshPath();
}
if (options && options.content || options.text) {
this.content(options.content);
}
this.path.redraw({
fill: options.fill,
stroke: options.stroke,
startCap: options.startCap,
endCap: options.endCap
});
}
},
clone: function () {
var json = this.serialize();
if (this.diagram && this.diagram._isEditable && defined(this.dataItem)) {
json.options.dataItem = cloneDataItem(this.dataItem);
}
return new Connection(this.from, this.to, json.options);
},
serialize: function () {
var from = this.from.toJSON ? this.from.toJSON : this.from.toString(), to = this.to.toJSON ? this.to.toJSON : this.to.toString();
var json = deepExtend({}, {
options: this.options,
from: from,
to: to
});
if (defined(this.dataItem)) {
json.dataItem = this.dataItem.toString();
}
json.options.points = this.points();
return json;
},
_hitTest: function (value) {
if (this.visible()) {
var p = new Point(value.x, value.y), from = this.sourcePoint(), to = this.targetPoint();
if (value.isEmpty && !value.isEmpty() && value.contains(from) && value.contains(to)) {
return this;
}
if (this._router.hitTest(p)) {
return this;
}
}
},
_hover: function (value) {
var color = (this.options.stroke || {}).color;
if (value && isDefined(this.options.hover.stroke.color)) {
color = this.options.hover.stroke.color;
}
this.path.redraw({ stroke: { color: color } });
},
_refreshPath: function () {
if (!defined(this.path)) {
return;
}
this._drawPath();
this.bounds(this._router.getBounds());
},
_drawPath: function () {
if (this._router) {
this._router.route();
}
var source = this.sourcePoint();
var target = this.targetPoint();
var points = this.points();
this.path.redraw({ points: [source].concat(points, [target]) });
},
_clearSourceConnector: function () {
this.sourceConnector = undefined;
this._resolvedSourceConnector = undefined;
},
_clearTargetConnector: function () {
this.targetConnector = undefined;
this._resolvedTargetConnector = undefined;
},
_removeFromSourceConnector: function () {
if (this.sourceConnector) {
Utils.remove(this.sourceConnector.connections, this);
}
},
_removeFromTargetConnector: function () {
if (this.targetConnector) {
Utils.remove(this.targetConnector.connections, this);
}
},
toJSON: function () {
var connection = this;
var from, to, point;
if (connection.from && connection.from.toJSON) {
from = connection.from.toJSON();
} else {
point = connection._sourcePoint;
from = {
x: point.x,
y: point.y
};
}
if (connection.to && connection.to.toJSON) {
to = connection.to.toJSON();
} else {
point = connection._targetPoint;
to = {
x: point.x,
y: point.y
};
}
return {
from: from,
to: to
};
}
});
var Diagram = Widget.extend({
init: function (element, userOptions) {
var that = this;
kendo.destroy(element);
Widget.fn.init.call(that, element, userOptions);
that._initTheme();
that._initElements();
that._extendLayoutOptions(that.options);
that._initDefaults(userOptions);
that._initCanvas();
that.mainLayer = new Group({ id: 'main-layer' });
that.canvas.append(that.mainLayer);
that._shapesQuadTree = new ShapesQuadTree(that);
that._pan = new Point();
that._adorners = [];
that.adornerLayer = new Group({ id: 'adorner-layer' });
that.canvas.append(that.adornerLayer);
that._createHandlers();
that._initialize();
that._resizingAdorner = new ResizingAdorner(that, { editable: that.options.editable });
that._connectorsAdorner = new ConnectorsAdorner(that);
that._adorn(that._resizingAdorner, true);
that._adorn(that._connectorsAdorner, true);
that.selector = new Selector(that);
that._clipboard = [];
that.pauseMouseHandlers = false;
that._fetchFreshData();
that._createGlobalToolBar();
that._createOptionElements();
that.zoom(that.options.zoom);
that.canvas.draw();
},
options: {
name: 'Diagram',
theme: 'default',
layout: '',
zoomRate: 0.1,
zoom: 1,
zoomMin: 0,
zoomMax: 2,
dataSource: {},
draggable: true,
template: '',
autoBind: true,
editable: {
rotate: {},
resize: {},
text: true,
tools: [],
drag: {
snap: {
size: 10,
angle: 10
}
},
remove: true
},
pannable: { key: 'ctrl' },
selectable: { key: 'none' },
tooltip: {
enabled: true,
format: '{0}'
},
copy: {
enabled: true,
offsetX: 20,
offsetY: 20
},
shapeDefaults: diagram.shapeDefaults({ undoable: true }),
connectionDefaults: {
editable: { tools: [] },
type: CASCADING
},
shapes: [],
connections: []
},
events: [
ZOOM_END,
ZOOM_START,
PAN,
SELECT,
ITEMROTATE,
ITEMBOUNDSCHANGE,
CHANGE,
CLICK,
MOUSE_ENTER,
MOUSE_LEAVE,
'toolBarClick',
'save',
'cancel',
'edit',
'remove',
'add',
'dataBound',
DRAG_START,
DRAG,
DRAG_END
],
items: function () {
return $();
},
_createGlobalToolBar: function () {
var editable = this.options.editable;
if (editable) {
var tools = editable.tools;
if (this._isEditable && tools !== false && (!tools || tools.length === 0)) {
tools = [
'createShape',
'undo',
'redo',
'rotateClockwise',
'rotateAnticlockwise'
];
}
if (tools && tools.length) {
this.toolBar = new DiagramToolBar(this, {
tools: tools || {},
click: proxy(this._toolBarClick, this),
modal: false
});
this.toolBar.element.css({ textAlign: 'left' });
this.element.prepend(this.toolBar.element);
this._resize();
}
}
},
createShape: function () {
if (this.editor && this.editor.end() || !this.editor) {
var dataSource = this.dataSource;
var view = dataSource.view() || [];
var index = view.length;
var model = createModel(dataSource, {});
var shape = this._createShape(model, {});
if (!this.trigger('add', { shape: shape })) {
dataSource.insert(index, model);
var inactiveItem = this._inactiveShapeItems.getByUid(model.uid);
inactiveItem.element = shape;
this.edit(shape);
}
}
},
_createShape: function (dataItem, options) {
options = deepExtend({}, this.options.shapeDefaults, options);
options.dataItem = dataItem;
var shape = new Shape(options, this);
return shape;
},
createConnection: function () {
if (this.editor && this.editor.end() || !this.editor) {
var connectionsDataSource = this.connectionsDataSource;
var view = connectionsDataSource.view() || [];
var index = view.length;
var model = createModel(connectionsDataSource, {});
var connection = this._createConnection(model);
if (!this.trigger('add', { connection: connection })) {
this._connectionsDataMap[model.uid] = connection;
connectionsDataSource.insert(index, model);
this.addConnection(connection, false);
this.edit(connection);
}
}
},
_createConnection: function (dataItem, source, target) {
var options = deepExtend({}, this.options.connectionDefaults);
options.dataItem = dataItem;
var connection = new Connection(source || new Point(), target || new Point(), options);
return connection;
},
editModel: function (dataItem, editorType) {
this.cancelEdit();
var editors, template;
var editable = this.options.editable;
if (editorType == 'shape') {
editors = editable.shapeEditors;
template = editable.shapeTemplate;
} else if (editorType == 'connection') {
var connectionSelectorHandler = proxy(connectionSelector, this);
editors = deepExtend({}, {
from: connectionSelectorHandler,
to: connectionSelectorHandler
}, editable.connectionEditors);
template = editable.connectionTemplate;
} else {
return;
}
this.editor = new PopupEditor(this.element, {
update: proxy(this._update, this),
cancel: proxy(this._cancel, this),
model: dataItem,
type: editorType,
target: this,
editors: editors,
template: template
});
this.trigger('edit', this._editArgs());
},
edit: function (item) {
if (item.dataItem) {
var editorType = item instanceof Shape ? 'shape' : 'connection';
this.editModel(item.dataItem, editorType);
}
},
cancelEdit: function () {
if (this.editor) {
this._getEditDataSource().cancelChanges(this.editor.model);
this._destroyEditor();
}
},
saveEdit: function () {
if (this.editor && this.editor.end() && !this.trigger('save', this._editArgs())) {
this._getEditDataSource().sync();
}
},
_update: function () {
if (this.editor && this.editor.end() && !this.trigger('save', this._editArgs())) {
this._getEditDataSource().sync();
this._destroyEditor();
}
},
_cancel: function () {
if (this.editor && !this.trigger('cancel', this._editArgs())) {
var model = this.editor.model;
this._getEditDataSource().cancelChanges(model);
var element = this._connectionsDataMap[model.uid] || this._dataMap[model.id];
if (element) {
element._setOptionsFromModel(model);
}
this._destroyEditor();
}
},
_getEditDataSource: function () {
return this.editor.options.type === 'shape' ? this.dataSource : this.connectionsDataSource;
},
_editArgs: function () {
var result = { container: this.editor.wrapper };
result[this.editor.options.type] = this.editor.model;
return result;
},
_destroyEditor: function () {
if (this.editor) {
this.editor.close();
this.editor = null;
}
},
_initElements: function () {
this.wrapper = this.element.empty().css('position', 'relative').attr('tabindex', 0).addClass('k-widget k-diagram');
this.scrollable = $('
').appendTo(this.element);
},
_initDefaults: function (userOptions) {
var options = this.options;
var editable = options.editable;
var shapeDefaults = options.shapeDefaults;
var connectionDefaults = options.connectionDefaults;
var userShapeDefaults = (userOptions || {}).shapeDefaults;
if (editable === false) {
shapeDefaults.editable = false;
connectionDefaults.editable = false;
} else {
copyDefaultOptions(editable, shapeDefaults.editable, [
'drag',
'remove',
'connect'
]);
copyDefaultOptions(editable, connectionDefaults.editable, [
'drag',
'remove'
]);
}
if (userShapeDefaults && userShapeDefaults.connectors) {
options.shapeDefaults.connectors = userShapeDefaults.connectors;
}
},
_initCanvas: function () {
var canvasContainer = $('
').appendTo(this.scrollable)[0];
var viewPort = this.viewport();
this.canvas = new Canvas(canvasContainer, {
width: viewPort.width || DEFAULT_CANVAS_WIDTH,
height: viewPort.height || DEFAULT_CANVAS_HEIGHT
});
},
_createHandlers: function () {
var that = this;
var element = that.element;
element.on(MOUSEWHEEL_NS, proxy(that._wheel, that));
if (!kendo.support.touch && !kendo.support.mobileOS) {
that.toolService = new ToolService(that);
this.scroller.wrapper.on('mousemove' + NS, proxy(that._mouseMove, that)).on('mouseup' + NS, proxy(that._mouseUp, that)).on('mousedown' + NS, proxy(that._mouseDown, that)).on('mouseover' + NS, proxy(that._mouseover, that)).on('mouseout' + NS, proxy(that._mouseout, that));
element.on('keydown' + NS, proxy(that._keydown, that));
} else {
that._userEvents = new kendo.UserEvents(element, {
multiTouch: true,
tap: proxy(that._tap, that)
});
that._userEvents.bind([
'gesturestart',
'gesturechange',
'gestureend'
], {
gesturestart: proxy(that._gestureStart, that),
gesturechange: proxy(that._gestureChange, that),
gestureend: proxy(that._gestureEnd, that)
});
that.toolService = new ToolService(that);
if (that.options.pannable !== false) {
that.scroller.enable();
}
}
this._syncHandler = proxy(that._syncChanges, that);
that._resizeHandler = proxy(that.resize, that, false);
kendo.onResize(that._resizeHandler);
this.bind(ZOOM_START, proxy(that._destroyToolBar, that));
this.bind(PAN, proxy(that._destroyToolBar, that));
},
_tap: function (e) {
var toolService = this.toolService;
var p = this._caculateMobilePosition(e);
toolService._updateHoveredItem(p);
if (toolService.hoveredItem) {
var item = toolService.hoveredItem;
if (this.options.selectable !== false) {
this._destroyToolBar();
if (item.isSelected) {
item.select(false);
} else {
this.select(item, { addToSelection: true });
}
this._createToolBar();
}
this.trigger('click', {
item: item,
point: p
});
}
},
_caculateMobilePosition: function (e) {
return this.documentToModel(Point(e.x.location, e.y.location));
},
_gestureStart: function (e) {
this._destroyToolBar();
this.scroller.disable();
var initialCenter = this.documentToModel(new Point(e.center.x, e.center.y));
var eventArgs = {
point: initialCenter,
zoom: this.zoom()
};
if (this.trigger(ZOOM_START, eventArgs)) {
return;
}
this._gesture = e;
this._initialCenter = initialCenter;
},
_gestureChange: function (e) {
var previousGesture = this._gesture;
var initialCenter = this._initialCenter;
var center = this.documentToView(new Point(e.center.x, e.center.y));
var scaleDelta = e.distance / previousGesture.distance;
var zoom = this._zoom;
var updateZoom = false;
if (math.abs(scaleDelta - 1) >= MOBILE_ZOOM_RATE) {
this._zoom = zoom = this._getValidZoom(zoom * scaleDelta);
this.options.zoom = zoom;
this._gesture = e;
updateZoom = true;
}
var zoomedPoint = initialCenter.times(zoom);
var pan = center.minus(zoomedPoint);
if (updateZoom || this._pan.distanceTo(pan) >= MOBILE_PAN_DISTANCE) {
this._panTransform(pan);
this._updateAdorners();
}
e.preventDefault();
},
_gestureEnd: function () {
if (this.options.pannable !== false) {
this.scroller.enable();
}
this.trigger(ZOOM_END, {
point: this._initialCenter,
zoom: this.zoom()
});
},
_resize: function () {
var viewport = this.viewport();
if (this.canvas) {
this.canvas.size(viewport);
}
if (this.scrollable && this.toolBar) {
this.scrollable.height(viewport.height);
}
},
_mouseover: function (e) {
var node = e.target._kendoNode;
if (node && node.srcElement._hover) {
node.srcElement._hover(true, node.srcElement);
}
},
_mouseout: function (e) {
var node = e.target._kendoNode;
if (node && node.srcElement._hover) {
node.srcElement._hover(false, node.srcElement);
}
},
_initTheme: function () {
var that = this, themes = dataviz.ui.themes || {}, themeName = ((that.options || {}).theme || '').toLowerCase(), themeOptions = (themes[themeName] || {}).diagram;
that.options = deepExtend({}, themeOptions, that.options);
if (that.options.editable === true) {
deepExtend(that.options, { editable: (themeOptions || {}).editable });
}
},
_createOptionElements: function () {
var options = this.options;
var shapesLength = options.shapes.length;
if (shapesLength) {
this._createShapes();
}
if (options.connections.length) {
this._createConnections();
}
if (shapesLength && options.layout) {
this.layout(options.layout);
}
},
_createShapes: function () {
var that = this, options = that.options, shapes = options.shapes, shape, i;
for (i = 0; i < shapes.length; i++) {
shape = shapes[i];
that.addShape(shape);
}
},
_createConnections: function () {
var diagram = this, options = diagram.options, defaults = options.connectionDefaults, connections = options.connections, conn, source, target, i;
for (i = 0; i < connections.length; i++) {
conn = connections[i];
source = diagram._findConnectionTarget(conn.from);
target = diagram._findConnectionTarget(conn.to);
diagram.connect(source, target, deepExtend({}, defaults, conn));
}
},
_findConnectionTarget: function (options) {
var diagram = this;
var shapeId = isString(options) ? options : options.shapeId || options.id;
var target;
if (shapeId) {
target = diagram.getShapeById(shapeId);
if (options.connector) {
target = target.getConnector(options.connector);
}
} else {
target = new Point(options.x || 0, options.y || 0);
}
return target;
},
destroy: function () {
var that = this;
Widget.fn.destroy.call(that);
if (this._userEvents) {
this._userEvents.destroy();
}
kendo.unbindResize(that._resizeHandler);
that.clear();
that.element.off(NS);
that.scroller.wrapper.off(NS);
that.canvas.destroy(true);
that.canvas = undefined;
that._destroyEditor();
that.destroyScroller();
that._destroyGlobalToolBar();
that._destroyToolBar();
},
destroyScroller: function () {
var scroller = this.scroller;
if (!scroller) {
return;
}
scroller.destroy();
scroller.element.remove();
this.scroller = null;
},
save: function () {
var json = {
shapes: [],
connections: []
};
var i, connection, shape;
for (i = 0; i < this.shapes.length; i++) {
shape = this.shapes[i];
if (shape.options.serializable) {
json.shapes.push(shape.options);
}
}
for (i = 0; i < this.connections.length; i++) {
connection = this.connections[i];
json.connections.push(deepExtend({}, connection.options, connection.toJSON()));
}
return json;
},
focus: function () {
if (!this.element.is(kendo._activeElement())) {
var element = this.element, scrollContainer = element[0], containers = [], offsets = [], documentElement = document.documentElement, i;
do {
scrollContainer = scrollContainer.parentNode;
if (scrollContainer.scrollHeight > scrollContainer.clientHeight) {
containers.push(scrollContainer);
offsets.push(scrollContainer.scrollTop);
}
} while (scrollContainer != documentElement);
element.focus();
for (i = 0; i < containers.length; i++) {
containers[i].scrollTop = offsets[i];
}
}
},
load: function (options) {
this.clear();
this.setOptions(options);
this._createShapes();
this._createConnections();
},
setOptions: function (options) {
deepExtend(this.options, options);
},
clear: function () {
var that = this;
that.select(false);
that.mainLayer.clear();
that._shapesQuadTree.clear();
that._initialize();
},
connect: function (source, target, options) {
var connection;
if (this.connectionsDataSource && this._isEditable) {
var dataItem = this.connectionsDataSource.add({});
connection = this._connectionsDataMap[dataItem.uid];
connection.source(source);
connection.target(target);
connection.redraw(options);
connection.updateModel();
} else {
connection = new Connection(source, target, deepExtend({}, this.options.connectionDefaults, options));
this.addConnection(connection);
}
return connection;
},
connected: function (source, target) {
for (var i = 0; i < this.connections.length; i++) {
var c = this.connections[i];
if (c.from == source && c.to == target) {
return true;
}
}
return false;
},
addConnection: function (connection, undoable) {
if (undoable !== false) {
this.undoRedoService.add(new diagram.AddConnectionUnit(connection, this), false);
}
connection.diagram = this;
connection._setOptionsFromModel();
connection.refresh();
this.mainLayer.append(connection.visual);
this.connections.push(connection);
this.trigger(CHANGE, {
added: [connection],
removed: []
});
return connection;
},
_addConnection: function (connection, undoable) {
var connectionsDataSource = this.connectionsDataSource;
var dataItem;
if (connectionsDataSource && this._isEditable) {
dataItem = createModel(connectionsDataSource, cloneDataItem(connection.dataItem));
connection.dataItem = dataItem;
connection.updateModel();
if (!this.trigger('add', { connection: connection })) {
this._connectionsDataMap[dataItem.uid] = connection;
connectionsDataSource.add(dataItem);
this.addConnection(connection, undoable);
connection._updateConnectors();
return connection;
}
} else if (!this.trigger('add', { connection: connection })) {
this.addConnection(connection, undoable);
connection._updateConnectors();
return connection;
}
},
addShape: function (item, undoable) {
var shape, shapeDefaults = this.options.shapeDefaults;
if (item instanceof Shape) {
shape = item;
} else if (!(item instanceof kendo.Class)) {
shapeDefaults = deepExtend({}, shapeDefaults, item || {});
shape = new Shape(shapeDefaults, this);
} else {
return;
}
if (undoable !== false) {
this.undoRedoService.add(new diagram.AddShapeUnit(shape, this), false);
}
this.shapes.push(shape);
if (shape.diagram !== this) {
this._shapesQuadTree.insert(shape);
shape.diagram = this;
}
this.mainLayer.append(shape.visual);
this.trigger(CHANGE, {
added: [shape],
removed: []
});
return shape;
},
_addShape: function (shape, undoable) {
var that = this;
var dataSource = that.dataSource;
var dataItem;
if (dataSource && this._isEditable) {
dataItem = createModel(dataSource, cloneDataItem(shape.dataItem));
shape.dataItem = dataItem;
shape.updateModel();
if (!this.trigger('add', { shape: shape })) {
this.dataSource.add(dataItem);
var inactiveItem = this._inactiveShapeItems.getByUid(dataItem.uid);
inactiveItem.element = shape;
inactiveItem.undoable = undoable;
return shape;
}
} else if (!this.trigger('add', { shape: shape })) {
return this.addShape(shape, undoable);
}
},
remove: function (items, undoable) {
items = isArray(items) ? items.slice(0) : [items];
var elements = splitDiagramElements(items);
var shapes = elements.shapes;
var connections = elements.connections;
var i;
if (!defined(undoable)) {
undoable = true;
}
if (undoable) {
this.undoRedoService.begin();
}
this._suspendModelRefresh();
for (i = shapes.length - 1; i >= 0; i--) {
this._removeItem(shapes[i], undoable, connections);
}
for (i = connections.length - 1; i >= 0; i--) {
this._removeItem(connections[i], undoable);
}
this._resumeModelRefresh();
if (undoable) {
this.undoRedoService.commit(false);
}
this.trigger(CHANGE, {
added: [],
removed: items
});
},
_removeShapeDataItem: function (item) {
if (this._isEditable) {
this.dataSource.remove(item.dataItem);
delete this._dataMap[item.dataItem.id];
}
},
_removeConnectionDataItem: function (item) {
if (this._isEditable) {
this.connectionsDataSource.remove(item.dataItem);
delete this._connectionsDataMap[item.dataItem.uid];
}
},
_triggerRemove: function (items) {
var toRemove = [];
var item, args, editable;
for (var idx = 0; idx < items.length; idx++) {
item = items[idx];
editable = item.options.editable;
if (item instanceof Shape) {
args = { shape: item };
} else {
args = { connection: item };
}
if (editable && editable.remove !== false && !this.trigger('remove', args)) {
toRemove.push(item);
}
}
return toRemove;
},
undo: function () {
this.undoRedoService.undo();
},
redo: function () {
this.undoRedoService.redo();
},
select: function (item, options) {
if (isDefined(item)) {
options = deepExtend({ addToSelection: false }, options);
var addToSelection = options.addToSelection, items = [], selected = [], i, element;
if (!addToSelection) {
this.deselect();
}
this._internalSelection = true;
if (item instanceof Array) {
items = item;
} else if (item instanceof DiagramElement) {
items = [item];
}
for (i = 0; i < items.length; i++) {
element = items[i];
if (element.select(true)) {
selected.push(element);
}
}
this._selectionChanged(selected, []);
this._internalSelection = false;
} else {
return this._selectedItems;
}
},
selectAll: function () {
this.select(this.shapes.concat(this.connections));
},
selectArea: function (rect) {
var i, items, item;
this._internalSelection = true;
var selected = [];
if (rect instanceof Rect) {
items = this.shapes.concat(this.connections);
for (i = 0; i < items.length; i++) {
item = items[i];
if ((!rect || item._hitTest(rect)) && item.options.enable) {
if (item.select(true)) {
selected.push(item);
}
}
}
}
this._selectionChanged(selected, []);
this._internalSelection = false;
},
deselect: function (item) {
this._internalSelection = true;
var deselected = [], items = [], element, i;
if (item instanceof Array) {
items = item;
} else if (item instanceof DiagramElement) {
items.push(item);
} else if (!isDefined(item)) {
items = this._selectedItems.slice(0);
}
for (i = 0; i < items.length; i++) {
element = items[i];
if (element.select(false)) {
deselected.push(element);
}
}
this._selectionChanged([], deselected);
this._internalSelection = false;
},
toFront: function (items, undoable) {
if (!items) {
items = this._selectedItems.slice();
}
var result = this._getDiagramItems(items), indices;
if (!defined(undoable) || undoable) {
indices = indicesOfItems(this.mainLayer, result.visuals);
var unit = new ToFrontUnit(this, items, indices);
this.undoRedoService.add(unit);
} else {
this.mainLayer.toFront(result.visuals);
this._fixOrdering(result, true);
}
},
toBack: function (items, undoable) {
if (!items) {
items = this._selectedItems.slice();
}
var result = this._getDiagramItems(items), indices;
if (!defined(undoable) || undoable) {
indices = indicesOfItems(this.mainLayer, result.visuals);
var unit = new ToBackUnit(this, items, indices);
this.undoRedoService.add(unit);
} else {
this.mainLayer.toBack(result.visuals);
this._fixOrdering(result, false);
}
},
bringIntoView: function (item, options) {
var viewport = this.viewport();
var aligner = new diagram.RectAlign(viewport);
var current, rect, original, newPan;
if (viewport.width === 0 || viewport.height === 0) {
return;
}
options = deepExtend({
animate: false,
align: 'center middle'
}, options);
if (options.align == 'none') {
options.align = 'center middle';
}
if (item instanceof DiagramElement) {
rect = item.bounds(TRANSFORMED);
} else if (isArray(item)) {
rect = this.boundingBox(item);
} else if (item instanceof Rect) {
rect = item.clone();
}
original = rect.clone();
rect.zoom(this._zoom);
if (rect.width > viewport.width || rect.height > viewport.height) {
this._zoom = this._getValidZoom(math.min(viewport.width / original.width, viewport.height / original.height));
rect = original.clone().zoom(this._zoom);
}
this._zoomMainLayer();
current = rect.clone();
aligner.align(rect, options.align);
newPan = rect.topLeft().minus(current.topLeft());
this.pan(newPan.times(-1), options.animate);
},
alignShapes: function (direction) {
if (isUndefined(direction)) {
direction = 'Left';
}
var items = this.select(), val, item, i;
if (items.length === 0) {
return;
}
switch (direction.toLowerCase()) {
case 'left':
case 'top':
val = MAX_VALUE;
break;
case 'right':
case 'bottom':
val = MIN_VALUE;
break;
}
for (i = 0; i < items.length; i++) {
item = items[i];
if (item instanceof Shape) {
switch (direction.toLowerCase()) {
case 'left':
val = math.min(val, item.options.x);
break;
case 'top':
val = math.min(val, item.options.y);
break;
case 'right':
val = math.max(val, item.options.x);
break;
case 'bottom':
val = math.max(val, item.options.y);
break;
}
}
}
var undoStates = [];
var shapes = [];
for (i = 0; i < items.length; i++) {
item = items[i];
if (item instanceof Shape) {
shapes.push(item);
undoStates.push(item.bounds());
switch (direction.toLowerCase()) {
case 'left':
case 'right':
item.position(new Point(val, item.options.y));
break;
case 'top':
case 'bottom':
item.position(new Point(item.options.x, val));
break;
}
}
}
var unit = new diagram.TransformUnit(shapes, undoStates);
this.undoRedoService.add(unit, false);
},
zoom: function (zoom, options) {
if (zoom) {
var staticPoint = options ? options.point : new diagram.Point(0, 0);
zoom = this._zoom = this._getValidZoom(zoom);
if (!isUndefined(staticPoint)) {
staticPoint = new diagram.Point(math.round(staticPoint.x), math.round(staticPoint.y));
var zoomedPoint = staticPoint.times(zoom);
var viewportVector = this.modelToView(staticPoint);
var raw = viewportVector.minus(zoomedPoint);
this._storePan(new diagram.Point(math.round(raw.x), math.round(raw.y)));
}
if (options) {
options.zoom = zoom;
}
this._panTransform();
this._updateAdorners();
}
return this._zoom;
},
_getPan: function (pan) {
var canvas = this.canvas;
if (!canvas.translate) {
pan = pan.plus(this._pan);
}
return pan;
},
pan: function (pan, animate) {
if (pan instanceof Point) {
var that = this;
var scroller = that.scroller;
pan = that._getPan(pan);
pan = pan.times(-1);
if (animate) {
scroller.animatedScrollTo(pan.x, pan.y, function () {
that._updateAdorners();
});
} else {
scroller.scrollTo(pan.x, pan.y);
that._updateAdorners();
}
}
},
viewport: function () {
var element = this.element;
var width = element.width();
var height = element.height();
if (this.toolBar) {
height -= this.toolBar.element.outerHeight();
}
return new Rect(0, 0, width, height);
},
copy: function () {
if (this.options.copy.enabled) {
this._clipboard = [];
this._copyOffset = 1;
for (var i = 0; i < this._selectedItems.length; i++) {
var item = this._selectedItems[i];
this._clipboard.push(item);
}
}
},
cut: function () {
if (this.options.copy.enabled) {
this._clipboard = [];
this._copyOffset = 0;
for (var i = 0; i < this._selectedItems.length; i++) {
var item = this._selectedItems[i];
this._clipboard.push(item);
}
this.remove(this._clipboard, true);
}
},
paste: function () {
if (this._clipboard.length > 0) {
var item, copied, i;
var mapping = {};
var elements = splitDiagramElements(this._clipboard);
var connections = elements.connections;
var shapes = elements.shapes;
var offset = {
x: this._copyOffset * this.options.copy.offsetX,
y: this._copyOffset * this.options.copy.offsetY
};
this.deselect();
for (i = 0; i < shapes.length; i++) {
item = shapes[i];
copied = item.clone();
mapping[item.id] = copied;
copied.position(new Point(item.options.x + offset.x, item.options.y + offset.y));
copied.diagram = this;
copied = this._addShape(copied);
if (copied) {
copied.select();
}
}
for (i = 0; i < connections.length; i++) {
item = connections[i];
copied = this._addConnection(item.clone());
if (copied) {
this._updateCopiedConnection(copied, item, 'source', mapping, offset);
this._updateCopiedConnection(copied, item, 'target', mapping, offset);
copied.select(true);
copied.updateModel();
}
}
this._syncChanges();
this._copyOffset += 1;
}
},
_updateCopiedConnection: function (connection, sourceConnection, connectorName, mapping, offset) {
var onActivate, inactiveItem, targetShape;
var target = sourceConnection[connectorName]();
var diagram = this;
if (target instanceof Connector && mapping[target.shape.id]) {
targetShape = mapping[target.shape.id];
if (diagram.getShapeById(targetShape.id)) {
connection[connectorName](targetShape.getConnector(target.options.name));
} else {
inactiveItem = diagram._inactiveShapeItems.getByUid(targetShape.dataItem.uid);
if (inactiveItem) {
onActivate = function (item) {
targetShape = diagram._dataMap[item.id];
connection[connectorName](targetShape.getConnector(target.options.name));
connection.updateModel();
};
diagram._deferredConnectionUpdates.push(inactiveItem.onActivate(onActivate));
}
}
} else {
connection[connectorName](new Point(sourceConnection[connectorName + 'Point']().x + offset.x, sourceConnection[connectorName + 'Point']().y + offset.y));
}
},
boundingBox: function (items, origin) {
var rect = Rect.empty(), temp, di = isDefined(items) ? this._getDiagramItems(items) : { shapes: this.shapes };
if (di.shapes.length > 0) {
var item = di.shapes[0];
rect = item.bounds(ROTATED);
for (var i = 1; i < di.shapes.length; i++) {
item = di.shapes[i];
temp = item.bounds(ROTATED);
if (origin === true) {
temp.x -= item._rotationOffset.x;
temp.y -= item._rotationOffset.y;
}
rect = rect.union(temp);
}
}
return rect;
},
_containerOffset: function () {
var containerOffset = this.element.offset();
if (this.toolBar) {
containerOffset.top += this.toolBar.element.outerHeight();
}
return containerOffset;
},
documentToView: function (point) {
var containerOffset = this._containerOffset();
return new Point(point.x - containerOffset.left, point.y - containerOffset.top);
},
viewToDocument: function (point) {
var containerOffset = this._containerOffset();
return new Point(point.x + containerOffset.left, point.y + containerOffset.top);
},
viewToModel: function (point) {
return this._transformWithMatrix(point, this._matrixInvert);
},
modelToView: function (point) {
return this._transformWithMatrix(point, this._matrix);
},
modelToLayer: function (point) {
return this._transformWithMatrix(point, this._layerMatrix);
},
layerToModel: function (point) {
return this._transformWithMatrix(point, this._layerMatrixInvert);
},
documentToModel: function (point) {
var viewPoint = this.documentToView(point);
if (!this.canvas.translate) {
viewPoint.x = viewPoint.x + this.scroller.scrollLeft;
viewPoint.y = viewPoint.y + this.scroller.scrollTop;
}
return this.viewToModel(viewPoint);
},
modelToDocument: function (point) {
return this.viewToDocument(this.modelToView(point));
},
_transformWithMatrix: function (point, matrix) {
var result = point;
if (point instanceof Point) {
if (matrix) {
result = matrix.apply(point);
}
} else {
var tl = this._transformWithMatrix(point.topLeft(), matrix), br = this._transformWithMatrix(point.bottomRight(), matrix);
result = Rect.fromPoints(tl, br);
}
return result;
},
setDataSource: function (dataSource) {
this.options.dataSource = dataSource;
this._dataSource();
if (this.options.autoBind) {
this.dataSource.fetch();
}
},
setConnectionsDataSource: function (dataSource) {
this.options.connectionsDataSource = dataSource;
this._connectionDataSource();
if (this.options.autoBind) {
this.connectionsDataSource.fetch();
}
},
layout: function (options) {
this._layouting = true;
var type;
if (isUndefined(options)) {
options = this.options.layout;
}
if (isUndefined(options) || isUndefined(options.type)) {
type = 'Tree';
} else {
type = options.type;
}
var l;
switch (type.toLowerCase()) {
case 'tree':
l = new diagram.TreeLayout(this);
break;
case 'layered':
l = new diagram.LayeredLayout(this);
break;
case 'forcedirected':
case 'force':
case 'spring':
case 'springembedder':
l = new diagram.SpringLayout(this);
break;
default:
throw 'Layout algorithm \'' + type + '\' is not supported.';
}
var initialState = new diagram.LayoutState(this);
var finalState = l.layout(options);
if (finalState) {
var unit = new diagram.LayoutUndoUnit(initialState, finalState, options ? options.animate : null);
this.undoRedoService.add(unit);
}
this._layouting = false;
this._redrawConnections();
},
getShapeById: function (id) {
var found;
found = Utils.first(this.shapes, function (s) {
return s.visual.id === id;
});
if (found) {
return found;
}
found = Utils.first(this.connections, function (c) {
return c.visual.id === id;
});
return found;
},
_extendLayoutOptions: function (options) {
if (options.layout) {
options.layout = deepExtend(diagram.LayoutBase.fn.defaultOptions || {}, options.layout);
}
},
_selectionChanged: function (selected, deselected) {
if (selected.length || deselected.length) {
this.trigger(SELECT, {
selected: selected,
deselected: deselected
});
}
},
_getValidZoom: function (zoom) {
return math.min(math.max(zoom, this.options.zoomMin), this.options.zoomMax);
},
_panTransform: function (pos) {
var diagram = this, pan = pos || diagram._pan;
if (diagram.canvas.translate) {
diagram.scroller.scrollTo(pan.x, pan.y);
diagram._zoomMainLayer();
} else {
diagram._storePan(pan);
diagram._transformMainLayer();
}
},
_finishPan: function () {
this.trigger(PAN, {
total: this._pan,
delta: Number.NaN
});
},
_storePan: function (pan) {
this._pan = pan;
this._storeViewMatrix();
},
_zoomMainLayer: function () {
var zoom = this._zoom;
var transform = new CompositeTransform(0, 0, zoom, zoom);
transform.render(this.mainLayer);
this._storeLayerMatrix(transform);
this._storeViewMatrix();
},
_transformMainLayer: function () {
var pan = this._pan, zoom = this._zoom;
var transform = new CompositeTransform(pan.x, pan.y, zoom, zoom);
transform.render(this.mainLayer);
this._storeLayerMatrix(transform);
this._storeViewMatrix();
},
_storeLayerMatrix: function (canvasTransform) {
this._layerMatrix = canvasTransform.toMatrix();
this._layerMatrixInvert = canvasTransform.invert().toMatrix();
},
_storeViewMatrix: function () {
var pan = this._pan, zoom = this._zoom;
var transform = new CompositeTransform(pan.x, pan.y, zoom, zoom);
this._matrix = transform.toMatrix();
this._matrixInvert = transform.invert().toMatrix();
},
_toIndex: function (items, indices) {
var result = this._getDiagramItems(items);
this.mainLayer.toIndex(result.visuals, indices);
this._fixOrdering(result, false);
},
_fixOrdering: function (result, toFront) {
var shapePos = toFront ? this.shapes.length - 1 : 0, conPos = toFront ? this.connections.length - 1 : 0, i, item;
for (i = 0; i < result.shapes.length; i++) {
item = result.shapes[i];
Utils.remove(this.shapes, item);
Utils.insert(this.shapes, item, shapePos);
}
for (i = 0; i < result.cons.length; i++) {
item = result.cons[i];
Utils.remove(this.connections, item);
Utils.insert(this.connections, item, conPos);
}
},
_getDiagramItems: function (items) {
var i, result = {}, args = items;
result.visuals = [];
result.shapes = [];
result.cons = [];
if (!items) {
args = this._selectedItems.slice();
} else if (!isArray(items)) {
args = [items];
}
for (i = 0; i < args.length; i++) {
var item = args[i];
if (item instanceof Shape) {
result.shapes.push(item);
result.visuals.push(item.visual);
} else if (item instanceof Connection) {
result.cons.push(item);
result.visuals.push(item.visual);
}
}
return result;
},
_removeItem: function (item, undoable, removedConnections) {
item.select(false);
if (item instanceof Shape) {
this._removeShapeDataItem(item);
this._removeShape(item, undoable, removedConnections);
} else if (item instanceof Connection) {
this._removeConnectionDataItem(item);
this._removeConnection(item, undoable);
}
this.mainLayer.remove(item.visual);
},
_removeShape: function (shape, undoable, removedConnections) {
var i, connection, connector, sources = [], targets = [];
this.toolService._removeHover();
if (undoable) {
this.undoRedoService.addCompositeItem(new DeleteShapeUnit(shape));
}
Utils.remove(this.shapes, shape);
this._shapesQuadTree.remove(shape);
for (i = 0; i < shape.connectors.length; i++) {
connector = shape.connectors[i];
for (var j = 0; j < connector.connections.length; j++) {
connection = connector.connections[j];
if (!removedConnections || !dataviz.inArray(connection, removedConnections)) {
if (connection.sourceConnector == connector) {
sources.push(connection);
} else if (connection.targetConnector == connector) {
targets.push(connection);
}
}
}
}
for (i = 0; i < sources.length; i++) {
sources[i].source(null, undoable);
sources[i].updateModel();
}
for (i = 0; i < targets.length; i++) {
targets[i].target(null, undoable);
targets[i].updateModel();
}
},
_removeConnection: function (connection, undoable) {
if (connection.sourceConnector) {
Utils.remove(connection.sourceConnector.connections, connection);
}
if (connection.targetConnector) {
Utils.remove(connection.targetConnector.connections, connection);
}
if (undoable) {
this.undoRedoService.addCompositeItem(new DeleteConnectionUnit(connection));
}
Utils.remove(this.connections, connection);
},
_removeDataItems: function (items, recursive) {
var item, children, shape, idx;
items = isArray(items) ? items : [items];
while (items.length) {
item = items.shift();
shape = this._dataMap[item.uid];
if (shape) {
this._removeShapeConnections(shape);
this._removeItem(shape, false);
delete this._dataMap[item.uid];
if (recursive && item.hasChildren && item.loaded()) {
children = item.children.data();
for (idx = 0; idx < children.length; idx++) {
items.push(children[idx]);
}
}
}
}
},
_removeShapeConnections: function (shape) {
var connections = shape.connections();
var idx;
if (connections) {
for (idx = 0; idx < connections.length; idx++) {
this._removeItem(connections[idx], false);
}
}
},
_addDataItem: function (dataItem, undoable) {
if (!defined(dataItem)) {
return;
}
var shape = this._dataMap[dataItem.id];
if (shape) {
return shape;
}
var options = deepExtend({}, this.options.shapeDefaults);
options.dataItem = dataItem;
shape = new Shape(options, this);
this.addShape(shape, undoable !== false);
this._dataMap[dataItem.id] = shape;
return shape;
},
_addDataItemByUid: function (dataItem) {
if (!defined(dataItem)) {
return;
}
var shape = this._dataMap[dataItem.uid];
if (shape) {
return shape;
}
var options = deepExtend({}, this.options.shapeDefaults);
options.dataItem = dataItem;
shape = new Shape(options, this);
this.addShape(shape);
this._dataMap[dataItem.uid] = shape;
return shape;
},
_addDataItems: function (items, parent) {
var item, idx, shape, parentShape, connection;
for (idx = 0; idx < items.length; idx++) {
item = items[idx];
shape = this._addDataItemByUid(item);
parentShape = this._addDataItemByUid(parent);
if (parentShape && !this.connected(parentShape, shape)) {
connection = this.connect(parentShape, shape);
}
}
},
_refreshSource: function (e) {
var that = this, node = e.node, action = e.action, items = e.items, options = that.options, idx, dataBound;
if (e.field) {
return;
}
if (action == 'remove') {
this._removeDataItems(e.items, true);
} else {
if ((!action || action === 'itemloaded') && !this._bindingRoots) {
this._bindingRoots = true;
dataBound = true;
}
if (!action && !node) {
that.clear();
}
this._addDataItems(items, node);
for (idx = 0; idx < items.length; idx++) {
items[idx].load();
}
}
if (options.layout && (dataBound || action == 'remove' || action == 'add')) {
that.layout(options.layout);
}
if (dataBound) {
this.trigger('dataBound');
this._bindingRoots = false;
}
},
_mouseDown: function (e) {
var p = this._calculatePosition(e);
if (e.which == 1 && this.toolService.start(p, this._meta(e))) {
this._destroyToolBar();
e.preventDefault();
}
},
_addItem: function (item) {
if (item instanceof Shape) {
this.addShape(item);
} else if (item instanceof Connection) {
this.addConnection(item);
}
},
_mouseUp: function (e) {
var p = this._calculatePosition(e);
if (e.which == 1 && this.toolService.end(p, this._meta(e))) {
this._createToolBar();
e.preventDefault();
}
},
_createToolBar: function () {
var diagram = this.toolService.diagram;
if (!this.singleToolBar && diagram.select().length === 1) {
var element = diagram.select()[0];
if (element && element.options.editable !== false) {
var editable = element.options.editable;
var tools = editable.tools;
if (this._isEditable && tools.length === 0) {
if (element instanceof Shape) {
tools = [
'edit',
'rotateClockwise',
'rotateAnticlockwise'
];
} else if (element instanceof Connection) {
tools = ['edit'];
}
if (editable && editable.remove !== false) {
tools.push('delete');
}
}
if (tools && tools.length) {
var padding = 20;
var point;
this.singleToolBar = new DiagramToolBar(diagram, {
tools: tools,
click: proxy(this._toolBarClick, this),
modal: true
});
var popupWidth = this.singleToolBar._popup.element.outerWidth();
var popupHeight = this.singleToolBar._popup.element.outerHeight();
if (element instanceof Shape) {
var shapeBounds = this.modelToView(element.bounds(ROTATED));
point = Point(shapeBounds.x, shapeBounds.y).minus(Point((popupWidth - shapeBounds.width) / 2, popupHeight + padding));
} else if (element instanceof Connection) {
var connectionBounds = this.modelToView(element.bounds());
point = Point(connectionBounds.x, connectionBounds.y).minus(Point((popupWidth - connectionBounds.width - 20) / 2, popupHeight + padding));
}
if (point) {
if (!this.canvas.translate) {
point = point.minus(Point(this.scroller.scrollLeft, this.scroller.scrollTop));
}
point = this.viewToDocument(point);
point = Point(math.max(point.x, 0), math.max(point.y, 0));
this.singleToolBar.showAt(point);
} else {
this._destroyToolBar();
}
}
}
}
},
_toolBarClick: function (e) {
this.trigger('toolBarClick', e);
this._destroyToolBar();
},
_mouseMove: function (e) {
if (this.pauseMouseHandlers) {
return;
}
var p = this._calculatePosition(e);
if ((e.which === 0 || e.which == 1) && this.toolService.move(p, this._meta(e))) {
e.preventDefault();
}
},
_keydown: function (e) {
if (this.toolService.keyDown(e.keyCode, this._meta(e))) {
e.preventDefault();
}
},
_wheel: function (e) {
var delta = mwDelta(e), p = this._calculatePosition(e), meta = deepExtend(this._meta(e), { delta: delta });
if (this.toolService.wheel(p, meta)) {
e.preventDefault();
}
},
_meta: function (e) {
return {
ctrlKey: e.ctrlKey,
metaKey: e.metaKey,
altKey: e.altKey,
shiftKey: e.shiftKey
};
},
_calculatePosition: function (e) {
var pointEvent = e.pageX === undefined ? e.originalEvent : e, point = new Point(pointEvent.pageX, pointEvent.pageY), offset = this.documentToModel(point);
return offset;
},
_normalizePointZoom: function (point) {
return point.times(1 / this.zoom());
},
_initialize: function () {
this.shapes = [];
this._selectedItems = [];
this.connections = [];
this._dataMap = {};
this._connectionsDataMap = {};
this._inactiveShapeItems = new InactiveItemsCollection();
this._deferredConnectionUpdates = [];
this.undoRedoService = new UndoRedoService({
undone: this._syncHandler,
redone: this._syncHandler
});
this.id = diagram.randomId();
},
_fetchFreshData: function () {
var that = this;
that._dataSource();
if (that._isEditable) {
that._connectionDataSource();
}
if (that.options.autoBind) {
if (that._isEditable) {
this._loadingShapes = true;
this._loadingConnections = true;
that.dataSource.fetch();
that.connectionsDataSource.fetch();
} else {
that.dataSource.fetch();
}
}
},
_dataSource: function () {
if (defined(this.options.connectionsDataSource)) {
this._isEditable = true;
var dsOptions = this.options.dataSource || {};
var ds = isArray(dsOptions) ? { data: dsOptions } : dsOptions;
if (this.dataSource && this._shapesRefreshHandler) {
this.dataSource.unbind('change', this._shapesRefreshHandler).unbind('requestStart', this._shapesRequestStartHandler).unbind('error', this._shapesErrorHandler);
} else {
this._shapesRefreshHandler = proxy(this._refreshShapes, this);
this._shapesRequestStartHandler = proxy(this._shapesRequestStart, this);
this._shapesErrorHandler = proxy(this._error, this);
}
this.dataSource = kendo.data.DataSource.create(ds).bind('change', this._shapesRefreshHandler).bind('requestStart', this._shapesRequestStartHandler).bind('error', this._shapesErrorHandler);
} else {
this._treeDataSource();
this._isEditable = false;
}
},
_connectionDataSource: function () {
var dsOptions = this.options.connectionsDataSource;
if (dsOptions) {
var ds = isArray(dsOptions) ? { data: dsOptions } : dsOptions;
if (this.connectionsDataSource && this._connectionsRefreshHandler) {
this.connectionsDataSource.unbind('change', this._connectionsRefreshHandler).unbind('requestStart', this._connectionsRequestStartHandler).unbind('error', this._connectionsErrorHandler);
} else {
this._connectionsRefreshHandler = proxy(this._refreshConnections, this);
this._connectionsRequestStartHandler = proxy(this._connectionsRequestStart, this);
this._connectionsErrorHandler = proxy(this._connectionsError, this);
}
this.connectionsDataSource = kendo.data.DataSource.create(ds).bind('change', this._connectionsRefreshHandler).bind('requestStart', this._connectionsRequestStartHandler).bind('error', this._connectionsErrorHandler);
}
},
_shapesRequestStart: function (e) {
if (e.type == 'read') {
this._loadingShapes = true;
}
},
_connectionsRequestStart: function (e) {
if (e.type == 'read') {
this._loadingConnections = true;
}
},
_error: function () {
this._loadingShapes = false;
},
_connectionsError: function () {
this._loadingConnections = false;
},
_refreshShapes: function (e) {
if (e.action === 'remove') {
if (this._shouldRefresh()) {
this._removeShapes(e.items);
}
} else if (e.action === 'itemchange') {
if (this._shouldRefresh()) {
this._updateShapes(e.items, e.field);
}
} else if (e.action === 'add') {
this._inactiveShapeItems.add(e.items);
} else if (e.action === 'sync') {
this._syncShapes(e.items);
} else {
this.refresh();
}
},
_shouldRefresh: function () {
return !this._suspended;
},
_suspendModelRefresh: function () {
this._suspended = (this._suspended || 0) + 1;
},
_resumeModelRefresh: function () {
this._suspended = math.max((this._suspended || 0) - 1, 0);
},
refresh: function () {
this._loadingShapes = false;
if (!this._loadingConnections) {
this._rebindShapesAndConnections();
}
},
_rebindShapesAndConnections: function () {
this.clear();
this._addShapes(this.dataSource.view());
if (this.connectionsDataSource) {
this._addConnections(this.connectionsDataSource.view(), false);
}
if (this.options.layout) {
this.layout(this.options.layout);
} else {
this._redrawConnections();
}
this.trigger('dataBound');
},
refreshConnections: function () {
this._loadingConnections = false;
if (!this._loadingShapes) {
this._rebindShapesAndConnections();
}
},
_redrawConnections: function () {
var connections = this.connections;
for (var idx = 0; idx < connections.length; idx++) {
connections[idx].refresh();
}
},
_removeShapes: function (items) {
var dataMap = this._dataMap;
var item, i;
for (i = 0; i < items.length; i++) {
item = items[i];
if (dataMap[item.id]) {
this.remove(dataMap[item.id], false);
dataMap[item.id] = null;
}
}
},
_syncShapes: function () {
var diagram = this;
var inactiveItems = diagram._inactiveShapeItems;
inactiveItems.forEach(function (inactiveItem) {
var dataItem = inactiveItem.dataItem;
var shape = inactiveItem.element;
if (!dataItem.isNew()) {
if (shape) {
shape._setOptionsFromModel();
diagram.addShape(shape, inactiveItem.undoable);
diagram._dataMap[dataItem.id] = shape;
} else {
diagram._addDataItem(dataItem);
}
inactiveItem.activate();
inactiveItems.remove(dataItem);
}
});
},
_updateShapes: function (items, field) {
for (var i = 0; i < items.length; i++) {
var dataItem = items[i];
var shape = this._dataMap[dataItem.id];
if (shape) {
shape.updateOptionsFromModel(dataItem, field);
}
}
},
_addShapes: function (dataItems) {
for (var i = 0; i < dataItems.length; i++) {
this._addDataItem(dataItems[i], false);
}
},
_refreshConnections: function (e) {
if (e.action === 'remove') {
if (this._shouldRefresh()) {
this._removeConnections(e.items);
}
} else if (e.action === 'add') {
this._addConnections(e.items);
} else if (e.action === 'sync') {
} else if (e.action === 'itemchange') {
if (this._shouldRefresh()) {
this._updateConnections(e.items);
}
} else {
this.refreshConnections();
}
},
_removeConnections: function (items) {
for (var i = 0; i < items.length; i++) {
this.remove(this._connectionsDataMap[items[i].uid], false);
this._connectionsDataMap[items[i].uid] = null;
}
},
_updateConnections: function (items) {
for (var i = 0; i < items.length; i++) {
var dataItem = items[i];
var connection = this._connectionsDataMap[dataItem.uid];
connection.updateOptionsFromModel(dataItem);
}
},
_addConnections: function (connections, undoable) {
var length = connections.length;
for (var i = 0; i < length; i++) {
var dataItem = connections[i];
this._addConnectionDataItem(dataItem, undoable);
}
},
_addConnectionDataItem: function (dataItem, undoable) {
if (!this._connectionsDataMap[dataItem.uid]) {
var from = this._validateConnector(dataItem.from);
if (!defined(from) || from === null) {
from = new Point(dataItem.fromX, dataItem.fromY);
}
var to = this._validateConnector(dataItem.to);
if (!defined(to) || to === null) {
to = new Point(dataItem.toX, dataItem.toY);
}
if (defined(from) && defined(to)) {
var options = deepExtend({}, this.options.connectionDefaults);
options.dataItem = dataItem;
var connection = new Connection(from, to, options);
this._connectionsDataMap[dataItem.uid] = connection;
this.addConnection(connection, undoable);
}
}
},
_validateConnector: function (value) {
var connector;
if (defined(value) && value !== null) {
connector = this._dataMap[value];
}
return connector;
},
_treeDataSource: function () {
var that = this, options = that.options, dataSource = options.dataSource;
dataSource = isArray(dataSource) ? { data: dataSource } : dataSource;
if (!dataSource.fields) {
dataSource.fields = [
{ field: 'text' },
{ field: 'url' },
{ field: 'spriteCssClass' },
{ field: 'imageUrl' }
];
}
if (that.dataSource && that._refreshHandler) {
that._unbindDataSource();
}
that._refreshHandler = proxy(that._refreshSource, that);
that._errorHandler = proxy(that._error, that);
that.dataSource = HierarchicalDataSource.create(dataSource).bind(CHANGE, that._refreshHandler).bind(ERROR, that._errorHandler);
},
_unbindDataSource: function () {
var that = this;
that.dataSource.unbind(CHANGE, that._refreshHandler).unbind(ERROR, that._errorHandler);
},
_adorn: function (adorner, isActive) {
if (isActive !== undefined && adorner) {
if (isActive) {
this._adorners.push(adorner);
this.adornerLayer.append(adorner.visual);
} else {
Utils.remove(this._adorners, adorner);
this.adornerLayer.remove(adorner.visual);
}
}
},
_showConnectors: function (shape, value) {
if (value) {
this._connectorsAdorner.show(shape);
} else {
this._connectorsAdorner.destroy();
}
},
_updateAdorners: function () {
var adorners = this._adorners;
for (var i = 0; i < adorners.length; i++) {
var adorner = adorners[i];
if (adorner.refreshBounds) {
adorner.refreshBounds();
}
adorner.refresh();
}
},
_refresh: function () {
for (var i = 0; i < this.connections.length; i++) {
this.connections[i].refresh();
}
},
_destroyToolBar: function () {
if (this.singleToolBar) {
this.singleToolBar.hide();
this.singleToolBar.destroy();
this.singleToolBar = null;
}
},
_destroyGlobalToolBar: function () {
if (this.toolBar) {
this.toolBar.hide();
this.toolBar.destroy();
this.toolBar = null;
}
},
exportDOMVisual: function () {
var viewBox = this.canvas._viewBox;
var scrollOffset = geom.transform().translate(-viewBox.x, -viewBox.y);
var viewRect = new geom.Rect([
0,
0
], [
viewBox.width,
viewBox.height
]);
var clipPath = draw.Path.fromRect(viewRect);
var wrap = new draw.Group({ transform: scrollOffset });
var clipWrap = new draw.Group({ clip: clipPath });
var root = this.canvas.drawingElement.children[0];
clipWrap.append(wrap);
wrap.children.push(root);
return clipWrap;
},
exportVisual: function () {
var scale = geom.transform().scale(1 / this._zoom);
var wrap = new draw.Group({ transform: scale });
var root = this.mainLayer.drawingElement;
wrap.children.push(root);
return wrap;
},
_syncChanges: function () {
this._syncShapeChanges();
this._syncConnectionChanges();
},
_syncShapeChanges: function () {
if (this.dataSource && this._isEditable) {
this.dataSource.sync();
}
},
_syncConnectionChanges: function () {
var that = this;
if (that.connectionsDataSource && that._isEditable) {
$.when.apply($, that._deferredConnectionUpdates).then(function () {
that.connectionsDataSource.sync();
});
that.deferredConnectionUpdates = [];
}
}
});
dataviz.ExportMixin.extend(Diagram.fn, true);
if (kendo.PDFMixin) {
kendo.PDFMixin.extend(Diagram.fn);
}
function filterShapeDataItem(dataItem) {
var result = {};
dataItem = dataItem || {};
if (defined(dataItem.text) && dataItem.text !== null) {
result.text = dataItem.text;
}
if (defined(dataItem.x) && dataItem.x !== null) {
result.x = dataItem.x;
}
if (defined(dataItem.y) && dataItem.y !== null) {
result.y = dataItem.y;
}
if (defined(dataItem.width) && dataItem.width !== null) {
result.width = dataItem.width;
}
if (defined(dataItem.height) && dataItem.height !== null) {
result.height = dataItem.height;
}
if (defined(dataItem.type) && dataItem.type !== null) {
result.type = dataItem.type;
}
return result;
}
function filterConnectionDataItem(dataItem) {
var result = {};
dataItem = dataItem || {};
if (defined(dataItem.text) && dataItem.text !== null) {
result.content = dataItem.text;
}
if (defined(dataItem.type) && dataItem.type !== null) {
result.type = dataItem.type;
}
if (defined(dataItem.from) && dataItem.from !== null) {
result.from = dataItem.from;
}
if (defined(dataItem.fromConnector) && dataItem.fromConnector !== null) {
result.fromConnector = dataItem.fromConnector;
}
if (defined(dataItem.fromX) && dataItem.fromX !== null) {
result.fromX = dataItem.fromX;
}
if (defined(dataItem.fromY) && dataItem.fromY !== null) {
result.fromY = dataItem.fromY;
}
if (defined(dataItem.to) && dataItem.to !== null) {
result.to = dataItem.to;
}
if (defined(dataItem.toConnector) && dataItem.toConnector !== null) {
result.toConnector = dataItem.toConnector;
}
if (defined(dataItem.toX) && dataItem.toX !== null) {
result.toX = dataItem.toX;
}
if (defined(dataItem.toY) && dataItem.toY !== null) {
result.toY = dataItem.toY;
}
return result;
}
var DiagramToolBar = kendo.Observable.extend({
init: function (diagram, options) {
kendo.Observable.fn.init.call(this);
this.diagram = diagram;
this.options = deepExtend({}, this.options, options);
this._tools = [];
this.createToolBar();
this.createTools();
this.appendTools();
if (this.options.modal) {
this.createPopup();
}
this.bind(this.events, options);
},
events: ['click'],
createPopup: function () {
this.container = $('
').append(this.element);
this._popup = this.container.kendoPopup({}).getKendoPopup();
},
appendTools: function () {
for (var i = 0; i < this._tools.length; i++) {
var tool = this._tools[i];
if (tool.buttons && tool.buttons.length || !defined(tool.buttons)) {
this._toolBar.add(tool);
}
}
},
createToolBar: function () {
this.element = $('
');
this._toolBar = this.element.kendoToolBar({
click: proxy(this.click, this),
resizable: false
}).getKendoToolBar();
this.element.css('border', 'none');
},
createTools: function () {
for (var i = 0; i < this.options.tools.length; i++) {
this.createTool(this.options.tools[i]);
}
},
createTool: function (tool) {
if (!isPlainObject(tool)) {
tool = { name: tool };
}
var toolName = tool.name + 'Tool';
if (this[toolName]) {
this[toolName](tool);
} else {
this._tools.push(deepExtend({}, tool, { attributes: this._setAttributes({ action: tool.name }) }));
}
},
showAt: function (point) {
if (this._popup) {
this._popup.open(point.x, point.y);
}
},
hide: function () {
if (this._popup) {
this._popup.close();
}
},
newGroup: function () {
return {
type: 'buttonGroup',
buttons: []
};
},
editTool: function () {
this._tools.push({
spriteCssClass: 'k-icon k-i-pencil',
showText: 'overflow',
type: 'button',
text: 'Edit',
attributes: this._setAttributes({ action: 'edit' })
});
},
deleteTool: function () {
this._tools.push({
spriteCssClass: 'k-icon k-i-close',
showText: 'overflow',
type: 'button',
text: 'Delete',
attributes: this._setAttributes({ action: 'delete' })
});
},
rotateAnticlockwiseTool: function (options) {
this._appendGroup('rotate');
this._rotateGroup.buttons.push({
spriteCssClass: 'k-icon k-i-rotateccw',
showText: 'overflow',
text: 'RotateAnticlockwise',
group: 'rotate',
attributes: this._setAttributes({
action: 'rotateAnticlockwise',
step: options.step
})
});
},
rotateClockwiseTool: function (options) {
this._appendGroup('rotate');
this._rotateGroup.buttons.push({
spriteCssClass: 'k-icon k-i-rotatecw',
attributes: this._setAttributes({
action: 'rotateClockwise',
step: options.step
}),
showText: 'overflow',
text: 'RotateClockwise',
group: 'rotate'
});
},
createShapeTool: function () {
this._appendGroup('create');
this._createGroup.buttons.push({
spriteCssClass: 'k-icon k-i-shape',
showText: 'overflow',
text: 'CreateShape',
group: 'create',
attributes: this._setAttributes({ action: 'createShape' })
});
},
createConnectionTool: function () {
this._appendGroup('create');
this._createGroup.buttons.push({
spriteCssClass: 'k-icon k-i-connector',
showText: 'overflow',
text: 'CreateConnection',
group: 'create',
attributes: this._setAttributes({ action: 'createConnection' })
});
},
undoTool: function () {
this._appendGroup('history');
this._historyGroup.buttons.push({
spriteCssClass: 'k-icon k-i-undo',
showText: 'overflow',
text: 'Undo',
group: 'history',
attributes: this._setAttributes({ action: 'undo' })
});
},
redoTool: function () {
this._appendGroup('history');
this._historyGroup.buttons.push({
spriteCssClass: 'k-icon k-i-redo',
showText: 'overflow',
text: 'Redo',
group: 'history',
attributes: this._setAttributes({ action: 'redo' })
});
},
_appendGroup: function (name) {
var prop = '_' + name + 'Group';
if (!this[prop]) {
this[prop] = this.newGroup();
this._tools.push(this[prop]);
}
},
_setAttributes: function (attributes) {
var attr = {};
if (attributes.action) {
attr[kendo.attr('action')] = attributes.action;
}
if (attributes.step) {
attr[kendo.attr('step')] = attributes.step;
}
return attr;
},
_getAttributes: function (element) {
var attr = {};
var action = element.attr(kendo.attr('action'));
if (action) {
attr.action = action;
}
var step = element.attr(kendo.attr('step'));
if (step) {
attr.step = step;
}
return attr;
},
click: function (e) {
var attributes = this._getAttributes($(e.target));
var action = attributes.action;
if (action && this[action]) {
this[action](attributes);
}
this.trigger('click', this.eventData(action, e.target));
},
eventData: function (action, target) {
var elements = this.selectedElements(), length = elements.length, shapes = [], connections = [], element;
for (var idx = 0; idx < length; idx++) {
element = elements[idx];
if (element instanceof Shape) {
shapes.push(element);
} else {
connections.push(element);
}
}
return {
shapes: shapes,
connections: connections,
action: action,
target: target
};
},
'delete': function () {
var diagram = this.diagram;
var toRemove = diagram._triggerRemove(this.selectedElements());
if (toRemove.length) {
this.diagram.remove(toRemove, true);
this.diagram._syncChanges();
}
},
edit: function () {
this.diagram.edit(this.selectedElements()[0]);
},
rotateClockwise: function (options) {
var angle = parseFloat(options.step || 90);
this._rotate(angle);
},
rotateAnticlockwise: function (options) {
var angle = parseFloat(options.step || 90);
this._rotate(-angle);
},
_rotate: function (angle) {
var adorner = this.diagram._resizingAdorner;
adorner.angle(adorner.angle() + angle);
adorner.rotate();
},
selectedElements: function () {
return this.diagram.select();
},
createShape: function () {
this.diagram.createShape();
},
createConnection: function () {
this.diagram.createConnection();
},
undo: function () {
this.diagram.undo();
},
redo: function () {
this.diagram.redo();
},
destroy: function () {
this.diagram = null;
this.element = null;
this.options = null;
if (this._toolBar) {
this._toolBar.destroy();
}
if (this._popup) {
this._popup.destroy();
}
}
});
var Editor = kendo.Observable.extend({
init: function (element, options) {
kendo.Observable.fn.init.call(this);
this.options = extend(true, {}, this.options, options);
this.element = element;
this.model = this.options.model;
this.fields = this._getFields();
this._initContainer();
this.createEditable();
},
options: { editors: {} },
_initContainer: function () {
this.wrapper = this.element;
},
createEditable: function () {
var options = this.options;
this.editable = new kendo.ui.Editable(this.wrapper, {
fields: this.fields,
target: options.target,
clearContainer: false,
model: this.model
});
},
_isEditable: function (field) {
return this.model.editable && this.model.editable(field);
},
_getFields: function () {
var fields = [];
var modelFields = this.model.fields;
for (var field in modelFields) {
var result = {};
if (this._isEditable(field)) {
var editor = this.options.editors[field];
if (editor) {
result.editor = editor;
}
result.field = field;
fields.push(result);
}
}
return fields;
},
end: function () {
return this.editable.end();
},
destroy: function () {
this.editable.destroy();
this.editable.element.find('[' + kendo.attr('container-for') + ']').empty();
this.model = this.wrapper = this.element = this.columns = this.editable = null;
}
});
var PopupEditor = Editor.extend({
init: function (element, options) {
Editor.fn.init.call(this, element, options);
this.bind(this.events, this.options);
this.open();
},
events: [
'update',
'cancel'
],
options: {
window: {
modal: true,
resizable: false,
draggable: true,
title: 'Edit',
visible: false
}
},
_initContainer: function () {
var that = this;
this.wrapper = $('').attr(kendo.attr('uid'), this.model.uid);
var formContent = '';
if (this.options.template) {
formContent += this._renderTemplate();
this.fields = [];
} else {
formContent += this._renderFields();
}
formContent += this._renderButtons();
this.wrapper.append($('
').append(formContent));
this.window = new kendo.ui.Window(this.wrapper.appendTo(this.element), this.options.window);
this.window.bind('close', function (e) {
if (e.userTriggered) {
e.sender.element.focus();
that._cancelClick(e);
}
});
this._attachButtonEvents();
},
_renderTemplate: function () {
var template = this.options.template;
if (typeof template === 'string') {
template = window.unescape(template);
}
template = kendo.template(template)(this.model);
return template;
},
_renderFields: function () {
var form = '';
for (var i = 0; i < this.fields.length; i++) {
var field = this.fields[i];
form += '
';
if (this._isEditable(field.field)) {
form += '
';
}
}
return form;
},
_renderButtons: function () {
var form = '
';
form += this._createButton('update');
form += this._createButton('cancel');
form += '
';
return form;
},
_createButton: function (name) {
return kendo.template(BUTTON_TEMPLATE)(defaultButtons[name]);
},
_attachButtonEvents: function () {
this._cancelClickHandler = proxy(this._cancelClick, this);
this.window.element.on(CLICK + NS, 'a.k-diagram-cancel', this._cancelClickHandler);
this._updateClickHandler = proxy(this._updateClick, this);
this.window.element.on(CLICK + NS, 'a.k-diagram-update', this._updateClickHandler);
},
_updateClick: function (e) {
e.preventDefault();
this.trigger('update');
},
_cancelClick: function (e) {
e.preventDefault();
this.trigger('cancel');
},
open: function () {
this.window.center().open();
},
close: function () {
this.window.bind('deactivate', proxy(this.destroy, this)).close();
},
destroy: function () {
this.window.close().destroy();
this.window.element.off(CLICK + NS, 'a.k-diagram-cancel', this._cancelClickHandler);
this.window.element.off(CLICK + NS, 'a.k-diagram-update', this._updateClickHandler);
this._cancelClickHandler = null;
this._editUpdateClickHandler = null;
this.window = null;
Editor.fn.destroy.call(this);
}
});
function connectionSelector(container, options) {
var model = this.dataSource.reader.model;
if (model) {
var textField = model.fn.fields.text ? 'text' : model.idField;
$('
').appendTo(container).kendoDropDownList({
dataValueField: model.idField,
dataTextField: textField,
dataSource: this.dataSource.data().toJSON(),
optionLabel: ' ',
valuePrimitive: true
});
}
}
function InactiveItem(dataItem) {
this.dataItem = dataItem;
this.callbacks = [];
}
InactiveItem.fn = InactiveItem.prototype = {
onActivate: function (callback) {
var deffered = $.Deferred();
this.callbacks.push({
callback: callback,
deferred: deffered
});
return deffered;
},
activate: function () {
var callbacks = this.callbacks;
var item;
for (var idx = 0; idx < callbacks.length; idx++) {
item = this.callbacks[idx];
item.callback(this.dataItem);
item.deferred.resolve();
}
this.callbacks = [];
}
};
function InactiveItemsCollection() {
this.items = {};
}
InactiveItemsCollection.fn = InactiveItemsCollection.prototype = {
add: function (items) {
for (var idx = 0; idx < items.length; idx++) {
this.items[items[idx].uid] = new InactiveItem(items[idx]);
}
},
forEach: function (callback) {
for (var uid in this.items) {
callback(this.items[uid]);
}
},
getByUid: function (uid) {
return this.items[uid];
},
remove: function (item) {
delete this.items[item.uid];
}
};
var QuadRoot = Class.extend({
init: function () {
this.shapes = [];
},
_add: function (shape, bounds) {
this.shapes.push({
bounds: bounds,
shape: shape
});
shape._quadNode = this;
},
insert: function (shape, bounds) {
this._add(shape, bounds);
},
remove: function (shape) {
var shapes = this.shapes;
var length = shapes.length;
for (var idx = 0; idx < length; idx++) {
if (shapes[idx].shape === shape) {
shapes.splice(idx, 1);
break;
}
}
},
hitTestRect: function (rect) {
var shapes = this.shapes;
var length = shapes.length;
for (var i = 0; i < length; i++) {
if (this._testRect(shapes[i].shape, rect)) {
return true;
}
}
},
_testRect: function (shape, rect) {
var angle = shape.rotate().angle;
var bounds = shape.bounds();
var hit;
if (!angle) {
hit = bounds.overlaps(rect);
} else {
hit = Intersect.rects(rect, bounds, -angle);
}
return hit;
}
});
var QuadNode = QuadRoot.extend({
init: function (rect) {
QuadRoot.fn.init.call(this);
this.children = [];
this.rect = rect;
},
inBounds: function (rect) {
var nodeRect = this.rect;
var nodeBottomRight = nodeRect.bottomRight();
var bottomRight = rect.bottomRight();
var inBounds = nodeRect.x <= rect.x && nodeRect.y <= rect.y && bottomRight.x <= nodeBottomRight.x && bottomRight.y <= nodeBottomRight.y;
return inBounds;
},
overlapsBounds: function (rect) {
return this.rect.overlaps(rect);
},
insert: function (shape, bounds) {
var inserted = false;
var children = this.children;
var length = children.length;
if (this.inBounds(bounds)) {
if (!length && this.shapes.length < 4) {
this._add(shape, bounds);
} else {
if (!length) {
this._initChildren();
}
for (var idx = 0; idx < children.length; idx++) {
if (children[idx].insert(shape, bounds)) {
inserted = true;
break;
}
}
if (!inserted) {
this._add(shape, bounds);
}
}
inserted = true;
}
return inserted;
},
_initChildren: function () {
var rect = this.rect, children = this.children, shapes = this.shapes, center = rect.center(), halfWidth = rect.width / 2, halfHeight = rect.height / 2, childIdx, shapeIdx;
children.push(new QuadNode(new Rect(rect.x, rect.y, halfWidth, halfHeight)), new QuadNode(new Rect(center.x, rect.y, halfWidth, halfHeight)), new QuadNode(new Rect(rect.x, center.y, halfWidth, halfHeight)), new QuadNode(new Rect(center.x, center.y, halfWidth, halfHeight)));
for (shapeIdx = shapes.length - 1; shapeIdx >= 0; shapeIdx--) {
for (childIdx = 0; childIdx < children.length; childIdx++) {
if (children[childIdx].insert(shapes[shapeIdx].shape, shapes[shapeIdx].bounds)) {
shapes.splice(shapeIdx, 1);
break;
}
}
}
},
hitTestRect: function (rect) {
var idx;
var children = this.children;
var length = children.length;
var hit = false;
if (this.overlapsBounds(rect)) {
if (QuadRoot.fn.hitTestRect.call(this, rect)) {
hit = true;
} else {
for (idx = 0; idx < length; idx++) {
if (children[idx].hitTestRect(rect)) {
hit = true;
break;
}
}
}
}
return hit;
}
});
var ShapesQuadTree = Class.extend({
ROOT_SIZE: 1000,
init: function (diagram) {
var boundsChangeHandler = proxy(this._boundsChange, this);
diagram.bind(ITEMBOUNDSCHANGE, boundsChangeHandler);
diagram.bind(ITEMROTATE, boundsChangeHandler);
this.initRoots();
},
initRoots: function () {
this.rootMap = {};
this.root = new QuadRoot();
},
clear: function () {
this.initRoots();
},
_boundsChange: function (e) {
if (e.item._quadNode) {
e.item._quadNode.remove(e.item);
}
this.insert(e.item);
},
insert: function (shape) {
var bounds = shape.bounds(ROTATED);
var rootSize = this.ROOT_SIZE;
var sectors = this.getSectors(bounds);
var x = sectors[0][0];
var y = sectors[1][0];
if (this.inRoot(sectors)) {
this.root.insert(shape, bounds);
} else {
if (!this.rootMap[x]) {
this.rootMap[x] = {};
}
if (!this.rootMap[x][y]) {
this.rootMap[x][y] = new QuadNode(new Rect(x * rootSize, y * rootSize, rootSize, rootSize));
}
this.rootMap[x][y].insert(shape, bounds);
}
},
remove: function (shape) {
if (shape._quadNode) {
shape._quadNode.remove(shape);
}
},
inRoot: function (sectors) {
return sectors[0].length > 1 || sectors[1].length > 1;
},
getSectors: function (rect) {
var rootSize = this.ROOT_SIZE;
var bottomRight = rect.bottomRight();
var bottomX = math.floor(bottomRight.x / rootSize);
var bottomY = math.floor(bottomRight.y / rootSize);
var sectors = [
[],
[]
];
for (var x = math.floor(rect.x / rootSize); x <= bottomX; x++) {
sectors[0].push(x);
}
for (var y = math.floor(rect.y / rootSize); y <= bottomY; y++) {
sectors[1].push(y);
}
return sectors;
},
hitTestRect: function (rect) {
var sectors = this.getSectors(rect);
var xIdx, yIdx, x, y;
var root;
if (this.root.hitTestRect(rect)) {
return true;
}
for (xIdx = 0; xIdx < sectors[0].length; xIdx++) {
x = sectors[0][xIdx];
for (yIdx = 0; yIdx < sectors[1].length; yIdx++) {
y = sectors[1][yIdx];
root = (this.rootMap[x] || {})[y];
if (root && root.hitTestRect(rect)) {
return true;
}
}
}
return false;
}
});
function cloneDataItem(dataItem) {
var result = dataItem;
if (dataItem instanceof kendo.data.Model) {
result = dataItem.toJSON();
result[dataItem.idField] = dataItem._defaultId;
}
return result;
}
function splitDiagramElements(elements) {
var connections = [];
var shapes = [];
var element, idx;
for (idx = 0; idx < elements.length; idx++) {
element = elements[idx];
if (element instanceof Shape) {
shapes.push(element);
} else {
connections.push(element);
}
}
return {
shapes: shapes,
connections: connections
};
}
function createModel(dataSource, model) {
if (dataSource.reader.model) {
return new dataSource.reader.model(model);
}
return new kendo.data.ObservableObject(model);
}
function clearField(field, model) {
if (defined(model[field])) {
model.set(field, null);
}
}
function copyDefaultOptions(mainOptions, elementOptions, fields) {
var field;
for (var idx = 0; idx < fields.length; idx++) {
field = fields[idx];
if (elementOptions && !defined(elementOptions[field])) {
elementOptions[field] = mainOptions[field];
}
}
}
function translateToOrigin(visual) {
var bbox = visual.drawingContainer().clippedBBox(null);
if (bbox.origin.x !== 0 || bbox.origin.y !== 0) {
visual.position(-bbox.origin.x, -bbox.origin.y);
}
}
dataviz.ui.plugin(Diagram);
deepExtend(diagram, {
Shape: Shape,
Connection: Connection,
Connector: Connector,
DiagramToolBar: DiagramToolBar,
QuadNode: QuadNode,
QuadRoot: QuadRoot,
ShapesQuadTree: ShapesQuadTree,
PopupEditor: PopupEditor
});
}(window.kendo.jQuery));
}, typeof define == 'function' && define.amd ? define : function (a1, a2, a3) {
(a3 || a2)();
}));
(function (f, define) {
define('kendo.dataviz.diagram', [
'kendo.data',
'kendo.draganddrop',
'kendo.userevents',
'kendo.mobile.scroller',
'kendo.drawing',
'dataviz/diagram/utils',
'dataviz/diagram/math',
'dataviz/diagram/svg',
'dataviz/diagram/services',
'dataviz/diagram/layout',
'dataviz/diagram/dom'
], f);
}(function () {
var __meta__ = {
id: 'dataviz.diagram',
name: 'Diagram',
category: 'dataviz',
description: 'The Kendo DataViz Diagram ',
depends: [
'data',
'userevents',
'mobile.scroller',
'draganddrop',
'drawing',
'dataviz.core',
'dataviz.themes',
'toolbar'
],
features: [
{
id: 'dataviz.diagram-pdf-export',
name: 'PDF export',
description: 'Export Diagram as PDF',
depends: ['pdf']
},
{
id: 'dataviz.diagram-editing',
name: 'Editing',
description: 'Support for model editing',
depends: [
'editable',
'window',
'dropdownlist'
]
}
]
};
}, typeof define == 'function' && define.amd ? define : function (a1, a2, a3) {
(a3 || a2)();
}));
(function (f, define) {
define('kendo.dataviz.treemap', [
'kendo.data',
'kendo.userevents',
'kendo.dataviz.themes'
], f);
}(function () {
var __meta__ = {
id: 'dataviz.treeMap',
name: 'TreeMap',
category: 'dataviz',
description: 'The Kendo DataViz TreeMap',
depends: [
'data',
'userevents',
'dataviz.themes'
]
};
(function ($, undefined) {
var math = Math, proxy = $.proxy, isArray = $.isArray, kendo = window.kendo, Class = kendo.Class, Widget = kendo.ui.Widget, template = kendo.template, deepExtend = kendo.deepExtend, HierarchicalDataSource = kendo.data.HierarchicalDataSource, getter = kendo.getter, dataviz = kendo.dataviz;
var NS = '.kendoTreeMap', CHANGE = 'change', DATA_BOUND = 'dataBound', ITEM_CREATED = 'itemCreated', MAX_VALUE = Number.MAX_VALUE, MOUSEOVER_NS = 'mouseover' + NS, MOUSELEAVE_NS = 'mouseleave' + NS, UNDEFINED = 'undefined';
var TreeMap = Widget.extend({
init: function (element, options) {
kendo.destroy(element);
$(element).empty();
Widget.fn.init.call(this, element, options);
this.wrapper = this.element;
this._initTheme(this.options);
this.element.addClass('k-widget k-treemap');
this._setLayout();
this._originalOptions = deepExtend({}, this.options);
this._initDataSource();
this._attachEvents();
kendo.notify(this, dataviz.ui);
},
options: {
name: 'TreeMap',
theme: 'default',
autoBind: true,
textField: 'text',
valueField: 'value',
colorField: 'color'
},
events: [
DATA_BOUND,
ITEM_CREATED
],
_initTheme: function (options) {
var that = this, themes = dataviz.ui.themes || {}, themeName = ((options || {}).theme || '').toLowerCase(), themeOptions = (themes[themeName] || {}).treeMap;
that.options = deepExtend({}, themeOptions, options);
},
_attachEvents: function () {
this.element.on(MOUSEOVER_NS, proxy(this._mouseover, this)).on(MOUSELEAVE_NS, proxy(this._mouseleave, this));
this._resizeHandler = proxy(this.resize, this, false);
kendo.onResize(this._resizeHandler);
},
_setLayout: function () {
if (this.options.type === 'horizontal') {
this._layout = new SliceAndDice(false);
this._view = new SliceAndDiceView(this, this.options);
} else if (this.options.type === 'vertical') {
this._layout = new SliceAndDice(true);
this._view = new SliceAndDiceView(this, this.options);
} else {
this._layout = new Squarified();
this._view = new SquarifiedView(this, this.options);
}
},
_initDataSource: function () {
var that = this, options = that.options, dataSource = options.dataSource;
that._dataChangeHandler = proxy(that._onDataChange, that);
that.dataSource = HierarchicalDataSource.create(dataSource).bind(CHANGE, that._dataChangeHandler);
if (dataSource) {
if (that.options.autoBind) {
that.dataSource.fetch();
}
}
},
setDataSource: function (dataSource) {
var that = this;
that.dataSource.unbind(CHANGE, that._dataChangeHandler);
that.dataSource = dataSource.bind(CHANGE, that._dataChangeHandler);
if (dataSource) {
if (that.options.autoBind) {
that.dataSource.fetch();
}
}
},
_onDataChange: function (e) {
var node = e.node;
var items = e.items;
var options = this.options;
var item, i;
if (!node) {
this._cleanItems();
this.element.empty();
item = this._wrapItem(items[0]);
this._layout.createRoot(item, this.element.outerWidth(), this.element.outerHeight(), this.options.type === 'vertical');
this._view.createRoot(item);
this._root = item;
this._colorIdx = 0;
} else {
if (items.length) {
var root = this._getByUid(node.uid);
root.children = [];
items = new kendo.data.Query(items)._sortForGrouping(options.valueField, 'desc');
for (i = 0; i < items.length; i++) {
item = items[i];
root.children.push(this._wrapItem(item));
}
var htmlSize = this._view.htmlSize(root);
this._layout.compute(root.children, root.coord, htmlSize);
this._setColors(root.children);
this._view.render(root);
}
}
for (i = 0; i < items.length; i++) {
items[i].load();
}
if (node) {
this.trigger(DATA_BOUND, { node: node });
}
},
_cleanItems: function () {
var that = this;
that.angular('cleanup', function () {
return { elements: that.element.find('.k-leaf div,.k-treemap-title,.k-treemap-title-vertical') };
});
},
_setColors: function (items) {
var colors = this.options.colors;
var colorIdx = this._colorIdx;
var color = colors[colorIdx % colors.length];
var colorRange, item;
if (isArray(color)) {
colorRange = colorsByLength(color[0], color[1], items.length);
}
var leafNodes = false;
for (var i = 0; i < items.length; i++) {
item = items[i];
if (!defined(item.color)) {
if (colorRange) {
item.color = colorRange[i];
} else {
item.color = color;
}
}
if (!item.dataItem.hasChildren) {
leafNodes = true;
}
}
if (leafNodes) {
this._colorIdx++;
}
},
_contentSize: function (root) {
this.view.renderHeight(root);
},
_wrapItem: function (item) {
var wrap = {};
if (defined(this.options.valueField)) {
wrap.value = getField(this.options.valueField, item);
}
if (defined(this.options.colorField)) {
wrap.color = getField(this.options.colorField, item);
}
if (defined(this.options.textField)) {
wrap.text = getField(this.options.textField, item);
}
wrap.level = item.level();
wrap.dataItem = item;
return wrap;
},
_getByUid: function (uid) {
var items = [this._root];
var item;
while (items.length) {
item = items.pop();
if (item.dataItem.uid === uid) {
return item;
}
if (item.children) {
items = items.concat(item.children);
}
}
},
dataItem: function (node) {
var uid = $(node).attr(kendo.attr('uid')), dataSource = this.dataSource;
return dataSource && dataSource.getByUid(uid);
},
findByUid: function (uid) {
return this.element.find('.k-treemap-tile[' + kendo.attr('uid') + '=\'' + uid + '\']');
},
_mouseover: function (e) {
var target = $(e.target);
if (target.hasClass('k-leaf')) {
this._removeActiveState();
target.removeClass('k-state-hover').addClass('k-state-hover');
}
},
_removeActiveState: function () {
this.element.find('.k-state-hover').removeClass('k-state-hover');
},
_mouseleave: function () {
this._removeActiveState();
},
destroy: function () {
Widget.fn.destroy.call(this);
this.element.off(NS);
if (this.dataSource) {
this.dataSource.unbind(CHANGE, this._dataChangeHandler);
}
this._root = null;
kendo.unbindResize(this._resizeHandler);
kendo.destroy(this.element);
},
items: function () {
return $();
},
getSize: function () {
return kendo.dimensions(this.element);
},
_resize: function () {
var root = this._root;
if (root) {
var element = this.element;
var rootElement = element.children();
root.coord.width = element.outerWidth();
root.coord.height = element.outerHeight();
rootElement.css({
width: root.coord.width,
height: root.coord.height
});
this._resizeItems(root, rootElement);
}
},
_resizeItems: function (root, element) {
if (root.children && root.children.length) {
var elements = element.children('.k-treemap-wrap').children();
var child, childElement;
this._layout.compute(root.children, root.coord, { text: this._view.titleSize(root, element) });
for (var idx = 0; idx < root.children.length; idx++) {
child = root.children[idx];
childElement = elements.filter('[' + kendo.attr('uid') + '=\'' + child.dataItem.uid + '\']');
this._view.setItemSize(child, childElement);
this._resizeItems(child, childElement);
}
}
},
setOptions: function (options) {
var dataSource = options.dataSource;
options.dataSource = undefined;
this._originalOptions = deepExtend(this._originalOptions, options);
this.options = deepExtend({}, this._originalOptions);
this._setLayout();
this._initTheme(this.options);
Widget.fn._setEvents.call(this, options);
if (dataSource) {
this.setDataSource(HierarchicalDataSource.create(dataSource));
}
if (this.options.autoBind) {
this.dataSource.fetch();
}
}
});
var Squarified = Class.extend({
createRoot: function (root, width, height) {
root.coord = {
width: width,
height: height,
top: 0,
left: 0
};
},
leaf: function (tree) {
return !tree.children;
},
layoutChildren: function (items, coord) {
var parentArea = coord.width * coord.height;
var totalArea = 0, itemsArea = [], i;
for (i = 0; i < items.length; i++) {
itemsArea[i] = parseFloat(items[i].value);
totalArea += itemsArea[i];
}
for (i = 0; i < itemsArea.length; i++) {
items[i].area = parentArea * itemsArea[i] / totalArea;
}
var minimumSideValue = this.layoutHorizontal() ? coord.height : coord.width;
var firstElement = [items[0]];
var tail = items.slice(1);
this.squarify(tail, firstElement, minimumSideValue, coord);
},
squarify: function (tail, initElement, width, coord) {
this.computeDim(tail, initElement, width, coord);
},
computeDim: function (tail, initElement, width, coord) {
if (tail.length + initElement.length == 1) {
var element = tail.length == 1 ? tail : initElement;
this.layoutLast(element, width, coord);
return;
}
if (tail.length >= 2 && initElement.length === 0) {
initElement = [tail[0]];
tail = tail.slice(1);
}
if (tail.length === 0) {
if (initElement.length > 0) {
this.layoutRow(initElement, width, coord);
}
return;
}
var firstElement = tail[0];
if (this.worstAspectRatio(initElement, width) >= this.worstAspectRatio([firstElement].concat(initElement), width)) {
this.computeDim(tail.slice(1), initElement.concat([firstElement]), width, coord);
} else {
var newCoords = this.layoutRow(initElement, width, coord);
this.computeDim(tail, [], newCoords.dim, newCoords);
}
},
layoutLast: function (items, w, coord) {
items[0].coord = coord;
},
layoutRow: function (items, width, coord) {
if (this.layoutHorizontal()) {
return this.layoutV(items, width, coord);
} else {
return this.layoutH(items, width, coord);
}
},
orientation: 'h',
layoutVertical: function () {
return this.orientation === 'v';
},
layoutHorizontal: function () {
return this.orientation === 'h';
},
layoutChange: function () {
this.orientation = this.layoutVertical() ? 'h' : 'v';
},
worstAspectRatio: function (items, width) {
if (!items || items.length === 0) {
return MAX_VALUE;
}
var areaSum = 0, maxArea = 0, minArea = MAX_VALUE;
for (var i = 0; i < items.length; i++) {
var area = items[i].area;
areaSum += area;
minArea = minArea < area ? minArea : area;
maxArea = maxArea > area ? maxArea : area;
}
return math.max(width * width * maxArea / (areaSum * areaSum), areaSum * areaSum / (width * width * minArea));
},
compute: function (children, rootCoord, htmlSize) {
if (!(rootCoord.width >= rootCoord.height && this.layoutHorizontal())) {
this.layoutChange();
}
if (children && children.length > 0) {
var newRootCoord = {
width: rootCoord.width,
height: rootCoord.height - htmlSize.text,
top: 0,
left: 0
};
this.layoutChildren(children, newRootCoord);
}
},
layoutV: function (items, width, coord) {
var totalArea = this._totalArea(items), top = 0;
width = round(totalArea / width);
for (var i = 0; i < items.length; i++) {
var height = round(items[i].area / width);
items[i].coord = {
height: height,
width: width,
top: coord.top + top,
left: coord.left
};
top += height;
}
var ans = {
height: coord.height,
width: coord.width - width,
top: coord.top,
left: coord.left + width
};
ans.dim = math.min(ans.width, ans.height);
if (ans.dim != ans.height) {
this.layoutChange();
}
return ans;
},
layoutH: function (items, width, coord) {
var totalArea = this._totalArea(items);
var height = round(totalArea / width), top = coord.top, left = 0;
for (var i = 0; i < items.length; i++) {
items[i].coord = {
height: height,
width: round(items[i].area / height),
top: top,
left: coord.left + left
};
left += items[i].coord.width;
}
var ans = {
height: coord.height - height,
width: coord.width,
top: coord.top + height,
left: coord.left
};
ans.dim = math.min(ans.width, ans.height);
if (ans.dim != ans.width) {
this.layoutChange();
}
return ans;
},
_totalArea: function (items) {
var total = 0;
for (var i = 0; i < items.length; i++) {
total += items[i].area;
}
return total;
}
});
var SquarifiedView = Class.extend({
init: function (treeMap, options) {
this.options = deepExtend({}, this.options, options);
this.treeMap = treeMap;
this.element = $(treeMap.element);
this.offset = 0;
},
titleSize: function (item, element) {
var text = element.children('.k-treemap-title');
return text.height();
},
htmlSize: function (root) {
var rootElement = this._getByUid(root.dataItem.uid);
var htmlSize = { text: 0 };
if (root.children) {
this._clean(rootElement);
var text = this._getText(root);
if (text) {
var title = this._createTitle(root);
rootElement.append(title);
this._compile(title, root.dataItem);
htmlSize.text = title.height();
}
rootElement.append(this._createWrap());
this.offset = (rootElement.outerWidth() - rootElement.innerWidth()) / 2;
}
return htmlSize;
},
_compile: function (element, dataItem) {
this.treeMap.angular('compile', function () {
return {
elements: element,
data: [{ dataItem: dataItem }]
};
});
},
_getByUid: function (uid) {
return this.element.find('.k-treemap-tile[' + kendo.attr('uid') + '=\'' + uid + '\']');
},
render: function (root) {
var rootElement = this._getByUid(root.dataItem.uid);
var children = root.children;
if (children) {
var rootWrap = rootElement.find('.k-treemap-wrap');
for (var i = 0; i < children.length; i++) {
var leaf = children[i];
var htmlElement = this._createLeaf(leaf);
rootWrap.append(htmlElement);
this._compile(htmlElement.children(), leaf.dataItem);
this.treeMap.trigger(ITEM_CREATED, { element: htmlElement });
}
}
},
createRoot: function (root) {
var htmlElement = this._createLeaf(root);
this.element.append(htmlElement);
this._compile(htmlElement.children(), root.dataItem);
this.treeMap.trigger(ITEM_CREATED, { element: htmlElement });
},
_clean: function (root) {
this.treeMap.angular('cleanup', function () {
return { elements: root.children(':not(.k-treemap-wrap)') };
});
root.css('background-color', '');
root.removeClass('k-leaf');
root.removeClass('k-inverse');
root.empty();
},
_createLeaf: function (item) {
return this._createTile(item).css('background-color', item.color).addClass('k-leaf').toggleClass('k-inverse', this._tileColorBrightness(item) > 180).append($('
').html(this._getText(item)));
},
_createTile: function (item) {
var tile = $('
');
this.setItemSize(item, tile);
if (defined(item.dataItem) && defined(item.dataItem.uid)) {
tile.attr(kendo.attr('uid'), item.dataItem.uid);
}
return tile;
},
_itemCoordinates: function (item) {
var coordinates = {
width: item.coord.width,
height: item.coord.height,
left: item.coord.left,
top: item.coord.top
};
if (coordinates.left && this.offset) {
coordinates.width += this.offset * 2;
} else {
coordinates.width += this.offset;
}
if (coordinates.top) {
coordinates.height += this.offset * 2;
} else {
coordinates.height += this.offset;
}
return coordinates;
},
setItemSize: function (item, element) {
var coordinates = this._itemCoordinates(item);
element.css({
width: coordinates.width,
height: coordinates.height,
left: coordinates.left,
top: coordinates.top
});
},
_getText: function (item) {
var text = item.text;
if (this.options.template) {
text = this._renderTemplate(item);
}
return text;
},
_renderTemplate: function (item) {
var titleTemplate = template(this.options.template);
return titleTemplate({
dataItem: item.dataItem,
text: item.text
});
},
_createTitle: function (item) {
return $('
').append($('
').html(this._getText(item)));
},
_createWrap: function () {
return $('
');
},
_tileColorBrightness: function (item) {
return colorBrightness(item.color);
}
});
var SliceAndDice = Class.extend({
createRoot: function (root, width, height, vertical) {
root.coord = {
width: width,
height: height,
top: 0,
left: 0
};
root.vertical = vertical;
},
init: function (vertical) {
this.vertical = vertical;
this.quotient = vertical ? 1 : 0;
},
compute: function (children, rootCoord, htmlSize) {
if (children.length > 0) {
var width = rootCoord.width;
var height = rootCoord.height;
if (this.vertical) {
height -= htmlSize.text;
} else {
width -= htmlSize.text;
}
var newRootCoord = {
width: width,
height: height,
top: 0,
left: 0
};
this.layoutChildren(children, newRootCoord);
}
},
layoutChildren: function (items, coord) {
var parentArea = coord.width * coord.height;
var totalArea = 0;
var itemsArea = [];
var i;
for (i = 0; i < items.length; i++) {
var item = items[i];
itemsArea[i] = parseFloat(items[i].value);
totalArea += itemsArea[i];
item.vertical = this.vertical;
}
for (i = 0; i < itemsArea.length; i++) {
items[i].area = parentArea * itemsArea[i] / totalArea;
}
this.sliceAndDice(items, coord);
},
sliceAndDice: function (items, coord) {
var totalArea = this._totalArea(items);
if (items[0].level % 2 === this.quotient) {
this.layoutHorizontal(items, coord, totalArea);
} else {
this.layoutVertical(items, coord, totalArea);
}
},
layoutHorizontal: function (items, coord, totalArea) {
var left = 0;
for (var i = 0; i < items.length; i++) {
var item = items[i];
var width = item.area / (totalArea / coord.width);
item.coord = {
height: coord.height,
width: width,
top: coord.top,
left: coord.left + left
};
left += width;
}
},
layoutVertical: function (items, coord, totalArea) {
var top = 0;
for (var i = 0; i < items.length; i++) {
var item = items[i];
var height = item.area / (totalArea / coord.height);
item.coord = {
height: height,
width: coord.width,
top: coord.top + top,
left: coord.left
};
top += height;
}
},
_totalArea: function (items) {
var total = 0;
for (var i = 0; i < items.length; i++) {
total += items[i].area;
}
return total;
}
});
var SliceAndDiceView = SquarifiedView.extend({
htmlSize: function (root) {
var rootElement = this._getByUid(root.dataItem.uid);
var htmlSize = {
text: 0,
offset: 0
};
if (root.children) {
this._clean(rootElement);
var text = this._getText(root);
if (text) {
var title = this._createTitle(root);
rootElement.append(title);
this._compile(title, root.dataItem);
if (root.vertical) {
htmlSize.text = title.height();
} else {
htmlSize.text = title.width();
}
}
rootElement.append(this._createWrap());
this.offset = (rootElement.outerWidth() - rootElement.innerWidth()) / 2;
}
return htmlSize;
},
titleSize: function (item, element) {
var size;
if (item.vertical) {
size = element.children('.k-treemap-title').height();
} else {
size = element.children('.k-treemap-title-vertical').width();
}
return size;
},
_createTitle: function (item) {
var title;
if (item.vertical) {
title = $('
');
} else {
title = $('
');
}
return title.append($('
').html(this._getText(item)));
}
});
function getField(field, row) {
if (row === null) {
return row;
}
var get = getter(field, true);
return get(row);
}
function defined(value) {
return typeof value !== UNDEFINED;
}
function colorsByLength(min, max, length) {
var minRGBtoDecimal = rgbToDecimal(min);
var maxRGBtoDecimal = rgbToDecimal(max);
var isDarker = colorBrightness(min) - colorBrightness(max) < 0;
var colors = [];
colors.push(min);
for (var i = 0; i < length; i++) {
var rgbColor = {
r: colorByIndex(minRGBtoDecimal.r, maxRGBtoDecimal.r, i, length, isDarker),
g: colorByIndex(minRGBtoDecimal.g, maxRGBtoDecimal.g, i, length, isDarker),
b: colorByIndex(minRGBtoDecimal.b, maxRGBtoDecimal.b, i, length, isDarker)
};
colors.push(buildColorFromRGB(rgbColor));
}
colors.push(max);
return colors;
}
function colorByIndex(min, max, index, length, isDarker) {
var minColor = math.min(math.abs(min), math.abs(max));
var maxColor = math.max(math.abs(min), math.abs(max));
var step = (maxColor - minColor) / (length + 1);
var currentStep = step * (index + 1);
var color;
if (isDarker) {
color = minColor + currentStep;
} else {
color = maxColor - currentStep;
}
return color;
}
function buildColorFromRGB(color) {
return '#' + decimalToRgb(color.r) + decimalToRgb(color.g) + decimalToRgb(color.b);
}
function rgbToDecimal(color) {
color = color.replace('#', '');
var rgbColor = colorToRGB(color);
return {
r: rgbToHex(rgbColor.r),
g: rgbToHex(rgbColor.g),
b: rgbToHex(rgbColor.b)
};
}
function decimalToRgb(number) {
var result = math.round(number).toString(16).toUpperCase();
if (result.length === 1) {
result = '0' + result;
}
return result;
}
function colorToRGB(color) {
var colorLength = color.length;
var rgbColor = {};
if (colorLength === 3) {
rgbColor.r = color[0];
rgbColor.g = color[1];
rgbColor.b = color[2];
} else {
rgbColor.r = color.substring(0, 2);
rgbColor.g = color.substring(2, 4);
rgbColor.b = color.substring(4, 6);
}
return rgbColor;
}
function rgbToHex(rgb) {
return parseInt(rgb.toString(16), 16);
}
function colorBrightness(color) {
var brightness = 0;
if (color) {
color = rgbToDecimal(color);
brightness = math.sqrt(0.241 * color.r * color.r + 0.691 * color.g * color.g + 0.068 * color.b * color.b);
}
return brightness;
}
function round(value) {
var power = math.pow(10, 4);
return math.round(value * power) / power;
}
dataviz.ui.plugin(TreeMap);
}(window.kendo.jQuery));
return window.kendo;
}, typeof define == 'function' && define.amd ? define : function (a1, a2, a3) {
(a3 || a2)();
}));
(function (f, define) {
define('kendo.dataviz', [
'kendo.core',
'kendo.fx',
'kendo.router',
'kendo.view',
'kendo.data.odata',
'kendo.data.xml',
'kendo.data',
'kendo.data.signalr',
'kendo.binder',
'kendo.userevents',
'kendo.draganddrop',
'kendo.mobile.scroller',
'kendo.popup',
'kendo.tooltip',
'kendo.drawing',
'kendo.dataviz.core',
'kendo.dataviz.themes',
'kendo.dataviz.chart',
'kendo.dataviz.chart.polar',
'kendo.dataviz.chart.funnel',
'kendo.dataviz.gauge',
'kendo.dataviz.barcode',
'kendo.dataviz.qrcode',
'kendo.dataviz.stock',
'kendo.dataviz.sparkline',
'kendo.dataviz.map',
'kendo.dataviz.diagram',
'kendo.dataviz.treemap',
'kendo.angular',
'kendo.webcomponents',
'kendo.angular2'
], f);
}(function () {
'bundle all';
return window.kendo;
}, typeof define == 'function' && define.amd ? define : function (a1, a2, a3) {
(a3 || a2)();
}));
(function (f, define) {
define('kendo.mobile.modalview', [
'kendo.mobile.shim',
'kendo.mobile.view'
], f);
}(function () {
var __meta__ = {
id: 'mobile.modalview',
name: 'ModalView',
category: 'mobile',
description: 'The Kendo ModalView is used to present self-contained functionality in the context of the current task.',
depends: [
'mobile.shim',
'mobile.view'
]
};
(function ($, undefined) {
var kendo = window.kendo, ui = kendo.mobile.ui, Shim = ui.Shim, Widget = ui.Widget, BEFORE_OPEN = 'beforeOpen', OPEN = 'open', CLOSE = 'close', INIT = 'init', WRAP = '
';
var ModalView = ui.View.extend({
init: function (element, options) {
var that = this;
Widget.fn.init.call(that, element, options);
that._id();
that._wrap();
that._shim();
if (!this.options.$angular) {
that._layout();
that._scroller();
that._model();
}
that.element.css('display', '');
that.trigger(INIT);
},
events: [
INIT,
BEFORE_OPEN,
OPEN,
CLOSE
],
options: {
name: 'ModalView',
modal: true,
width: null,
height: null
},
destroy: function () {
Widget.fn.destroy.call(this);
this.shim.destroy();
},
open: function (target) {
var that = this;
that.target = $(target);
that.shim.show();
that._invokeNgController();
that.trigger('show', { view: that });
},
openFor: function (target) {
if (!this.trigger(BEFORE_OPEN, { target: target })) {
this.open(target);
this.trigger(OPEN, { target: target });
}
},
close: function () {
if (this.element.is(':visible') && !this.trigger(CLOSE)) {
this.shim.hide();
}
},
_wrap: function () {
var that = this, element = that.element, options = that.options, width, height;
width = element[0].style.width || 'auto';
height = element[0].style.height || 'auto';
element.addClass('km-modalview').wrap(WRAP);
that.wrapper = element.parent().css({
width: options.width || width || 300,
height: options.height || height || 300
}).addClass(height == 'auto' ? ' km-auto-height' : '');
element.css({
width: '',
height: ''
});
},
_shim: function () {
var that = this;
that.shim = new Shim(that.wrapper, {
modal: that.options.modal,
position: 'center center',
align: 'center center',
effect: 'fade:in',
className: 'km-modalview-root',
hide: function (e) {
if (that.trigger(CLOSE)) {
e.preventDefault();
}
}
});
}
});
ui.plugin(ModalView);
}(window.kendo.jQuery));
return window.kendo;
}, typeof define == 'function' && define.amd ? define : function (a1, a2, a3) {
(a3 || a2)();
}));
(function (f, define) {
define('kendo.mobile.drawer', [
'kendo.mobile.view',
'kendo.userevents'
], f);
}(function () {
var __meta__ = {
id: 'mobile.drawer',
name: 'Drawer',
category: 'mobile',
description: 'The Kendo Mobile Drawer widget provides slide to reveal global application toolbox',
depends: [
'mobile.view',
'userevents'
]
};
(function ($, undefined) {
var kendo = window.kendo, mobile = kendo.mobile, os = kendo.support.mobileOS, Transition = kendo.effects.Transition, roleSelector = kendo.roleSelector, AXIS = 'x', ui = mobile.ui, SWIPE_TO_OPEN = !(os.ios && os.majorVersion == 7 && !os.appMode), BEFORE_SHOW = 'beforeShow', INIT = 'init', SHOW = 'show', HIDE = 'hide', AFTER_HIDE = 'afterHide', NULL_VIEW = { enable: $.noop };
var Drawer = ui.View.extend({
init: function (element, options) {
$(element).parent().prepend(element);
mobile.ui.Widget.fn.init.call(this, element, options);
if (!this.options.$angular) {
this._layout();
this._scroller();
}
this._model();
var pane = this.element.closest(roleSelector('pane')).data('kendoMobilePane'), userEvents;
if (pane) {
this.pane = pane;
this.pane.bind('viewShow', function (e) {
drawer._viewShow(e);
});
this.pane.bind('sameViewRequested', function () {
drawer.hide();
});
userEvents = this.userEvents = new kendo.UserEvents(pane.element, {
fastTap: true,
filter: roleSelector('view splitview'),
allowSelection: true
});
} else {
this.currentView = NULL_VIEW;
var container = $(this.options.container);
if (!container) {
throw new Error('The drawer needs a container configuration option set.');
}
userEvents = this.userEvents = new kendo.UserEvents(container, {
fastTap: true,
allowSelection: true
});
this._attachTransition(container);
}
var drawer = this;
var hide = function (e) {
if (drawer.visible) {
drawer.hide();
e.preventDefault();
}
};
if (this.options.swipeToOpen && SWIPE_TO_OPEN) {
userEvents.bind('press', function () {
drawer.transition.cancel();
});
userEvents.bind('start', function (e) {
drawer._start(e);
});
userEvents.bind('move', function (e) {
drawer._update(e);
});
userEvents.bind('end', function (e) {
drawer._end(e);
});
userEvents.bind('tap', hide);
} else {
userEvents.bind('press', hide);
}
this.leftPositioned = this.options.position === 'left';
this.visible = false;
this.element.hide().addClass('km-drawer').addClass(this.leftPositioned ? 'km-left-drawer' : 'km-right-drawer');
this.trigger(INIT);
},
options: {
name: 'Drawer',
position: 'left',
views: [],
swipeToOpenViews: [],
swipeToOpen: true,
title: '',
container: null
},
events: [
BEFORE_SHOW,
HIDE,
AFTER_HIDE,
INIT,
SHOW
],
show: function () {
if (this._activate()) {
this._show();
}
},
hide: function () {
if (!this.currentView) {
return;
}
this.currentView.enable();
Drawer.current = null;
this._moveViewTo(0);
this.trigger(HIDE, { view: this });
},
openFor: function () {
if (this.visible) {
this.hide();
} else {
this.show();
}
},
destroy: function () {
ui.View.fn.destroy.call(this);
this.userEvents.destroy();
},
_activate: function () {
if (this.visible) {
return true;
}
var visibleOnCurrentView = this._currentViewIncludedIn(this.options.views);
if (!visibleOnCurrentView || this.trigger(BEFORE_SHOW, { view: this })) {
return false;
}
this._setAsCurrent();
this.element.show();
this.trigger(SHOW, { view: this });
this._invokeNgController();
return true;
},
_currentViewIncludedIn: function (views) {
if (!this.pane || !views.length) {
return true;
}
var view = this.pane.view();
return $.inArray(view.id.replace('#', ''), views) > -1 || $.inArray(view.element.attr('id'), views) > -1;
},
_show: function () {
this.currentView.enable(false);
this.visible = true;
var offset = this.element.width();
if (!this.leftPositioned) {
offset = -offset;
}
this._moveViewTo(offset);
},
_setAsCurrent: function () {
if (Drawer.last !== this) {
if (Drawer.last) {
Drawer.last.element.hide();
}
this.element.show();
}
Drawer.last = this;
Drawer.current = this;
},
_moveViewTo: function (offset) {
this.userEvents.cancel();
this.transition.moveTo({
location: offset,
duration: 400,
ease: Transition.easeOutExpo
});
},
_viewShow: function (e) {
if (this.currentView) {
this.currentView.enable();
}
if (this.currentView === e.view) {
this.hide();
return;
}
this.currentView = e.view;
this._attachTransition(e.view.element);
},
_attachTransition: function (element) {
var that = this, movable = this.movable, currentOffset = movable && movable.x;
if (this.transition) {
this.transition.cancel();
this.movable.moveAxis('x', 0);
}
movable = this.movable = new kendo.ui.Movable(element);
this.transition = new Transition({
axis: AXIS,
movable: this.movable,
onEnd: function () {
if (movable[AXIS] === 0) {
element[0].style.cssText = '';
that.element.hide();
that.trigger(AFTER_HIDE);
that.visible = false;
}
}
});
if (currentOffset) {
element.addClass('k-fx-hidden');
kendo.animationFrame(function () {
element.removeClass('k-fx-hidden');
that.movable.moveAxis(AXIS, currentOffset);
that.hide();
});
}
},
_start: function (e) {
var userEvents = e.sender;
if (Math.abs(e.x.velocity) < Math.abs(e.y.velocity) || kendo.triggeredByInput(e.event) || !this._currentViewIncludedIn(this.options.swipeToOpenViews)) {
userEvents.cancel();
return;
}
var leftPositioned = this.leftPositioned, visible = this.visible, canMoveLeft = leftPositioned && visible || !leftPositioned && !Drawer.current, canMoveRight = !leftPositioned && visible || leftPositioned && !Drawer.current, leftSwipe = e.x.velocity < 0;
if (canMoveLeft && leftSwipe || canMoveRight && !leftSwipe) {
if (this._activate()) {
userEvents.capture();
return;
}
}
userEvents.cancel();
},
_update: function (e) {
var movable = this.movable, newPosition = movable.x + e.x.delta, limitedPosition;
if (this.leftPositioned) {
limitedPosition = Math.min(Math.max(0, newPosition), this.element.width());
} else {
limitedPosition = Math.max(Math.min(0, newPosition), -this.element.width());
}
this.movable.moveAxis(AXIS, limitedPosition);
e.event.preventDefault();
e.event.stopPropagation();
},
_end: function (e) {
var velocity = e.x.velocity, pastHalf = Math.abs(this.movable.x) > this.element.width() / 2, velocityThreshold = 0.8, shouldShow;
if (this.leftPositioned) {
shouldShow = velocity > -velocityThreshold && (velocity > velocityThreshold || pastHalf);
} else {
shouldShow = velocity < velocityThreshold && (velocity < -velocityThreshold || pastHalf);
}
if (shouldShow) {
this._show();
} else {
this.hide();
}
}
});
ui.plugin(Drawer);
}(window.kendo.jQuery));
return window.kendo;
}, typeof define == 'function' && define.amd ? define : function (a1, a2, a3) {
(a3 || a2)();
}));
(function (f, define) {
define('kendo.mobile.splitview', ['kendo.mobile.pane'], f);
}(function () {
var __meta__ = {
id: 'mobile.splitview',
name: 'SplitView',
category: 'mobile',
description: 'The mobile SplitView is a tablet-specific view that consists of two or more mobile Pane widgets.',
depends: ['mobile.pane']
};
(function ($, undefined) {
var kendo = window.kendo, ui = kendo.mobile.ui, Widget = ui.Widget, EXPANED_PANE_SHIM = '
', View = ui.View;
var SplitView = View.extend({
init: function (element, options) {
var that = this, pane, modalViews;
Widget.fn.init.call(that, element, options);
element = that.element;
$.extend(that, options);
that._id();
if (!that.options.$angular) {
that._layout();
that._overlay();
} else {
that._overlay();
}
that._style();
modalViews = element.children(that._locate('modalview'));
if (!that.options.$angular) {
kendo.mobile.init(modalViews);
} else {
modalViews.each(function (idx, element) {
kendo.compileMobileDirective($(element), options.$angular[0]);
});
}
that.panes = [];
that._paramsHistory = [];
if (!that.options.$angular) {
that.content.children(kendo.roleSelector('pane')).each(function () {
pane = kendo.initWidget(this, {}, ui.roles);
that.panes.push(pane);
});
} else {
that.element.children(kendo.directiveSelector('pane')).each(function () {
pane = kendo.compileMobileDirective($(this), options.$angular[0]);
that.panes.push(pane);
});
that.element.children(kendo.directiveSelector('header footer')).each(function () {
kendo.compileMobileDirective($(this), options.$angular[0]);
});
}
that.expandedPaneShim = $(EXPANED_PANE_SHIM).appendTo(that.element);
that._shimUserEvents = new kendo.UserEvents(that.expandedPaneShim, {
fastTap: true,
tap: function () {
that.collapsePanes();
}
});
},
_locate: function (selectors) {
return this.options.$angular ? kendo.directiveSelector(selectors) : kendo.roleSelector(selectors);
},
options: {
name: 'SplitView',
style: 'horizontal'
},
expandPanes: function () {
this.element.addClass('km-expanded-splitview');
},
collapsePanes: function () {
this.element.removeClass('km-expanded-splitview');
},
_layout: function () {
var that = this, element = that.element;
that.transition = kendo.attrValue(element, 'transition');
kendo.mobile.ui.View.prototype._layout.call(this);
kendo.mobile.init(this.header.add(this.footer));
that.element.addClass('km-splitview');
that.content.addClass('km-split-content');
},
_style: function () {
var style = this.options.style, element = this.element, styles;
if (style) {
styles = style.split(' ');
$.each(styles, function () {
element.addClass('km-split-' + this);
});
}
},
showStart: function () {
var that = this;
that.element.css('display', '');
if (!that.inited) {
that.inited = true;
$.each(that.panes, function () {
if (this.options.initial) {
this.navigateToInitial();
} else {
this.navigate('');
}
});
that.trigger('init', { view: that });
} else {
this._invokeNgController();
}
that.trigger('show', { view: that });
}
});
ui.plugin(SplitView);
}(window.kendo.jQuery));
return window.kendo;
}, typeof define == 'function' && define.amd ? define : function (a1, a2, a3) {
(a3 || a2)();
}));
(function (f, define) {
define('kendo.mobile.application', [
'kendo.mobile.pane',
'kendo.router'
], f);
}(function () {
var __meta__ = {
id: 'mobile.application',
name: 'Application',
category: 'mobile',
description: 'The Mobile application provides a framework to build native looking web applications on mobile devices.',
depends: [
'mobile.pane',
'router'
]
};
(function ($, undefined) {
var kendo = window.kendo, mobile = kendo.mobile, support = kendo.support, Widget = mobile.ui.Widget, Pane = mobile.ui.Pane, DEFAULT_OS = 'ios7', OS = support.mobileOS, BERRYPHONEGAP = OS.device == 'blackberry' && OS.flatVersion >= 600 && OS.flatVersion < 1000 && OS.appMode, FONT_SIZE_COEF = 0.93, VERTICAL = 'km-vertical', CHROME = OS.browser === 'chrome', BROKEN_WEBVIEW_RESIZE = OS.ios && OS.flatVersion >= 700 && OS.flatVersion < 800 && (OS.appMode || CHROME), INITIALLY_HORIZONTAL = Math.abs(window.orientation) / 90 == 1, HORIZONTAL = 'km-horizontal', MOBILE_PLATFORMS = {
ios7: {
ios: true,
browser: 'default',
device: 'iphone',
flatVersion: '700',
majorVersion: '7',
minorVersion: '0.0',
name: 'ios',
tablet: false
},
ios: {
ios: true,
browser: 'default',
device: 'iphone',
flatVersion: '612',
majorVersion: '6',
minorVersion: '1.2',
name: 'ios',
tablet: false
},
android: {
android: true,
browser: 'default',
device: 'android',
flatVersion: '442',
majorVersion: '4',
minorVersion: '4.2',
name: 'android',
tablet: false
},
blackberry: {
blackberry: true,
browser: 'default',
device: 'blackberry',
flatVersion: '710',
majorVersion: '7',
minorVersion: '1.0',
name: 'blackberry',
tablet: false
},
meego: {
meego: true,
browser: 'default',
device: 'meego',
flatVersion: '850',
majorVersion: '8',
minorVersion: '5.0',
name: 'meego',
tablet: false
},
wp: {
wp: true,
browser: 'default',
device: 'wp',
flatVersion: '800',
majorVersion: '8',
minorVersion: '0.0',
name: 'wp',
tablet: false
}
}, viewportTemplate = kendo.template('
', { usedWithBlock: false }), systemMeta = kendo.template('
' + '
' + '
', { usedWithBlock: false }), clipTemplate = kendo.template('', { usedWithBlock: false }), ENABLE_CLIP = OS.android && OS.browser != 'chrome' || OS.blackberry, iconMeta = kendo.template('
', { usedWithBlock: false }), HIDEBAR = (OS.device == 'iphone' || OS.device == 'ipod') && OS.majorVersion < 7, SUPPORT_SWIPE_TO_GO_BACK = (OS.device == 'iphone' || OS.device == 'ipod') && OS.majorVersion >= 7, HISTORY_TRANSITION = SUPPORT_SWIPE_TO_GO_BACK ? 'none' : null, BARCOMPENSATION = OS.browser == 'mobilesafari' ? 60 : 0, STATUS_BAR_HEIGHT = 20, WINDOW = $(window), SCREEN = window.screen, HEAD = $('head'), INIT = 'init', proxy = $.proxy;
function osCssClass(os, options) {
var classes = [];
if (OS) {
classes.push('km-on-' + OS.name);
}
if (os.skin) {
classes.push('km-' + os.skin);
} else {
if (os.name == 'ios' && os.majorVersion > 6) {
classes.push('km-ios7');
} else {
classes.push('km-' + os.name);
}
}
if (os.name == 'ios' && os.majorVersion < 7 || os.name != 'ios') {
classes.push('km-' + os.name + os.majorVersion);
}
classes.push('km-' + os.majorVersion);
classes.push('km-m' + (os.minorVersion ? os.minorVersion[0] : 0));
if (os.variant && (os.skin && os.skin === os.name || !os.skin || os.setDefaultPlatform === false)) {
classes.push('km-' + (os.skin ? os.skin : os.name) + '-' + os.variant);
}
if (os.cordova) {
classes.push('km-cordova');
}
if (os.appMode) {
classes.push('km-app');
} else {
classes.push('km-web');
}
if (options && options.statusBarStyle) {
classes.push('km-' + options.statusBarStyle + '-status-bar');
}
return classes.join(' ');
}
function wp8Background(os) {
return 'km-wp-' + (os.noVariantSet ? parseInt($('
').css('background-color').split(',')[1], 10) === 0 ? 'dark' : 'light' : os.variant + ' km-wp-' + os.variant + '-force');
}
function isOrientationHorizontal(element) {
return OS.wp ? element.css('animation-name') == '-kendo-landscape' : Math.abs(window.orientation) / 90 == 1;
}
function getOrientationClass(element) {
return isOrientationHorizontal(element) ? HORIZONTAL : VERTICAL;
}
function setMinimumHeight(pane) {
pane.parent().addBack().css('min-height', window.innerHeight);
}
function applyViewportHeight() {
$('meta[name=viewport]').remove();
HEAD.append(viewportTemplate({ height: ', width=device-width' + (isOrientationHorizontal() ? ', height=' + window.innerHeight + 'px' : support.mobileOS.flatVersion >= 600 && support.mobileOS.flatVersion < 700 ? ', height=' + window.innerWidth + 'px' : ', height=device-height') }));
}
var Application = Widget.extend({
init: function (element, options) {
mobile.application = this;
$($.proxy(this, 'bootstrap', element, options));
},
bootstrap: function (element, options) {
element = $(element);
if (!element[0]) {
element = $(document.body);
}
Widget.fn.init.call(this, element, options);
this.element.removeAttr('data-' + kendo.ns + 'role');
this._setupPlatform();
this._attachMeta();
this._setupElementClass();
this._attachHideBarHandlers();
var paneOptions = $.extend({}, this.options);
delete paneOptions.name;
var that = this, startHistory = function () {
that.pane = new Pane(that.element, paneOptions);
that.pane.navigateToInitial();
if (that.options.updateDocumentTitle) {
that._setupDocumentTitle();
}
that._startHistory();
that.trigger(INIT);
};
if (this.options.$angular) {
setTimeout(startHistory);
} else {
startHistory();
}
},
options: {
name: 'Application',
hideAddressBar: true,
browserHistory: true,
historyTransition: HISTORY_TRANSITION,
modelScope: window,
statusBarStyle: 'black',
transition: '',
retina: false,
platform: null,
skin: null,
updateDocumentTitle: true,
useNativeScrolling: false
},
events: [INIT],
navigate: function (url, transition) {
this.pane.navigate(url, transition);
},
replace: function (url, transition) {
this.pane.replace(url, transition);
},
scroller: function () {
return this.view().scroller;
},
hideLoading: function () {
if (this.pane) {
this.pane.hideLoading();
} else {
throw new Error('The mobile application instance is not fully instantiated. Please consider activating loading in the application init event handler.');
}
},
showLoading: function () {
if (this.pane) {
this.pane.showLoading();
} else {
throw new Error('The mobile application instance is not fully instantiated. Please consider activating loading in the application init event handler.');
}
},
changeLoadingMessage: function (message) {
if (this.pane) {
this.pane.changeLoadingMessage(message);
} else {
throw new Error('The mobile application instance is not fully instantiated. Please consider changing the message in the application init event handler.');
}
},
view: function () {
return this.pane.view();
},
skin: function (skin) {
var that = this;
if (!arguments.length) {
return that.options.skin;
}
that.options.skin = skin || '';
that.element[0].className = 'km-pane';
that._setupPlatform();
that._setupElementClass();
return that.options.skin;
},
destroy: function () {
Widget.fn.destroy.call(this);
this.pane.destroy();
this.router.destroy();
},
_setupPlatform: function () {
var that = this, platform = that.options.platform, skin = that.options.skin, split = [], os = OS || MOBILE_PLATFORMS[DEFAULT_OS];
if (platform) {
os.setDefaultPlatform = true;
if (typeof platform === 'string') {
split = platform.split('-');
os = $.extend({ variant: split[1] }, os, MOBILE_PLATFORMS[split[0]]);
} else {
os = platform;
}
}
if (skin) {
split = skin.split('-');
if (!OS) {
os.setDefaultPlatform = false;
}
os = $.extend({}, os, {
skin: split[0],
variant: split[1]
});
}
if (!os.variant) {
os.noVariantSet = true;
os.variant = 'dark';
}
that.os = os;
that.osCssClass = osCssClass(that.os, that.options);
if (os.name == 'wp') {
if (!that.refreshBackgroundColorProxy) {
that.refreshBackgroundColorProxy = $.proxy(function () {
if (that.os.variant && (that.os.skin && that.os.skin === that.os.name) || !that.os.skin) {
that.element.removeClass('km-wp-dark km-wp-light km-wp-dark-force km-wp-light-force').addClass(wp8Background(that.os));
}
}, that);
}
$(document).off('visibilitychange', that.refreshBackgroundColorProxy);
$(document).off('resume', that.refreshBackgroundColorProxy);
if (!os.skin) {
that.element.parent().css('overflow', 'hidden');
$(document).on('visibilitychange', that.refreshBackgroundColorProxy);
$(document).on('resume', that.refreshBackgroundColorProxy);
that.refreshBackgroundColorProxy();
}
}
},
_startHistory: function () {
if (this.options.browserHistory) {
this.router = new kendo.Router({
pushState: this.options.pushState,
root: this.options.root,
hashBang: this.options.hashBang
});
this.pane.bindToRouter(this.router);
this.router.start();
} else {
if (!this.options.initial) {
this.pane.navigate('');
}
}
},
_resizeToScreenHeight: function () {
var includeStatusBar = $('meta[name=apple-mobile-web-app-status-bar-style]').attr('content').match(/black-translucent|hidden/), element = this.element, height;
if (CHROME) {
height = window.innerHeight;
} else {
if (isOrientationHorizontal(element)) {
if (includeStatusBar) {
if (INITIALLY_HORIZONTAL) {
height = SCREEN.availWidth + STATUS_BAR_HEIGHT;
} else {
height = SCREEN.availWidth;
}
} else {
if (INITIALLY_HORIZONTAL) {
height = SCREEN.availWidth;
} else {
height = SCREEN.availWidth - STATUS_BAR_HEIGHT;
}
}
} else {
if (includeStatusBar) {
if (INITIALLY_HORIZONTAL) {
height = SCREEN.availHeight;
} else {
height = SCREEN.availHeight + STATUS_BAR_HEIGHT;
}
} else {
if (INITIALLY_HORIZONTAL) {
height = SCREEN.availHeight - STATUS_BAR_HEIGHT;
} else {
height = SCREEN.availHeight;
}
}
}
}
element.height(height);
},
_setupElementClass: function () {
var that = this, size, element = that.element;
element.parent().addClass('km-root km-' + (that.os.tablet ? 'tablet' : 'phone'));
element.addClass(that.osCssClass + ' ' + getOrientationClass(element));
if (this.options.useNativeScrolling) {
element.parent().addClass('km-native-scrolling');
}
if (CHROME) {
element.addClass('km-ios-chrome');
}
if (support.wpDevicePixelRatio) {
element.parent().css('font-size', support.wpDevicePixelRatio + 'em');
}
if (this.options.retina) {
element.parent().addClass('km-retina');
element.parent().css('font-size', support.devicePixelRatio * FONT_SIZE_COEF + 'em');
}
if (BERRYPHONEGAP) {
applyViewportHeight();
}
if (that.options.useNativeScrolling) {
element.parent().addClass('km-native-scrolling');
} else if (ENABLE_CLIP) {
size = (screen.availWidth > screen.availHeight ? screen.availWidth : screen.availHeight) + 200;
$(clipTemplate({
width: size,
height: size
})).appendTo(HEAD);
}
if (BROKEN_WEBVIEW_RESIZE) {
that._resizeToScreenHeight();
}
kendo.onResize(function () {
element.removeClass('km-horizontal km-vertical').addClass(getOrientationClass(element));
if (that.options.useNativeScrolling) {
setMinimumHeight(element);
}
if (BROKEN_WEBVIEW_RESIZE) {
that._resizeToScreenHeight();
}
if (BERRYPHONEGAP) {
applyViewportHeight();
}
kendo.resize(element);
});
},
_clearExistingMeta: function () {
HEAD.find('meta').filter('[name|=\'apple-mobile-web-app\'],[name|=\'msapplication-tap\'],[name=\'viewport\']').remove();
},
_attachMeta: function () {
var options = this.options, icon = options.icon, size;
this._clearExistingMeta();
if (!BERRYPHONEGAP) {
HEAD.prepend(viewportTemplate({
height: '',
scale: this.options.retina ? 1 / support.devicePixelRatio : '1.0'
}));
}
HEAD.prepend(systemMeta(options));
if (icon) {
if (typeof icon === 'string') {
icon = { '': icon };
}
for (size in icon) {
HEAD.prepend(iconMeta({
icon: icon[size],
size: size
}));
}
}
if (options.useNativeScrolling) {
setMinimumHeight(this.element);
}
},
_attachHideBarHandlers: function () {
var that = this, hideBar = proxy(that, '_hideBar');
if (support.mobileOS.appMode || !that.options.hideAddressBar || !HIDEBAR || that.options.useNativeScrolling) {
return;
}
that._initialHeight = {};
WINDOW.on('load', hideBar);
kendo.onResize(function () {
setTimeout(window.scrollTo, 0, 0, 1);
});
},
_setupDocumentTitle: function () {
var that = this, defaultTitle = document.title;
that.pane.bind('viewShow', function (e) {
var title = e.view.title;
document.title = title !== undefined ? title : defaultTitle;
});
},
_hideBar: function () {
var that = this, element = that.element;
element.height(kendo.support.transforms.css + 'calc(100% + ' + BARCOMPENSATION + 'px)');
$(window).trigger(kendo.support.resize);
}
});
kendo.mobile.Application = Application;
kendo.ui.plugin(Application, kendo.mobile, 'Mobile');
}(window.kendo.jQuery));
return window.kendo;
}, typeof define == 'function' && define.amd ? define : function (a1, a2, a3) {
(a3 || a2)();
}));
(function (f, define) {
define('kendo.mobile.button', ['kendo.userevents'], f);
}(function () {
var __meta__ = {
id: 'mobile.button',
name: 'Button',
category: 'mobile',
description: 'The Button widget navigates between mobile Application views when pressed.',
depends: ['userevents']
};
(function ($, undefined) {
var kendo = window.kendo, mobile = kendo.mobile, ui = mobile.ui, Widget = ui.Widget, support = kendo.support, os = support.mobileOS, ANDROID3UP = os.android && os.flatVersion >= 300, CLICK = 'click', DISABLED = 'disabled', DISABLEDSTATE = 'km-state-disabled';
function highlightButton(widget, event, highlight) {
$(event.target).closest('.km-button,.km-detail').toggleClass('km-state-active', highlight);
if (ANDROID3UP && widget.deactivateTimeoutID) {
clearTimeout(widget.deactivateTimeoutID);
widget.deactivateTimeoutID = 0;
}
}
function createBadge(value) {
return $('
' + value + '');
}
var Button = Widget.extend({
init: function (element, options) {
var that = this;
Widget.fn.init.call(that, element, options);
var useTap = that.options.clickOn === 'up';
that._wrap();
that._style();
if (!useTap) {
that.element.attr('data-navigate-on-press', true);
}
that.options.enable = that.options.enable && !that.element.attr(DISABLED);
that.enable(that.options.enable);
that._userEvents = new kendo.UserEvents(that.element, {
allowSelection: !useTap,
fastTap: true,
press: function (e) {
that._activate(e);
},
release: function (e) {
highlightButton(that, e, false);
if (!useTap) {
e.event.stopPropagation();
}
}
});
that._userEvents.bind(useTap ? 'tap' : 'press', function (e) {
that._release(e);
});
if (ANDROID3UP) {
that.element.on('move', function (e) {
that._timeoutDeactivate(e);
});
}
},
destroy: function () {
Widget.fn.destroy.call(this);
this._userEvents.destroy();
},
events: [CLICK],
options: {
name: 'Button',
icon: '',
style: '',
badge: '',
clickOn: 'up',
enable: true
},
badge: function (value) {
var badge = this.badgeElement = this.badgeElement || createBadge(value).appendTo(this.element);
if (value || value === 0) {
badge.html(value);
return this;
}
if (value === false) {
badge.empty().remove();
this.badgeElement = false;
return this;
}
return badge.html();
},
enable: function (enable) {
var element = this.element;
if (typeof enable == 'undefined') {
enable = true;
}
this.options.enable = enable;
if (enable) {
element.removeAttr(DISABLED);
} else {
element.attr(DISABLED, DISABLED);
}
element.toggleClass(DISABLEDSTATE, !enable);
},
_timeoutDeactivate: function (e) {
if (!this.deactivateTimeoutID) {
this.deactivateTimeoutID = setTimeout(highlightButton, 500, this, e, false);
}
},
_activate: function (e) {
var activeElement = document.activeElement, nodeName = activeElement ? activeElement.nodeName : '';
if (this.options.enable) {
highlightButton(this, e, true);
if (nodeName == 'INPUT' || nodeName == 'TEXTAREA') {
activeElement.blur();
}
}
},
_release: function (e) {
var that = this;
if (e.which > 1) {
return;
}
if (!that.options.enable) {
e.preventDefault();
return;
}
if (that.trigger(CLICK, {
target: $(e.target),
button: that.element
})) {
e.preventDefault();
}
},
_style: function () {
var style = this.options.style, element = this.element, styles;
if (style) {
styles = style.split(' ');
$.each(styles, function () {
element.addClass('km-' + this);
});
}
},
_wrap: function () {
var that = this, icon = that.options.icon, badge = that.options.badge, iconSpan = '
').children('span.km-text');
}
if (!image[0] && icon) {
if (!span[0]) {
iconSpan += ' km-notext';
}
that.iconElement = element.prepend($(iconSpan + '" />'));
}
if (badge || badge === 0) {
that.badgeElement = createBadge(badge).appendTo(element);
}
}
});
var BackButton = Button.extend({
options: {
name: 'BackButton',
style: 'back'
},
init: function (element, options) {
var that = this;
Button.fn.init.call(that, element, options);
if (typeof that.element.attr('href') === 'undefined') {
that.element.attr('href', '#:back');
}
}
});
var DetailButton = Button.extend({
options: {
name: 'DetailButton',
style: ''
},
init: function (element, options) {
Button.fn.init.call(this, element, options);
},
_style: function () {
var style = this.options.style + ' detail', element = this.element;
if (style) {
var styles = style.split(' ');
$.each(styles, function () {
element.addClass('km-' + this);
});
}
},
_wrap: function () {
var that = this, icon = that.options.icon, iconSpan = '
'));
}
}
});
ui.plugin(Button);
ui.plugin(BackButton);
ui.plugin(DetailButton);
}(window.kendo.jQuery));
return window.kendo;
}, typeof define == 'function' && define.amd ? define : function (a1, a2, a3) {
(a3 || a2)();
}));
(function (f, define) {
define('kendo.mobile.buttongroup', ['kendo.core'], f);
}(function () {
var __meta__ = {
id: 'mobile.buttongroup',
name: 'ButtonGroup',
category: 'mobile',
description: 'The Kendo mobile ButtonGroup widget is a linear set of grouped buttons.',
depends: ['core']
};
(function ($, undefined) {
var kendo = window.kendo, ui = kendo.mobile.ui, Widget = ui.Widget, ACTIVE = 'km-state-active', DISABLE = 'km-state-disabled', SELECT = 'select', SELECTOR = 'li:not(.' + ACTIVE + ')';
function createBadge(value) {
return $('
' + value + '');
}
var ButtonGroup = Widget.extend({
init: function (element, options) {
var that = this;
Widget.fn.init.call(that, element, options);
that.element.addClass('km-buttongroup').find('li').each(that._button);
that.element.on(that.options.selectOn, SELECTOR, '_select');
that._enable = true;
that.select(that.options.index);
if (!that.options.enable) {
that._enable = false;
that.wrapper.addClass(DISABLE);
}
},
events: [SELECT],
options: {
name: 'ButtonGroup',
selectOn: 'down',
index: -1,
enable: true
},
current: function () {
return this.element.find('.' + ACTIVE);
},
select: function (li) {
var that = this, index = -1;
if (li === undefined || li === -1 || !that._enable || $(li).is('.' + DISABLE)) {
return;
}
that.current().removeClass(ACTIVE);
if (typeof li === 'number') {
index = li;
li = $(that.element[0].children[li]);
} else if (li.nodeType) {
li = $(li);
index = li.index();
}
li.addClass(ACTIVE);
that.selectedIndex = index;
},
badge: function (item, value) {
var buttongroup = this.element, badge;
if (!isNaN(item)) {
item = buttongroup.children().get(item);
}
item = buttongroup.find(item);
badge = $(item.children('.km-badge')[0] || createBadge(value).appendTo(item));
if (value || value === 0) {
badge.html(value);
return this;
}
if (value === false) {
badge.empty().remove();
return this;
}
return badge.html();
},
enable: function (enable) {
var wrapper = this.wrapper;
if (typeof enable == 'undefined') {
enable = true;
}
if (enable) {
wrapper.removeClass(DISABLE);
} else {
wrapper.addClass(DISABLE);
}
this._enable = this.options.enable = enable;
},
_button: function () {
var button = $(this).addClass('km-button'), icon = kendo.attrValue(button, 'icon'), badge = kendo.attrValue(button, 'badge'), span = button.children('span'), image = button.find('img').addClass('km-image');
if (!span[0]) {
span = button.wrapInner('
').children('span');
}
span.addClass('km-text');
if (!image[0] && icon) {
button.prepend($('
'));
}
if (badge || badge === 0) {
createBadge(badge).appendTo(button);
}
},
_select: function (e) {
if (e.which > 1 || e.isDefaultPrevented() || !this._enable) {
return;
}
this.select(e.currentTarget);
this.trigger(SELECT, { index: this.selectedIndex });
}
});
ui.plugin(ButtonGroup);
}(window.kendo.jQuery));
return window.kendo;
}, typeof define == 'function' && define.amd ? define : function (a1, a2, a3) {
(a3 || a2)();
}));
(function (f, define) {
define('kendo.mobile.collapsible', ['kendo.core'], f);
}(function () {
var __meta__ = {
id: 'mobile.collapsible',
name: 'Collapsible',
category: 'mobile',
description: 'The Kendo mobile Collapsible widget provides ability for creating collapsible blocks of content.',
depends: [
'core',
'userevents'
]
};
(function ($, undefined) {
var kendo = window.kendo, ui = kendo.mobile.ui, Widget = ui.Widget, COLLAPSIBLE = 'km-collapsible', HEADER = 'km-collapsible-header', CONTENT = 'km-collapsible-content', INSET = 'km-collapsibleinset', HEADER_WRAPPER = '
', CONTENT_WRAPPER = '
', COLLAPSED = 'km-collapsed', EXPANDED = 'km-expanded', ANIMATED = 'km-animated', LEFT = 'left', EXAPND = 'expand', COLLAPSE = 'collapse';
var Collapsible = Widget.extend({
init: function (element, options) {
var that = this, container = $(element);
Widget.fn.init.call(that, container, options);
container.addClass(COLLAPSIBLE);
that._buildHeader();
that.content = container.children().not(that.header).wrapAll(CONTENT_WRAPPER).parent();
that._userEvents = new kendo.UserEvents(that.header, {
fastTap: true,
tap: function () {
that.toggle();
}
});
container.addClass(that.options.collapsed ? COLLAPSED : EXPANDED);
if (that.options.inset) {
container.addClass(INSET);
}
if (that.options.animation) {
that.content.addClass(ANIMATED);
that.content.height(0);
if (that.options.collapsed) {
that.content.hide();
}
} else if (that.options.collapsed) {
that.content.hide();
}
},
events: [
EXAPND,
COLLAPSE
],
options: {
name: 'Collapsible',
collapsed: true,
collapseIcon: 'arrow-n',
expandIcon: 'arrow-s',
iconPosition: LEFT,
animation: true,
inset: false
},
destroy: function () {
Widget.fn.destroy.call(this);
this._userEvents.destroy();
},
expand: function (instant) {
var icon = this.options.collapseIcon, content = this.content, ios = kendo.support.mobileOS.ios;
if (!this.trigger(EXAPND)) {
if (icon) {
this.header.find('.km-icon').removeClass().addClass('km-icon km-' + icon);
}
this.element.removeClass(COLLAPSED).addClass(EXPANDED);
if (this.options.animation && !instant) {
content.off('transitionend');
content.show();
if (ios) {
content.removeClass(ANIMATED);
}
content.height(this._getContentHeight());
if (ios) {
content.addClass(ANIMATED);
}
kendo.resize(content);
} else {
content.show();
}
}
},
collapse: function (instant) {
var icon = this.options.expandIcon, content = this.content;
if (!this.trigger(COLLAPSE)) {
if (icon) {
this.header.find('.km-icon').removeClass().addClass('km-icon km-' + icon);
}
this.element.removeClass(EXPANDED).addClass(COLLAPSED);
if (this.options.animation && !instant) {
content.one('transitionend', function () {
content.hide();
});
content.height(0);
} else {
content.hide();
}
}
},
toggle: function (instant) {
if (this.isCollapsed()) {
this.expand(instant);
} else {
this.collapse(instant);
}
},
isCollapsed: function () {
return this.element.hasClass(COLLAPSED);
},
resize: function () {
if (!this.isCollapsed() && this.options.animation) {
this.content.height(this._getContentHeight());
}
},
_buildHeader: function () {
var header = this.element.children(':header').wrapAll(HEADER_WRAPPER), iconSpan = $('
'), icon = this.options.collapsed ? this.options.expandIcon : this.options.collapseIcon, iconPosition = this.options.iconPosition;
if (icon) {
header.prepend(iconSpan);
iconSpan.addClass('km-' + icon);
}
this.header = header.parent();
this.header.addClass('km-icon-' + iconPosition);
},
_getContentHeight: function () {
var style = this.content.attr('style'), height;
this.content.css({
position: 'absolute',
visibility: 'hidden',
height: 'auto'
});
height = this.content.height();
this.content.attr('style', style ? style : '');
return height;
}
});
ui.plugin(Collapsible);
}(window.kendo.jQuery));
return window.kendo;
}, typeof define == 'function' && define.amd ? define : function (a1, a2, a3) {
(a3 || a2)();
}));
(function (f, define) {
define('kendo.mobile.listview', [
'kendo.data',
'kendo.userevents',
'kendo.mobile.button'
], f);
}(function () {
var __meta__ = {
id: 'mobile.listview',
name: 'ListView',
category: 'mobile',
description: 'The Kendo Mobile ListView widget is used to display flat or grouped list of items.',
depends: [
'data',
'userevents',
'mobile.button'
]
};
(function ($, undefined) {
var kendo = window.kendo, Node = window.Node, mobile = kendo.mobile, ui = mobile.ui, DataSource = kendo.data.DataSource, Widget = ui.DataBoundWidget, ITEM_SELECTOR = '.km-list > li, > li:not(.km-group-container)', HIGHLIGHT_SELECTOR = '.km-listview-link, .km-listview-label', ICON_SELECTOR = '[' + kendo.attr('icon') + ']', proxy = $.proxy, attrValue = kendo.attrValue, GROUP_CLASS = 'km-group-title', ACTIVE_CLASS = 'km-state-active', GROUP_WRAPPER = '
', GROUP_TEMPLATE = kendo.template('
#= this.headerTemplate(data) #
#= kendo.render(this.template, data.items)#
'), WRAPPER = '
', SEARCH_TEMPLATE = kendo.template('
'), NS = '.kendoMobileListView', STYLED = 'styled', DATABOUND = 'dataBound', DATABINDING = 'dataBinding', ITEM_CHANGE = 'itemChange', CLICK = 'click', CHANGE = 'change', PROGRESS = 'progress', FUNCTION = 'function', whitespaceRegExp = /^\s+$/, buttonRegExp = /button/;
function whitespace() {
return this.nodeType === Node.TEXT_NODE && this.nodeValue.match(whitespaceRegExp);
}
function addIcon(item, icon) {
if (icon && !item[0].querySelector('.km-icon')) {
item.prepend('
');
}
}
function enhanceItem(item) {
addIcon(item, attrValue(item, 'icon'));
addIcon(item, attrValue(item.children(ICON_SELECTOR), 'icon'));
}
function enhanceLinkItem(item) {
var parent = item.parent(), itemAndDetailButtons = item.add(parent.children(kendo.roleSelector('detailbutton'))), otherNodes = parent.contents().not(itemAndDetailButtons).not(whitespace);
if (otherNodes.length) {
return;
}
item.addClass('km-listview-link').attr(kendo.attr('role'), 'listview-link');
addIcon(item, attrValue(parent, 'icon'));
addIcon(item, attrValue(item, 'icon'));
}
function enhanceCheckBoxItem(label) {
if (!label[0].querySelector('input[type=checkbox],input[type=radio]')) {
return;
}
var item = label.parent();
if (item.contents().not(label).not(function () {
return this.nodeType == 3;
})[0]) {
return;
}
label.addClass('km-listview-label');
label.children('[type=checkbox],[type=radio]').addClass('km-widget km-icon km-check');
}
function putAt(element, top) {
$(element).css('transform', 'translate3d(0px, ' + top + 'px, 0px)');
}
var HeaderFixer = kendo.Class.extend({
init: function (listView) {
var scroller = listView.scroller();
if (!scroller) {
return;
}
this.options = listView.options;
this.element = listView.element;
this.scroller = listView.scroller();
this._shouldFixHeaders();
var headerFixer = this;
var cacheHeaders = function () {
headerFixer._cacheHeaders();
};
listView.bind('resize', cacheHeaders);
listView.bind(STYLED, cacheHeaders);
listView.bind(DATABOUND, cacheHeaders);
scroller.bind('scroll', function (e) {
headerFixer._fixHeader(e);
});
},
_fixHeader: function (e) {
if (!this.fixedHeaders) {
return;
}
var i = 0, scroller = this.scroller, headers = this.headers, scrollTop = e.scrollTop, headerPair, offset, header;
do {
headerPair = headers[i++];
if (!headerPair) {
header = $('
');
break;
}
offset = headerPair.offset;
header = headerPair.header;
} while (offset + 1 > scrollTop);
if (this.currentHeader != i) {
scroller.fixedContainer.html(header.clone());
this.currentHeader = i;
}
},
_shouldFixHeaders: function () {
this.fixedHeaders = this.options.type === 'group' && this.options.fixedHeaders;
},
_cacheHeaders: function () {
this._shouldFixHeaders();
if (!this.fixedHeaders) {
return;
}
var headers = [], offset = this.scroller.scrollTop;
this.element.find('.' + GROUP_CLASS).each(function (_, header) {
header = $(header);
headers.unshift({
offset: header.position().top + offset,
header: header
});
});
this.headers = headers;
this._fixHeader({ scrollTop: offset });
}
});
var DEFAULT_PULL_PARAMETERS = function () {
return { page: 1 };
};
var RefreshHandler = kendo.Class.extend({
init: function (listView) {
var handler = this, options = listView.options, scroller = listView.scroller(), pullParameters = options.pullParameters || DEFAULT_PULL_PARAMETERS;
this.listView = listView;
this.scroller = scroller;
listView.bind('_dataSource', function (e) {
handler.setDataSource(e.dataSource);
});
scroller.setOptions({
pullToRefresh: true,
pull: function () {
if (!handler._pulled) {
handler._pulled = true;
handler.dataSource.read(pullParameters.call(listView, handler._first));
}
},
messages: {
pullTemplate: options.messages.pullTemplate,
releaseTemplate: options.messages.releaseTemplate,
refreshTemplate: options.messages.refreshTemplate
}
});
},
setDataSource: function (dataSource) {
var handler = this;
this._first = dataSource.view()[0];
this.dataSource = dataSource;
dataSource.bind('change', function () {
handler._change();
});
dataSource.bind('error', function () {
handler._change();
});
},
_change: function () {
var scroller = this.scroller, dataSource = this.dataSource;
if (this._pulled) {
scroller.pullHandled();
}
if (this._pulled || !this._first) {
var view = dataSource.view();
if (view[0]) {
this._first = view[0];
}
}
this._pulled = false;
}
});
var VirtualList = kendo.Observable.extend({
init: function (options) {
var list = this;
kendo.Observable.fn.init.call(list);
list.buffer = options.buffer;
list.height = options.height;
list.item = options.item;
list.items = [];
list.footer = options.footer;
list.buffer.bind('reset', function () {
list.refresh();
});
},
refresh: function () {
var buffer = this.buffer, items = this.items, endReached = false;
while (items.length) {
items.pop().destroy();
}
this.offset = buffer.offset;
var itemConstructor = this.item, prevItem, item;
for (var idx = 0; idx < buffer.viewSize; idx++) {
if (idx === buffer.total()) {
endReached = true;
break;
}
item = itemConstructor(this.content(this.offset + items.length));
item.below(prevItem);
prevItem = item;
items.push(item);
}
this.itemCount = items.length;
this.trigger('reset');
this._resize();
if (endReached) {
this.trigger('endReached');
}
},
totalHeight: function () {
if (!this.items[0]) {
return 0;
}
var list = this, items = list.items, top = items[0].top, bottom = items[items.length - 1].bottom, averageItemHeight = (bottom - top) / list.itemCount, remainingItemsCount = list.buffer.length - list.offset - list.itemCount;
return (this.footer ? this.footer.height : 0) + bottom + remainingItemsCount * averageItemHeight;
},
batchUpdate: function (top) {
var height = this.height(), items = this.items, item, initialOffset = this.offset;
if (!items[0]) {
return;
}
if (this.lastDirection) {
while (items[items.length - 1].bottom > top + height * 2) {
if (this.offset === 0) {
break;
}
this.offset--;
item = items.pop();
item.update(this.content(this.offset));
item.above(items[0]);
items.unshift(item);
}
} else {
while (items[0].top < top - height) {
var nextIndex = this.offset + this.itemCount;
if (nextIndex === this.buffer.total()) {
this.trigger('endReached');
break;
}
if (nextIndex === this.buffer.length) {
break;
}
item = items.shift();
item.update(this.content(this.offset + this.itemCount));
item.below(items[items.length - 1]);
items.push(item);
this.offset++;
}
}
if (initialOffset !== this.offset) {
this._resize();
}
},
update: function (top) {
var list = this, items = this.items, item, firstItem, lastItem, height = this.height(), itemCount = this.itemCount, padding = height / 2, up = (this.lastTop || 0) > top, topBorder = top - padding, bottomBorder = top + height + padding;
if (!items[0]) {
return;
}
this.lastTop = top;
this.lastDirection = up;
if (up) {
if (items[0].top > topBorder && items[items.length - 1].bottom > bottomBorder + padding && this.offset > 0) {
this.offset--;
item = items.pop();
firstItem = items[0];
item.update(this.content(this.offset));
items.unshift(item);
item.above(firstItem);
list._resize();
}
} else {
if (items[items.length - 1].bottom < bottomBorder && items[0].top < topBorder - padding) {
var nextIndex = this.offset + itemCount;
if (nextIndex === this.buffer.total()) {
this.trigger('endReached');
} else if (nextIndex !== this.buffer.length) {
item = items.shift();
lastItem = items[items.length - 1];
items.push(item);
item.update(this.content(this.offset + this.itemCount));
list.offset++;
item.below(lastItem);
list._resize();
}
}
}
},
content: function (index) {
return this.buffer.at(index);
},
destroy: function () {
this.unbind();
},
_resize: function () {
var items = this.items, top = 0, bottom = 0, firstItem = items[0], lastItem = items[items.length - 1];
if (firstItem) {
top = firstItem.top;
bottom = lastItem.bottom;
}
this.trigger('resize', {
top: top,
bottom: bottom
});
if (this.footer) {
this.footer.below(lastItem);
}
}
});
kendo.mobile.ui.VirtualList = VirtualList;
var VirtualListViewItem = kendo.Class.extend({
init: function (listView, dataItem) {
var element = listView.append([dataItem], true)[0], height = element.offsetHeight;
$.extend(this, {
top: 0,
element: element,
listView: listView,
height: height,
bottom: height
});
},
update: function (dataItem) {
this.element = this.listView.setDataItem(this.element, dataItem);
},
above: function (item) {
if (item) {
this.height = this.element.offsetHeight;
this.top = item.top - this.height;
this.bottom = item.top;
putAt(this.element, this.top);
}
},
below: function (item) {
if (item) {
this.height = this.element.offsetHeight;
this.top = item.bottom;
this.bottom = this.top + this.height;
putAt(this.element, this.top);
}
},
destroy: function () {
kendo.destroy(this.element);
$(this.element).remove();
}
});
var LOAD_ICON = '
';
var VirtualListViewLoadingIndicator = kendo.Class.extend({
init: function (listView) {
this.element = $('
').appendTo(listView.element);
this._loadIcon = $(LOAD_ICON).appendTo(this.element);
},
enable: function () {
this.element.show();
this.height = this.element.outerHeight(true);
},
disable: function () {
this.element.hide();
this.height = 0;
},
below: function (item) {
if (item) {
this.top = item.bottom;
this.bottom = this.height + this.top;
putAt(this.element, this.top);
}
}
});
var VirtualListViewPressToLoadMore = VirtualListViewLoadingIndicator.extend({
init: function (listView, buffer) {
this._loadIcon = $(LOAD_ICON).hide();
this._loadButton = $('
' + listView.options.messages.loadMoreText + '').hide();
this.element = $('
').append(this._loadIcon).append(this._loadButton).appendTo(listView.element);
var loadMore = this;
this._loadButton.kendoMobileButton().data('kendoMobileButton').bind('click', function () {
loadMore._hideShowButton();
buffer.next();
});
buffer.bind('resize', function () {
loadMore._showLoadButton();
});
this.height = this.element.outerHeight(true);
this.disable();
},
_hideShowButton: function () {
this._loadButton.hide();
this.element.addClass('km-scroller-refresh');
this._loadIcon.css('display', 'block');
},
_showLoadButton: function () {
this._loadButton.show();
this.element.removeClass('km-scroller-refresh');
this._loadIcon.hide();
}
});
var VirtualListViewItemBinder = kendo.Class.extend({
init: function (listView) {
var binder = this;
this.chromeHeight = listView.wrapper.children().not(listView.element).outerHeight() || 0;
this.listView = listView;
this.scroller = listView.scroller();
this.options = listView.options;
listView.bind('_dataSource', function (e) {
binder.setDataSource(e.dataSource, e.empty);
});
listView.bind('resize', function () {
if (!binder.list.items.length) {
return;
}
binder.scroller.reset();
binder.buffer.range(0);
binder.list.refresh();
});
this.scroller.makeVirtual();
this.scroller.bind('scroll', function (e) {
binder.list.update(e.scrollTop);
});
this.scroller.bind('scrollEnd', function (e) {
binder.list.batchUpdate(e.scrollTop);
});
},
destroy: function () {
this.list.unbind();
this.buffer.unbind();
},
setDataSource: function (dataSource, empty) {
var binder = this, options = this.options, listView = this.listView, scroller = listView.scroller(), pressToLoadMore = options.loadMore, pageSize, buffer, footer;
this.dataSource = dataSource;
pageSize = dataSource.pageSize() || options.virtualViewSize;
if (!pageSize && !empty) {
throw new Error('the DataSource does not have page size configured. Page Size setting is mandatory for the mobile listview virtual scrolling to work as expected.');
}
if (this.buffer) {
this.buffer.destroy();
}
buffer = new kendo.data.Buffer(dataSource, Math.floor(pageSize / 2), pressToLoadMore);
if (pressToLoadMore) {
footer = new VirtualListViewPressToLoadMore(listView, buffer);
} else {
footer = new VirtualListViewLoadingIndicator(listView);
}
if (this.list) {
this.list.destroy();
}
var list = new VirtualList({
buffer: buffer,
footer: footer,
item: function (dataItem) {
return new VirtualListViewItem(listView, dataItem);
},
height: function () {
return scroller.height();
}
});
list.bind('resize', function () {
binder.updateScrollerSize();
listView.updateSize();
});
list.bind('reset', function () {
binder.footer.enable();
});
list.bind('endReached', function () {
footer.disable();
binder.updateScrollerSize();
});
buffer.bind('expand', function () {
list.lastDirection = false;
list.batchUpdate(scroller.scrollTop);
});
$.extend(this, {
buffer: buffer,
scroller: scroller,
list: list,
footer: footer
});
},
updateScrollerSize: function () {
this.scroller.virtualSize(0, this.list.totalHeight() + this.chromeHeight);
},
refresh: function () {
this.list.refresh();
},
reset: function () {
this.buffer.range(0);
this.list.refresh();
}
});
var ListViewItemBinder = kendo.Class.extend({
init: function (listView) {
var binder = this;
this.listView = listView;
this.options = listView.options;
var itemBinder = this;
this._refreshHandler = function (e) {
itemBinder.refresh(e);
};
this._progressHandler = function () {
listView.showLoading();
};
listView.bind('_dataSource', function (e) {
binder.setDataSource(e.dataSource);
});
},
destroy: function () {
this._unbindDataSource();
},
reset: function () {
},
refresh: function (e) {
var action = e && e.action, dataItems = e && e.items, listView = this.listView, dataSource = this.dataSource, prependOnRefresh = this.options.appendOnRefresh, view = dataSource.view(), groups = dataSource.group(), groupedMode = groups && groups[0], item;
if (action === 'itemchange') {
if (!listView._hasBindingTarget()) {
item = listView.findByDataItem(dataItems)[0];
if (item) {
listView.setDataItem(item, dataItems[0]);
}
}
return;
}
var removedItems, addedItems, addedDataItems;
var adding = action === 'add' && !groupedMode || prependOnRefresh && !listView._filter;
var removing = action === 'remove' && !groupedMode;
if (adding) {
removedItems = [];
} else if (removing) {
removedItems = listView.findByDataItem(dataItems);
}
if (listView.trigger(DATABINDING, {
action: action || 'rebind',
items: dataItems,
removedItems: removedItems,
index: e && e.index
})) {
if (this._shouldShowLoading()) {
listView.hideLoading();
}
return;
}
if (action === 'add' && !groupedMode) {
var index = view.indexOf(dataItems[0]);
if (index > -1) {
addedItems = listView.insertAt(dataItems, index);
addedDataItems = dataItems;
}
} else if (action === 'remove' && !groupedMode) {
addedItems = [];
listView.remove(dataItems);
} else if (groupedMode) {
listView.replaceGrouped(view);
} else if (prependOnRefresh && !listView._filter) {
addedItems = listView.prepend(view);
addedDataItems = view;
} else {
listView.replace(view);
}
if (this._shouldShowLoading()) {
listView.hideLoading();
}
listView.trigger(DATABOUND, {
ns: ui,
addedItems: addedItems,
addedDataItems: addedDataItems
});
},
setDataSource: function (dataSource) {
if (this.dataSource) {
this._unbindDataSource();
}
this.dataSource = dataSource;
dataSource.bind(CHANGE, this._refreshHandler);
if (this._shouldShowLoading()) {
this.dataSource.bind(PROGRESS, this._progressHandler);
}
},
_unbindDataSource: function () {
this.dataSource.unbind(CHANGE, this._refreshHandler).unbind(PROGRESS, this._progressHandler);
},
_shouldShowLoading: function () {
var options = this.options;
return !options.pullToRefresh && !options.loadMore && !options.endlessScroll;
}
});
var ListViewFilter = kendo.Class.extend({
init: function (listView) {
var filter = this, filterable = listView.options.filterable, events = 'change paste', that = this;
this.listView = listView;
this.options = filterable;
listView.element.before(SEARCH_TEMPLATE({ placeholder: filterable.placeholder || 'Search...' }));
if (filterable.autoFilter !== false) {
events += ' keyup';
}
this.element = listView.wrapper.find('.km-search-form');
this.searchInput = listView.wrapper.find('input[type=search]').closest('form').on('submit' + NS, function (e) {
e.preventDefault();
}).end().on('focus' + NS, function () {
filter._oldFilter = filter.searchInput.val();
}).on(events.split(' ').join(NS + ' ') + NS, proxy(this._filterChange, this));
this.clearButton = listView.wrapper.find('.km-filter-reset').on(CLICK, proxy(this, '_clearFilter')).hide();
this._dataSourceChange = $.proxy(this._refreshInput, this);
listView.bind('_dataSource', function (e) {
e.dataSource.bind('change', that._dataSourceChange);
});
},
_refreshInput: function () {
var appliedFilters = this.listView.dataSource.filter();
var searchInput = this.listView._filter.searchInput;
if (!appliedFilters || appliedFilters.filters[0].field !== this.listView.options.filterable.field) {
searchInput.val('');
} else {
searchInput.val(appliedFilters.filters[0].value);
}
},
_search: function (expr) {
this._filter = true;
this.clearButton[expr ? 'show' : 'hide']();
this.listView.dataSource.filter(expr);
},
_filterChange: function (e) {
var filter = this;
if (e.type == 'paste' && this.options.autoFilter !== false) {
setTimeout(function () {
filter._applyFilter();
}, 1);
} else {
this._applyFilter();
}
},
_applyFilter: function () {
var options = this.options, value = this.searchInput.val(), expr = value.length ? {
field: options.field,
operator: options.operator || 'startswith',
ignoreCase: options.ignoreCase,
value: value
} : null;
if (value === this._oldFilter) {
return;
}
this._oldFilter = value;
this._search(expr);
},
_clearFilter: function (e) {
this.searchInput.val('');
this._search(null);
e.preventDefault();
}
});
var ListView = Widget.extend({
init: function (element, options) {
var listView = this;
Widget.fn.init.call(this, element, options);
element = this.element;
options = this.options;
if (options.scrollTreshold) {
options.scrollThreshold = options.scrollTreshold;
}
element.on('down', HIGHLIGHT_SELECTOR, '_highlight').on('move up cancel', HIGHLIGHT_SELECTOR, '_dim');
this._userEvents = new kendo.UserEvents(element, {
fastTap: true,
filter: ITEM_SELECTOR,
allowSelection: true,
tap: function (e) {
listView._click(e);
}
});
element.css('-ms-touch-action', 'auto');
element.wrap(WRAPPER);
this.wrapper = this.element.parent();
this._headerFixer = new HeaderFixer(this);
this._itemsCache = {};
this._templates();
this.virtual = options.endlessScroll || options.loadMore;
this._style();
if (this.options.$angular && (this.virtual || this.options.pullToRefresh)) {
setTimeout($.proxy(this, '_start'));
} else {
this._start();
}
},
_start: function () {
var options = this.options;
if (this.options.filterable) {
this._filter = new ListViewFilter(this);
}
if (this.virtual) {
this._itemBinder = new VirtualListViewItemBinder(this);
} else {
this._itemBinder = new ListViewItemBinder(this);
}
if (this.options.pullToRefresh) {
this._pullToRefreshHandler = new RefreshHandler(this);
}
this.setDataSource(options.dataSource);
this._enhanceItems(this.items());
kendo.notify(this, ui);
},
events: [
CLICK,
DATABINDING,
DATABOUND,
ITEM_CHANGE
],
options: {
name: 'ListView',
style: '',
type: 'flat',
autoBind: true,
fixedHeaders: false,
template: '#:data#',
headerTemplate: '
#:value#',
appendOnRefresh: false,
loadMore: false,
endlessScroll: false,
scrollThreshold: 30,
pullToRefresh: false,
messages: {
loadMoreText: 'Press to load more',
pullTemplate: 'Pull to refresh',
releaseTemplate: 'Release to refresh',
refreshTemplate: 'Refreshing'
},
pullOffset: 140,
filterable: false,
virtualViewSize: null
},
refresh: function () {
this._itemBinder.refresh();
},
reset: function () {
this._itemBinder.reset();
},
setDataSource: function (dataSource) {
var emptyDataSource = !dataSource;
this.dataSource = DataSource.create(dataSource);
this.trigger('_dataSource', {
dataSource: this.dataSource,
empty: emptyDataSource
});
if (this.options.autoBind && !emptyDataSource) {
this.items().remove();
this.dataSource.fetch();
}
},
destroy: function () {
Widget.fn.destroy.call(this);
kendo.destroy(this.element);
this._userEvents.destroy();
if (this._itemBinder) {
this._itemBinder.destroy();
}
this.element.unwrap();
delete this.element;
delete this.wrapper;
delete this._userEvents;
},
items: function () {
if (this.options.type === 'group') {
return this.element.find('.km-list').children();
} else {
return this.element.children().not('.km-load-more');
}
},
scroller: function () {
if (!this._scrollerInstance) {
this._scrollerInstance = this.element.closest('.km-scroll-wrapper').data('kendoMobileScroller');
}
return this._scrollerInstance;
},
showLoading: function () {
var view = this.view();
if (view && view.loader) {
view.loader.show();
}
},
hideLoading: function () {
var view = this.view();
if (view && view.loader) {
view.loader.hide();
}
},
insertAt: function (dataItems, index, triggerChange) {
var listView = this;
return listView._renderItems(dataItems, function (items) {
if (index === 0) {
listView.element.prepend(items);
} else if (index === -1) {
listView.element.append(items);
} else {
listView.items().eq(index - 1).after(items);
}
if (triggerChange) {
for (var i = 0; i < items.length; i++) {
listView.trigger(ITEM_CHANGE, {
item: items.eq(i),
data: dataItems[i],
ns: ui
});
}
}
});
},
append: function (dataItems, triggerChange) {
return this.insertAt(dataItems, -1, triggerChange);
},
prepend: function (dataItems, triggerChange) {
return this.insertAt(dataItems, 0, triggerChange);
},
replace: function (dataItems) {
this.options.type = 'flat';
this._angularItems('cleanup');
this.element.empty();
this._userEvents.cancel();
this._style();
return this.insertAt(dataItems, 0);
},
replaceGrouped: function (groups) {
this.options.type = 'group';
this._angularItems('cleanup');
this.element.empty();
var items = $(kendo.render(this.groupTemplate, groups));
this._enhanceItems(items.children('ul').children('li'));
this.element.append(items);
mobile.init(items);
this._style();
this._angularItems('compile');
},
remove: function (dataItems) {
var items = this.findByDataItem(dataItems);
this.angular('cleanup', function () {
return { elements: items };
});
kendo.destroy(items);
items.remove();
},
findByDataItem: function (dataItems) {
var selectors = [];
for (var idx = 0, length = dataItems.length; idx < length; idx++) {
selectors[idx] = '[data-' + kendo.ns + 'uid=' + dataItems[idx].uid + ']';
}
return this.element.find(selectors.join(','));
},
setDataItem: function (item, dataItem) {
var listView = this, replaceItem = function (items) {
var newItem = $(items[0]);
kendo.destroy(item);
listView.angular('cleanup', function () {
return { elements: [$(item)] };
});
$(item).replaceWith(newItem);
listView.trigger(ITEM_CHANGE, {
item: newItem,
data: dataItem,
ns: ui
});
};
return this._renderItems([dataItem], replaceItem)[0];
},
updateSize: function () {
this._size = this.getSize();
},
_renderItems: function (dataItems, callback) {
var items = $(kendo.render(this.template, dataItems));
callback(items);
this.angular('compile', function () {
return {
elements: items,
data: dataItems.map(function (data) {
return { dataItem: data };
})
};
});
mobile.init(items);
this._enhanceItems(items);
return items;
},
_dim: function (e) {
this._toggle(e, false);
},
_highlight: function (e) {
this._toggle(e, true);
},
_toggle: function (e, highlight) {
if (e.which > 1) {
return;
}
var clicked = $(e.currentTarget), item = clicked.parent(), role = attrValue(clicked, 'role') || '', plainItem = !role.match(buttonRegExp), prevented = e.isDefaultPrevented();
if (plainItem) {
item.toggleClass(ACTIVE_CLASS, highlight && !prevented);
}
},
_templates: function () {
var template = this.options.template, headerTemplate = this.options.headerTemplate, dataIDAttribute = ' data-uid="#=arguments[0].uid || ""#"', templateProxy = {}, groupTemplateProxy = {};
if (typeof template === FUNCTION) {
templateProxy.template = template;
template = '#=this.template(data)#';
}
this.template = proxy(kendo.template('
' + template + ''), templateProxy);
groupTemplateProxy.template = this.template;
if (typeof headerTemplate === FUNCTION) {
groupTemplateProxy._headerTemplate = headerTemplate;
headerTemplate = '#=this._headerTemplate(data)#';
}
groupTemplateProxy.headerTemplate = kendo.template(headerTemplate);
this.groupTemplate = proxy(GROUP_TEMPLATE, groupTemplateProxy);
},
_click: function (e) {
if (e.event.which > 1 || e.event.isDefaultPrevented()) {
return;
}
var dataItem, item = e.target, target = $(e.event.target), buttonElement = target.closest(kendo.roleSelector('button', 'detailbutton', 'backbutton')), button = kendo.widgetInstance(buttonElement, ui), id = item.attr(kendo.attr('uid'));
if (id) {
dataItem = this.dataSource.getByUid(id);
}
if (this.trigger(CLICK, {
target: target,
item: item,
dataItem: dataItem,
button: button
})) {
e.preventDefault();
}
},
_styleGroups: function () {
var rootItems = this.element.children();
rootItems.children('ul').addClass('km-list');
rootItems.each(function () {
var li = $(this), groupHeader = li.contents().first();
li.addClass('km-group-container');
if (!groupHeader.is('ul') && !groupHeader.is('div.' + GROUP_CLASS)) {
groupHeader.wrap(GROUP_WRAPPER);
}
});
},
_style: function () {
var options = this.options, grouped = options.type === 'group', element = this.element, inset = options.style === 'inset';
element.addClass('km-listview').toggleClass('km-list', !grouped).toggleClass('km-virtual-list', this.virtual).toggleClass('km-listinset', !grouped && inset).toggleClass('km-listgroup', grouped && !inset).toggleClass('km-listgroupinset', grouped && inset);
if (!element.parents('.km-listview')[0]) {
element.closest('.km-content').toggleClass('km-insetcontent', inset);
}
if (grouped) {
this._styleGroups();
}
this.trigger(STYLED);
},
_enhanceItems: function (items) {
items.each(function () {
var item = $(this), child, enhanced = false;
item.children().each(function () {
child = $(this);
if (child.is('a')) {
enhanceLinkItem(child);
enhanced = true;
} else if (child.is('label')) {
enhanceCheckBoxItem(child);
enhanced = true;
}
});
if (!enhanced) {
enhanceItem(item);
}
});
}
});
ui.plugin(ListView);
}(window.kendo.jQuery));
return window.kendo;
}, typeof define == 'function' && define.amd ? define : function (a1, a2, a3) {
(a3 || a2)();
}));
(function (f, define) {
define('kendo.mobile.navbar', ['kendo.core'], f);
}(function () {
var __meta__ = {
id: 'mobile.navbar',
name: 'NavBar',
category: 'mobile',
description: 'The Kendo mobile NavBar widget is used inside a mobile View or Layout Header element to display an application navigation bar.',
depends: ['core']
};
(function ($, undefined) {
var kendo = window.kendo, mobile = kendo.mobile, ui = mobile.ui, Widget = ui.Widget;
function createContainer(align, element) {
var items = element.find('[' + kendo.attr('align') + '=' + align + ']');
if (items[0]) {
return $('
').append(items).prependTo(element);
}
}
function toggleTitle(centerElement) {
var siblings = centerElement.siblings(), noTitle = !!centerElement.children('ul')[0], showTitle = !!siblings[0] && $.trim(centerElement.text()) === '', android = !!(kendo.mobile.application && kendo.mobile.application.element.is('.km-android'));
centerElement.prevAll().toggleClass('km-absolute', noTitle);
centerElement.toggleClass('km-show-title', showTitle);
centerElement.toggleClass('km-fill-title', showTitle && !$.trim(centerElement.html()));
centerElement.toggleClass('km-no-title', noTitle);
centerElement.toggleClass('km-hide-title', android && !siblings.children().is(':visible'));
}
var NavBar = Widget.extend({
init: function (element, options) {
var that = this;
Widget.fn.init.call(that, element, options);
element = that.element;
that.container().bind('show', $.proxy(this, 'refresh'));
element.addClass('km-navbar').wrapInner($('
'));
that.leftElement = createContainer('left', element);
that.rightElement = createContainer('right', element);
that.centerElement = element.find('.km-view-title');
},
options: { name: 'NavBar' },
title: function (value) {
this.element.find(kendo.roleSelector('view-title')).text(value);
toggleTitle(this.centerElement);
},
refresh: function (e) {
var view = e.view;
this.title(view.options.title);
},
destroy: function () {
Widget.fn.destroy.call(this);
kendo.destroy(this.element);
}
});
ui.plugin(NavBar);
}(window.kendo.jQuery));
return window.kendo;
}, typeof define == 'function' && define.amd ? define : function (a1, a2, a3) {
(a3 || a2)();
}));
(function (f, define) {
define('kendo.mobile.scrollview', [
'kendo.fx',
'kendo.data',
'kendo.draganddrop'
], f);
}(function () {
var __meta__ = {
id: 'mobile.scrollview',
name: 'ScrollView',
category: 'mobile',
description: 'The Kendo Mobile ScrollView widget is used to scroll content wider than the device screen.',
depends: [
'fx',
'data',
'draganddrop'
]
};
(function ($, undefined) {
var kendo = window.kendo, mobile = kendo.mobile, ui = mobile.ui, proxy = $.proxy, Transition = kendo.effects.Transition, Pane = kendo.ui.Pane, PaneDimensions = kendo.ui.PaneDimensions, Widget = ui.DataBoundWidget, DataSource = kendo.data.DataSource, Buffer = kendo.data.Buffer, BatchBuffer = kendo.data.BatchBuffer, math = Math, abs = math.abs, ceil = math.ceil, round = math.round, max = math.max, min = math.min, floor = math.floor, CHANGE = 'change', CHANGING = 'changing', REFRESH = 'refresh', CURRENT_PAGE_CLASS = 'km-current-page', VIRTUAL_PAGE_CLASS = 'km-virtual-page', FUNCTION = 'function', ITEM_CHANGE = 'itemChange', CLEANUP = 'cleanup', VIRTUAL_PAGE_COUNT = 3, LEFT_PAGE = -1, CETER_PAGE = 0, RIGHT_PAGE = 1, LEFT_SWIPE = -1, NUDGE = 0, RIGHT_SWIPE = 1;
var Pager = kendo.Class.extend({
init: function (scrollView) {
var that = this, element = $('
');
scrollView.element.append(element);
this._changeProxy = proxy(that, '_change');
this._refreshProxy = proxy(that, '_refresh');
scrollView.bind(CHANGE, this._changeProxy);
scrollView.bind(REFRESH, this._refreshProxy);
$.extend(that, {
element: element,
scrollView: scrollView
});
},
items: function () {
return this.element.children();
},
_refresh: function (e) {
var pageHTML = '';
for (var idx = 0; idx < e.pageCount; idx++) {
pageHTML += '';
}
this.element.html(pageHTML);
this.items().eq(e.page).addClass(CURRENT_PAGE_CLASS);
},
_change: function (e) {
this.items().removeClass(CURRENT_PAGE_CLASS).eq(e.page).addClass(CURRENT_PAGE_CLASS);
},
destroy: function () {
this.scrollView.unbind(CHANGE, this._changeProxy);
this.scrollView.unbind(REFRESH, this._refreshProxy);
this.element.remove();
}
});
kendo.mobile.ui.ScrollViewPager = Pager;
var TRANSITION_END = 'transitionEnd', DRAG_START = 'dragStart', DRAG_END = 'dragEnd';
var ElasticPane = kendo.Observable.extend({
init: function (element, options) {
var that = this;
kendo.Observable.fn.init.call(this);
this.element = element;
this.container = element.parent();
var movable, transition, userEvents, dimensions, dimension, pane;
movable = new kendo.ui.Movable(that.element);
transition = new Transition({
axis: 'x',
movable: movable,
onEnd: function () {
that.trigger(TRANSITION_END);
}
});
userEvents = new kendo.UserEvents(element, {
fastTap: true,
start: function (e) {
if (abs(e.x.velocity) * 2 >= abs(e.y.velocity)) {
userEvents.capture();
} else {
userEvents.cancel();
}
that.trigger(DRAG_START, e);
transition.cancel();
},
allowSelection: true,
end: function (e) {
that.trigger(DRAG_END, e);
}
});
dimensions = new PaneDimensions({
element: that.element,
container: that.container
});
dimension = dimensions.x;
dimension.bind(CHANGE, function () {
that.trigger(CHANGE);
});
pane = new Pane({
dimensions: dimensions,
userEvents: userEvents,
movable: movable,
elastic: true
});
$.extend(that, {
duration: options && options.duration || 1,
movable: movable,
transition: transition,
userEvents: userEvents,
dimensions: dimensions,
dimension: dimension,
pane: pane
});
this.bind([
TRANSITION_END,
DRAG_START,
DRAG_END,
CHANGE
], options);
},
size: function () {
return {
width: this.dimensions.x.getSize(),
height: this.dimensions.y.getSize()
};
},
total: function () {
return this.dimension.getTotal();
},
offset: function () {
return -this.movable.x;
},
updateDimension: function () {
this.dimension.update(true);
},
refresh: function () {
this.dimensions.refresh();
},
moveTo: function (offset) {
this.movable.moveAxis('x', -offset);
},
transitionTo: function (offset, ease, instant) {
if (instant) {
this.moveTo(-offset);
} else {
this.transition.moveTo({
location: offset,
duration: this.duration,
ease: ease
});
}
}
});
kendo.mobile.ui.ScrollViewElasticPane = ElasticPane;
var ScrollViewContent = kendo.Observable.extend({
init: function (element, pane, options) {
var that = this;
kendo.Observable.fn.init.call(this);
that.element = element;
that.pane = pane;
that._getPages();
this.page = 0;
this.pageSize = options.pageSize || 1;
this.contentHeight = options.contentHeight;
this.enablePager = options.enablePager;
this.pagerOverlay = options.pagerOverlay;
},
scrollTo: function (page, instant) {
this.page = page;
this.pane.transitionTo(-page * this.pane.size().width, Transition.easeOutExpo, instant);
},
paneMoved: function (swipeType, bounce, callback, instant) {
var that = this, pane = that.pane, width = pane.size().width * that.pageSize, approx = round, ease = bounce ? Transition.easeOutBack : Transition.easeOutExpo, snap, nextPage;
if (swipeType === LEFT_SWIPE) {
approx = ceil;
} else if (swipeType === RIGHT_SWIPE) {
approx = floor;
}
nextPage = approx(pane.offset() / width);
snap = max(that.minSnap, min(-nextPage * width, that.maxSnap));
if (nextPage != that.page) {
if (callback && callback({
currentPage: that.page,
nextPage: nextPage
})) {
snap = -that.page * pane.size().width;
}
}
pane.transitionTo(snap, ease, instant);
},
updatePage: function () {
var pane = this.pane, page = round(pane.offset() / pane.size().width);
if (page != this.page) {
this.page = page;
return true;
}
return false;
},
forcePageUpdate: function () {
return this.updatePage();
},
resizeTo: function (size) {
var pane = this.pane, width = size.width;
this.pageElements.width(width);
if (this.contentHeight === '100%') {
var containerHeight = this.element.parent().height();
if (this.enablePager === true) {
var pager = this.element.parent().find('ol.km-pages');
if (!this.pagerOverlay && pager.length) {
containerHeight -= pager.outerHeight(true);
}
}
this.element.css('height', containerHeight);
this.pageElements.css('height', containerHeight);
}
pane.updateDimension();
if (!this._paged) {
this.page = floor(pane.offset() / width);
}
this.scrollTo(this.page, true);
this.pageCount = ceil(pane.total() / width);
this.minSnap = -(this.pageCount - 1) * width;
this.maxSnap = 0;
},
_getPages: function () {
this.pageElements = this.element.find(kendo.roleSelector('page'));
this._paged = this.pageElements.length > 0;
}
});
kendo.mobile.ui.ScrollViewContent = ScrollViewContent;
var VirtualScrollViewContent = kendo.Observable.extend({
init: function (element, pane, options) {
var that = this;
kendo.Observable.fn.init.call(this);
that.element = element;
that.pane = pane;
that.options = options;
that._templates();
that.page = options.page || 0;
that.pages = [];
that._initPages();
that.resizeTo(that.pane.size());
that.pane.dimension.forceEnabled();
},
setDataSource: function (dataSource) {
this.dataSource = DataSource.create(dataSource);
this._buffer();
this._pendingPageRefresh = false;
this._pendingWidgetRefresh = false;
},
_viewShow: function () {
var that = this;
if (that._pendingWidgetRefresh) {
setTimeout(function () {
that._resetPages();
}, 0);
that._pendingWidgetRefresh = false;
}
},
_buffer: function () {
var itemsPerPage = this.options.itemsPerPage;
if (this.buffer) {
this.buffer.destroy();
}
if (itemsPerPage > 1) {
this.buffer = new BatchBuffer(this.dataSource, itemsPerPage);
} else {
this.buffer = new Buffer(this.dataSource, itemsPerPage * 3);
}
this._resizeProxy = proxy(this, '_onResize');
this._resetProxy = proxy(this, '_onReset');
this._endReachedProxy = proxy(this, '_onEndReached');
this.buffer.bind({
'resize': this._resizeProxy,
'reset': this._resetProxy,
'endreached': this._endReachedProxy
});
},
_templates: function () {
var template = this.options.template, emptyTemplate = this.options.emptyTemplate, templateProxy = {}, emptyTemplateProxy = {};
if (typeof template === FUNCTION) {
templateProxy.template = template;
template = '#=this.template(data)#';
}
this.template = proxy(kendo.template(template), templateProxy);
if (typeof emptyTemplate === FUNCTION) {
emptyTemplateProxy.emptyTemplate = emptyTemplate;
emptyTemplate = '#=this.emptyTemplate(data)#';
}
this.emptyTemplate = proxy(kendo.template(emptyTemplate), emptyTemplateProxy);
},
_initPages: function () {
var pages = this.pages, element = this.element, page;
for (var i = 0; i < VIRTUAL_PAGE_COUNT; i++) {
page = new Page(element);
pages.push(page);
}
this.pane.updateDimension();
},
resizeTo: function (size) {
var pages = this.pages, pane = this.pane;
for (var i = 0; i < pages.length; i++) {
pages[i].setWidth(size.width);
}
if (this.options.contentHeight === 'auto') {
this.element.css('height', this.pages[1].element.height());
} else if (this.options.contentHeight === '100%') {
var containerHeight = this.element.parent().height();
if (this.options.enablePager === true) {
var pager = this.element.parent().find('ol.km-pages');
if (!this.options.pagerOverlay && pager.length) {
containerHeight -= pager.outerHeight(true);
}
}
this.element.css('height', containerHeight);
pages[0].element.css('height', containerHeight);
pages[1].element.css('height', containerHeight);
pages[2].element.css('height', containerHeight);
}
pane.updateDimension();
this._repositionPages();
this.width = size.width;
},
scrollTo: function (page) {
var buffer = this.buffer, dataItem;
buffer.syncDataSource();
dataItem = buffer.at(page);
if (!dataItem) {
return;
}
this._updatePagesContent(page);
this.page = page;
},
paneMoved: function (swipeType, bounce, callback, instant) {
var that = this, pane = that.pane, width = pane.size().width, offset = pane.offset(), thresholdPassed = Math.abs(offset) >= width / 3, ease = bounce ? kendo.effects.Transition.easeOutBack : kendo.effects.Transition.easeOutExpo, isEndReached = that.page + 2 > that.buffer.total(), nextPage, delta = 0;
if (swipeType === RIGHT_SWIPE) {
if (that.page !== 0) {
delta = -1;
}
} else if (swipeType === LEFT_SWIPE && !isEndReached) {
delta = 1;
} else if (offset > 0 && (thresholdPassed && !isEndReached)) {
delta = 1;
} else if (offset < 0 && thresholdPassed) {
if (that.page !== 0) {
delta = -1;
}
}
nextPage = that.page;
if (delta) {
nextPage = delta > 0 ? nextPage + 1 : nextPage - 1;
}
if (callback && callback({
currentPage: that.page,
nextPage: nextPage
})) {
delta = 0;
}
if (delta === 0) {
that._cancelMove(ease, instant);
} else if (delta === -1) {
that._moveBackward(instant);
} else if (delta === 1) {
that._moveForward(instant);
}
},
updatePage: function () {
var pages = this.pages;
if (this.pane.offset() === 0) {
return false;
}
if (this.pane.offset() > 0) {
pages.push(this.pages.shift());
this.page++;
this.setPageContent(pages[2], this.page + 1);
} else {
pages.unshift(this.pages.pop());
this.page--;
this.setPageContent(pages[0], this.page - 1);
}
this._repositionPages();
this._resetMovable();
return true;
},
forcePageUpdate: function () {
var offset = this.pane.offset(), threshold = this.pane.size().width * 3 / 4;
if (abs(offset) > threshold) {
return this.updatePage();
}
return false;
},
_resetMovable: function () {
this.pane.moveTo(0);
},
_moveForward: function (instant) {
this.pane.transitionTo(-this.width, kendo.effects.Transition.easeOutExpo, instant);
},
_moveBackward: function (instant) {
this.pane.transitionTo(this.width, kendo.effects.Transition.easeOutExpo, instant);
},
_cancelMove: function (ease, instant) {
this.pane.transitionTo(0, ease, instant);
},
_resetPages: function () {
this.page = this.options.page || 0;
this._updatePagesContent(this.page);
this._repositionPages();
this.trigger('reset');
},
_onResize: function () {
this.pageCount = ceil(this.dataSource.total() / this.options.itemsPerPage);
if (this._pendingPageRefresh) {
this._updatePagesContent(this.page);
this._pendingPageRefresh = false;
}
this.trigger('resize');
},
_onReset: function () {
this.pageCount = ceil(this.dataSource.total() / this.options.itemsPerPage);
this._resetPages();
},
_onEndReached: function () {
this._pendingPageRefresh = true;
},
_repositionPages: function () {
var pages = this.pages;
pages[0].position(LEFT_PAGE);
pages[1].position(CETER_PAGE);
pages[2].position(RIGHT_PAGE);
},
_updatePagesContent: function (offset) {
var pages = this.pages, currentPage = offset || 0;
this.setPageContent(pages[0], currentPage - 1);
this.setPageContent(pages[1], currentPage);
this.setPageContent(pages[2], currentPage + 1);
},
setPageContent: function (page, index) {
var buffer = this.buffer, template = this.template, emptyTemplate = this.emptyTemplate, view = null;
if (index >= 0) {
view = buffer.at(index);
if ($.isArray(view) && !view.length) {
view = null;
}
}
this.trigger(CLEANUP, { item: page.element });
if (view !== null) {
page.content(template(view));
} else {
page.content(emptyTemplate({}));
}
kendo.mobile.init(page.element);
this.trigger(ITEM_CHANGE, {
item: page.element,
data: view,
ns: kendo.mobile.ui
});
}
});
kendo.mobile.ui.VirtualScrollViewContent = VirtualScrollViewContent;
var Page = kendo.Class.extend({
init: function (container) {
this.element = $('');
this.width = container.width();
this.element.width(this.width);
container.append(this.element);
},
content: function (theContent) {
this.element.html(theContent);
},
position: function (position) {
this.element.css('transform', 'translate3d(' + this.width * position + 'px, 0, 0)');
},
setWidth: function (width) {
this.width = width;
this.element.width(width);
}
});
kendo.mobile.ui.VirtualPage = Page;
var ScrollView = Widget.extend({
init: function (element, options) {
var that = this;
Widget.fn.init.call(that, element, options);
options = that.options;
element = that.element;
kendo.stripWhitespace(element[0]);
element.wrapInner('').addClass('km-scrollview');
if (this.options.enablePager) {
this.pager = new Pager(this);
if (this.options.pagerOverlay) {
element.addClass('km-scrollview-overlay');
}
}
that.inner = element.children().first();
that.page = 0;
that.inner.css('height', options.contentHeight);
that.pane = new ElasticPane(that.inner, {
duration: this.options.duration,
transitionEnd: proxy(this, '_transitionEnd'),
dragStart: proxy(this, '_dragStart'),
dragEnd: proxy(this, '_dragEnd'),
change: proxy(this, REFRESH)
});
that.bind('resize', function () {
that.pane.refresh();
});
that.page = options.page;
var empty = this.inner.children().length === 0;
var content = empty ? new VirtualScrollViewContent(that.inner, that.pane, options) : new ScrollViewContent(that.inner, that.pane, options);
content.page = that.page;
content.bind('reset', function () {
this._pendingPageRefresh = false;
that._syncWithContent();
that.trigger(REFRESH, {
pageCount: content.pageCount,
page: content.page
});
});
content.bind('resize', function () {
that.trigger(REFRESH, {
pageCount: content.pageCount,
page: content.page
});
});
content.bind(ITEM_CHANGE, function (e) {
that.trigger(ITEM_CHANGE, e);
that.angular('compile', function () {
return {
elements: e.item,
data: [{ dataItem: e.data }]
};
});
});
content.bind(CLEANUP, function (e) {
that.angular('cleanup', function () {
return { elements: e.item };
});
});
that._content = content;
that.setDataSource(options.dataSource);
var mobileContainer = that.container();
if (mobileContainer.nullObject) {
that.viewInit();
that.viewShow();
} else {
mobileContainer.bind('show', proxy(this, 'viewShow')).bind('init', proxy(this, 'viewInit'));
}
},
options: {
name: 'ScrollView',
page: 0,
duration: 400,
velocityThreshold: 0.8,
contentHeight: 'auto',
pageSize: 1,
itemsPerPage: 1,
bounceVelocityThreshold: 1.6,
enablePager: true,
pagerOverlay: false,
autoBind: true,
template: '',
emptyTemplate: ''
},
events: [
CHANGING,
CHANGE,
REFRESH
],
destroy: function () {
Widget.fn.destroy.call(this);
kendo.destroy(this.element);
},
viewInit: function () {
if (this.options.autoBind) {
this._content.scrollTo(this._content.page, true);
}
},
viewShow: function () {
this.pane.refresh();
},
refresh: function () {
var content = this._content;
content.resizeTo(this.pane.size());
this.page = content.page;
this.trigger(REFRESH, {
pageCount: content.pageCount,
page: content.page
});
},
content: function (html) {
this.element.children().first().html(html);
this._content._getPages();
this.pane.refresh();
},
value: function (item) {
var dataSource = this.dataSource;
if (item) {
this.scrollTo(dataSource.indexOf(item), true);
} else {
return dataSource.at(this.page);
}
},
scrollTo: function (page, instant) {
this._content.scrollTo(page, instant);
this._syncWithContent();
},
prev: function () {
var that = this, prevPage = that.page - 1;
if (that._content instanceof VirtualScrollViewContent) {
that._content.paneMoved(RIGHT_SWIPE, undefined, function (eventData) {
return that.trigger(CHANGING, eventData);
});
} else if (prevPage > -1) {
that.scrollTo(prevPage);
}
},
next: function () {
var that = this, nextPage = that.page + 1;
if (that._content instanceof VirtualScrollViewContent) {
that._content.paneMoved(LEFT_SWIPE, undefined, function (eventData) {
return that.trigger(CHANGING, eventData);
});
} else if (nextPage < that._content.pageCount) {
that.scrollTo(nextPage);
}
},
setDataSource: function (dataSource) {
if (!(this._content instanceof VirtualScrollViewContent)) {
return;
}
var emptyDataSource = !dataSource;
this.dataSource = DataSource.create(dataSource);
this._content.setDataSource(this.dataSource);
if (this.options.autoBind && !emptyDataSource) {
this.dataSource.fetch();
}
},
items: function () {
return this.element.find('.' + VIRTUAL_PAGE_CLASS);
},
_syncWithContent: function () {
var pages = this._content.pages, buffer = this._content.buffer, data, element;
this.page = this._content.page;
data = buffer ? buffer.at(this.page) : undefined;
if (!(data instanceof Array)) {
data = [data];
}
element = pages ? pages[1].element : undefined;
this.trigger(CHANGE, {
page: this.page,
element: element,
data: data
});
},
_dragStart: function () {
if (this._content.forcePageUpdate()) {
this._syncWithContent();
}
},
_dragEnd: function (e) {
var that = this, velocity = e.x.velocity, velocityThreshold = this.options.velocityThreshold, swipeType = NUDGE, bounce = abs(velocity) > this.options.bounceVelocityThreshold;
if (velocity > velocityThreshold) {
swipeType = RIGHT_SWIPE;
} else if (velocity < -velocityThreshold) {
swipeType = LEFT_SWIPE;
}
this._content.paneMoved(swipeType, bounce, function (eventData) {
return that.trigger(CHANGING, eventData);
});
},
_transitionEnd: function () {
if (this._content.updatePage()) {
this._syncWithContent();
}
}
});
ui.plugin(ScrollView);
}(window.kendo.jQuery));
return window.kendo;
}, typeof define == 'function' && define.amd ? define : function (a1, a2, a3) {
(a3 || a2)();
}));
(function (f, define) {
define('kendo.mobile.switch', [
'kendo.fx',
'kendo.userevents'
], f);
}(function () {
var __meta__ = {
id: 'mobile.switch',
name: 'Switch',
category: 'mobile',
description: 'The mobile Switch widget is used to display two exclusive choices.',
depends: [
'fx',
'userevents'
]
};
(function ($, undefined) {
var kendo = window.kendo, ui = kendo.mobile.ui, Widget = ui.Widget, support = kendo.support, CHANGE = 'change', SWITCHON = 'km-switch-on', SWITCHOFF = 'km-switch-off', MARGINLEFT = 'margin-left', ACTIVE_STATE = 'km-state-active', DISABLED_STATE = 'km-state-disabled', DISABLED = 'disabled', TRANSFORMSTYLE = support.transitions.css + 'transform', proxy = $.proxy;
function limitValue(value, minLimit, maxLimit) {
return Math.max(minLimit, Math.min(maxLimit, value));
}
var SWITCH_MARKUP = ' {0} {1} ';
var Switch = Widget.extend({
init: function (element, options) {
var that = this, checked;
Widget.fn.init.call(that, element, options);
options = that.options;
that.wrapper = $(kendo.format(SWITCH_MARKUP, options.onLabel, options.offLabel));
that.handle = that.wrapper.find('.km-switch-handle');
that.background = that.wrapper.find('.km-switch-background');
that.wrapper.insertBefore(that.element).prepend(that.element);
that._drag();
that.origin = parseInt(that.background.css(MARGINLEFT), 10);
that.constrain = 0;
that.snapPoint = 0;
element = that.element[0];
element.type = 'checkbox';
that._animateBackground = true;
checked = that.options.checked;
if (checked === null) {
checked = element.checked;
}
that.check(checked);
that.options.enable = that.options.enable && !that.element.attr(DISABLED);
that.enable(that.options.enable);
that.refresh();
kendo.notify(that, kendo.mobile.ui);
},
refresh: function () {
var that = this, handleWidth = that.handle.outerWidth(true);
that.width = that.wrapper.width();
that.constrain = that.width - handleWidth;
that.snapPoint = that.constrain / 2;
if (typeof that.origin != 'number') {
that.origin = parseInt(that.background.css(MARGINLEFT), 10);
}
that.background.data('origin', that.origin);
that.check(that.element[0].checked);
},
events: [CHANGE],
options: {
name: 'Switch',
onLabel: 'on',
offLabel: 'off',
checked: null,
enable: true
},
check: function (check) {
var that = this, element = that.element[0];
if (check === undefined) {
return element.checked;
}
that._position(check ? that.constrain : 0);
element.checked = check;
that.wrapper.toggleClass(SWITCHON, check).toggleClass(SWITCHOFF, !check);
},
value: function () {
return this.check.apply(this, arguments);
},
destroy: function () {
Widget.fn.destroy.call(this);
this.userEvents.destroy();
},
toggle: function () {
var that = this;
that.check(!that.element[0].checked);
},
enable: function (enable) {
var element = this.element, wrapper = this.wrapper;
if (typeof enable == 'undefined') {
enable = true;
}
this.options.enable = enable;
if (enable) {
element.removeAttr(DISABLED);
} else {
element.attr(DISABLED, DISABLED);
}
wrapper.toggleClass(DISABLED_STATE, !enable);
},
_resize: function () {
this.refresh();
},
_move: function (e) {
var that = this;
e.preventDefault();
that._position(limitValue(that.position + e.x.delta, 0, that.width - that.handle.outerWidth(true)));
},
_position: function (position) {
var that = this;
that.position = position;
that.handle.css(TRANSFORMSTYLE, 'translatex(' + position + 'px)');
if (that._animateBackground) {
that.background.css(MARGINLEFT, that.origin + position);
}
},
_start: function () {
if (!this.options.enable) {
this.userEvents.cancel();
} else {
this.userEvents.capture();
this.handle.addClass(ACTIVE_STATE);
}
},
_stop: function () {
var that = this;
that.handle.removeClass(ACTIVE_STATE);
that._toggle(that.position > that.snapPoint);
},
_toggle: function (checked) {
var that = this, handle = that.handle, element = that.element[0], value = element.checked, duration = kendo.mobile.application && kendo.mobile.application.os.wp ? 100 : 200, distance;
that.wrapper.toggleClass(SWITCHON, checked).toggleClass(SWITCHOFF, !checked);
that.position = distance = checked * that.constrain;
if (that._animateBackground) {
that.background.kendoStop(true, true).kendoAnimate({
effects: 'slideMargin',
offset: distance,
reset: true,
reverse: !checked,
axis: 'left',
duration: duration
});
}
handle.kendoStop(true, true).kendoAnimate({
effects: 'slideTo',
duration: duration,
offset: distance + 'px,0',
reset: true,
complete: function () {
if (value !== checked) {
element.checked = checked;
that.trigger(CHANGE, { checked: checked });
}
}
});
},
_drag: function () {
var that = this;
that.userEvents = new kendo.UserEvents(that.wrapper, {
fastTap: true,
tap: function () {
if (that.options.enable) {
that._toggle(!that.element[0].checked);
}
},
start: proxy(that._start, that),
move: proxy(that._move, that),
end: proxy(that._stop, that)
});
}
});
ui.plugin(Switch);
}(window.kendo.jQuery));
return window.kendo;
}, typeof define == 'function' && define.amd ? define : function (a1, a2, a3) {
(a3 || a2)();
}));
(function (f, define) {
define('kendo.mobile.tabstrip', ['kendo.core'], f);
}(function () {
var __meta__ = {
id: 'mobile.tabstrip',
name: 'TabStrip',
category: 'mobile',
description: 'The mobile TabStrip widget is used inside a mobile view or layout footer element to display an application-wide group of navigation buttons.',
depends: ['core']
};
(function ($, undefined) {
var kendo = window.kendo, ui = kendo.mobile.ui, Widget = ui.Widget, ACTIVE_STATE_CLASS = 'km-state-active', SELECT = 'select';
function createBadge(value) {
return $('' + value + '');
}
var TabStrip = Widget.extend({
init: function (element, options) {
var that = this;
Widget.fn.init.call(that, element, options);
that.container().bind('show', $.proxy(this, 'refresh'));
that.element.addClass('km-tabstrip').find('a').each(that._buildButton).eq(that.options.selectedIndex).addClass(ACTIVE_STATE_CLASS);
that.element.on('down', 'a', '_release');
},
events: [SELECT],
switchTo: function (url) {
var tabs = this.element.find('a'), tab, path, idx = 0, length = tabs.length;
if (isNaN(url)) {
for (; idx < length; idx++) {
tab = tabs[idx];
path = tab.href.replace(/(\#.+)(\?.+)$/, '$1');
if (path.indexOf(url, path.length - url.length) !== -1) {
this._setActiveItem($(tab));
return true;
}
}
} else {
this._setActiveItem(tabs.eq(url));
return true;
}
return false;
},
switchByFullUrl: function (url) {
var tab;
tab = this.element.find('a[href$=\'' + url + '\']');
this._setActiveItem(tab);
},
clear: function () {
this.currentItem().removeClass(ACTIVE_STATE_CLASS);
},
currentItem: function () {
return this.element.children('.' + ACTIVE_STATE_CLASS);
},
badge: function (item, value) {
var tabstrip = this.element, badge;
if (!isNaN(item)) {
item = tabstrip.children().get(item);
}
item = tabstrip.find(item);
badge = $(item.find('.km-badge')[0] || createBadge(value).insertAfter(item.children('.km-icon')));
if (value || value === 0) {
badge.html(value);
return this;
}
if (value === false) {
badge.empty().remove();
return this;
}
return badge.html();
},
_release: function (e) {
if (e.which > 1) {
return;
}
var that = this, item = $(e.currentTarget);
if (item[0] === that.currentItem()[0]) {
return;
}
if (that.trigger(SELECT, { item: item })) {
e.preventDefault();
} else {
that._setActiveItem(item);
}
},
_setActiveItem: function (item) {
if (!item[0]) {
return;
}
this.clear();
item.addClass(ACTIVE_STATE_CLASS);
},
_buildButton: function () {
var button = $(this), icon = kendo.attrValue(button, 'icon'), badge = kendo.attrValue(button, 'badge'), image = button.find('img'), iconSpan = $('');
button.addClass('km-button').attr(kendo.attr('role'), 'tab').contents().not(image).wrapAll('');
if (image[0]) {
image.addClass('km-image').prependTo(button);
} else {
button.prepend(iconSpan);
if (icon) {
iconSpan.addClass('km-' + icon);
if (badge || badge === 0) {
createBadge(badge).insertAfter(iconSpan);
}
}
}
},
refresh: function (e) {
var url = e.view.id;
if (url && !this.switchTo(e.view.id)) {
this.switchTo(url);
}
},
options: {
name: 'TabStrip',
selectedIndex: 0,
enable: true
}
});
ui.plugin(TabStrip);
}(window.kendo.jQuery));
return window.kendo;
}, typeof define == 'function' && define.amd ? define : function (a1, a2, a3) {
(a3 || a2)();
}));
(function (f, define) {
define('kendo.mobile', [
'kendo.core',
'kendo.fx',
'kendo.data.odata',
'kendo.data.xml',
'kendo.data',
'kendo.data.signalr',
'kendo.binder',
'kendo.validator',
'kendo.router',
'kendo.view',
'kendo.userevents',
'kendo.draganddrop',
'kendo.popup',
'kendo.touch',
'kendo.mobile.popover',
'kendo.mobile.loader',
'kendo.mobile.scroller',
'kendo.mobile.shim',
'kendo.mobile.view',
'kendo.mobile.modalview',
'kendo.mobile.drawer',
'kendo.mobile.splitview',
'kendo.mobile.pane',
'kendo.mobile.application',
'kendo.mobile.actionsheet',
'kendo.mobile.button',
'kendo.mobile.buttongroup',
'kendo.mobile.collapsible',
'kendo.mobile.listview',
'kendo.mobile.navbar',
'kendo.mobile.scrollview',
'kendo.mobile.switch',
'kendo.mobile.tabstrip',
'kendo.angular',
'kendo.webcomponents',
'kendo.angular2'
], f);
}(function () {
'bundle all';
return window.kendo;
}, typeof define == 'function' && define.amd ? define : function (a1, a2, a3) {
(a3 || a2)();
}));
(function (f, define) {
define('kendo.all', [
'kendo.web',
'kendo.dataviz',
'kendo.mobile',
'kendo.drawing',
'kendo.dom'
], f);
}(function () {
'bundle all';
return window.kendo;
}, typeof define == 'function' && define.amd ? define : function (a1, a2, a3) {
(a3 || a2)();
}));