@progress/kendo-ui
Version:
This package is part of the [Kendo UI for jQuery](http://www.telerik.com/kendo-ui) suite.
1,500 lines (1,358 loc) • 113 kB
JavaScript
module.exports =
/******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId])
/******/ return installedModules[moduleId].exports;
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ exports: {},
/******/ id: moduleId,
/******/ loaded: false
/******/ };
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/ // Flag the module as loaded
/******/ module.loaded = true;
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/ // Load entry module and return exports
/******/ return __webpack_require__(0);
/******/ })
/************************************************************************/
/******/ ({
/***/ 0:
/***/ (function(module, exports, __webpack_require__) {
module.exports = __webpack_require__(878);
/***/ }),
/***/ 3:
/***/ (function(module, exports) {
module.exports = function() { throw new Error("define cannot be used indirect"); };
/***/ }),
/***/ 857:
/***/ (function(module, exports) {
module.exports = require("../../kendo.dataviz.core");
/***/ }),
/***/ 878:
/***/ (function(module, exports, __webpack_require__) {
var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;(function(f, define){
!(__WEBPACK_AMD_DEFINE_ARRAY__ = [ __webpack_require__(879), __webpack_require__(857) ], __WEBPACK_AMD_DEFINE_FACTORY__ = (f), __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? (__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
})(function(){
(function ($, undefined) {
// Imports ================================================================
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;
// Constants ==============================================================
var HITTESTAREA = 3,
EPSILON = 1e-06;
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) {
// note that the angle goes down and not the usual mathematical convention
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) { // from must be the leftmost point
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);
}
}
});
/**
* Structure combining a Point with two additional points representing the handles or tangents attached to the first point.
* If the additional points are null or equal to the first point the path will be sharp.
* Left and right correspond to the direction of the underlying path.
*/
var PathDefiner = Class.extend(
{
init: function (p, left, right) {
this.point = p;
this.left = left;
this.right = right;
}
}
);
/**
* Defines a rectangular region.
*/
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)) {
//parallel lines
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)) {
//r < 0 => line 1 is below line 2
//r > 1 => line 1 is above line 2
//s < 0 => line 2 is below line 1
//s > 1 => line 2 is above line 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) {//last possible case is rect1 to be completely within rect2
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;
}
};
/**
* Aligns two rectangles, where one is the container and the other is content.
*/
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;
}
});
/**
* SVG transformation matrix.
*/
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();
// of the form "matrix(...)"
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);
}));
}
}
// of the form "(...)"
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;
}
});
/**
* SVG transformation represented as a vector.
*/
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;
}
});
/**
* Returns a value with Gaussian (normal) distribution.
* @param mean The mean value of the distribution.
* @param deviation The deviation (spreading at half-height) of the distribution.
* @returns {number}
*/
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);
}
/**
* Returns a random identifier which can be used as an ID of objects, eventually augmented with a prefix.
* @returns {string}
*/
function randomId(length) {
if (Utils.isUndefined(length)) {
length = 10;
}
// old version return Math.floor((1 + Math.random()) * 0x1000000).toString(16).substring(1);
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 = {
/**
* Returns the squared distance to the line defined by the two given Points.
* @param p An arbitrary Point.
* @param a An endpoint of the line or segment.
* @param b The complementary endpoint of the line or segment.
*/
_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) { // returns the distance of p to a
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); // sits on side of a
}
dot = (b.x - p.x) * vx + (b.y - p.y) * vy;
if (dot < 0) {
return d2(b, p); // sits on side of b
}
// regular case, use crossproduct to get the sine out
dot = (b.x - p.x) * vy - (b.y - p.y) * vx;
return dot * dot / (vx * vx + vy * vy);
},
/**
* Returns the distance to the line defined by the two given Points.
* @param p An arbitrary Point.
* @param a An endpoint of the line or segment.
* @param b The complementary endpoint of the line or segment.
*/
distanceToLine: function (p, a, b) {
return Math.sqrt(this._distanceToLineSquared(p, a, b));
},
/**
* Returns the distance of the given points to the polyline defined by the points.
* @param p An arbitrary point.
* @param points The points defining the polyline.
* @returns {Number}
*/
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);
}
};
/*---------------The HashTable structure--------------------------------*/
/**
* Represents a collection of key-value pairs that are organized based on the hash code of the key.
* _buckets[hashId] = {key: key, value:...}
* Important: do not use the standard Array access method, use the get/set methods instead.
* See http://en.wikipedia.org/wiki/Hash_table
*/
var HashTable = kendo.Class.extend({
init: function () {
this._buckets = [];
this.length = 0;
},
/**
* Adds the literal object with the given key (of the form {key: key,....}).
*/
add: function (key, value) {
var obj = this._createGetBucket(key);
if (Utils.isDefined(value)) {
obj.value = value;
}
return obj;
},
/**
* Gets the literal object with the given key.
*/
get: function (key) {
if (this._bucketExists(key)) {
return this._createGetBucket(key);
}
return null;
},
/**
* Set the key-value pair.
* @param key The key of the entry.
* @param value The value to set. If the key already exists the value will be overwritten.
*/
set: function (key, value) {
this.add(key, value);
},
/**
* Determines whether the HashTable contains a specific key.
*/
containsKey: function (key) {
return this._bucketExists(key);
},
/**
* Removes the element with the specified key from the hashtable.
* Returns the removed bucket.
*/
remove: function (key) {
if (this._bucketExists(key)) {
var hashId = this._hash(key);
delete this._buckets[hashId];
this.length--;
return key;
}
},
/**
* Foreach with an iterator working on the key-value pairs.
* @param func
*/
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);
}
},
/**
* Returns a (shallow) clone of the current HashTable.
* @returns {HashTable}
*/
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;
},
/**
* Returns the hashes of the buckets.
* @returns {Array}
* @private
*/
_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]);
},
/**
* Returns-adds the createGetBucket with the given key. If not present it will
* be created and returned.
* A createGetBucket is a literal object of the form {key: key, ...}.
*/
_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;
},
/**
* Hashing of the given key.
*/
_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.";
},
/**
* Hashing of a string.
*/
_hashString: function (s) {
// see for example http://stackoverflow.com/questions/7616461/generate-a-hash-from-string-in-javascript-jquery
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;
},
/**
* Returns the unique identifier for an object. This is automatically assigned and add on the object.
*/
_objectHashId: function (key) {
var id = key._hashId;
if (Utils.isUndefined(id)) {
id = randomId();
key._hashId = id;
}
return id;
}
});
/*---------------The Dictionary structure--------------------------------*/
/**
* Represents a collection of key-value pairs.
* Important: do not use the standard Array access method, use the get/Set methods instead.
*/
var Dictionary = kendo.Observable.extend({
/**
* Initializes a new instance of the Dictionary class.
* @param dictionary Loads the content of the given dictionary into this new one.
*/
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);
}
}
},
/**
* Adds a key-value to the dictionary.
* If the key already exists this will assign the given value to the existing entry.
*/
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 the key-value pair.
* @param key The key of the entry.
* @param value The value to set. If the key already exists the value will be overwritten.
*/
set: function (key, value) {
this.add(key, value);
},
/**
* Gets the value associated with the given key in the dictionary.
*/
get: function (key) {
var entry = this._hashTable.get(key);
if (entry) {
return entry.value;
}
throw new Error("Cannot find key " + key);
},
/**
* Returns whether the dictionary contains the given key.
*/
containsKey: function (key) {
return this._hashTable.containsKey(key);
},
/**
* Removes the element with the specified key from the dictionary.
*/
remove: function (key) {
if (this.containsKey(key)) {
this.trigger("changed");
this.length--;
return this._hashTable.remove(key);
}
},
/**
* The functional gets the key and value as parameters.
*/
forEach: function (func, thisRef) {
this._hashTable.forEach(function (entry) {
func.call(thisRef, entry.key, entry.value);
});
},
/**
* Same as forEach except that only the value is passed to the functional.
*/
forEachValue: function (func, thisRef) {
this._hashTable.forEach(function (entry) {
func.call(thisRef, entry.value);
});
},
/**
* Calls a defined callback function for each key in the dictionary.
*/
forEachKey: function (func, thisRef) {
this._hashTable.forEach(function (entry) {
func.call(thisRef, entry.key);
});
},
/**
* Gets an array with all keys in the dictionary.
*/
keys: function () {
var keys = [];
this.forEachKey(function (key) {
keys.push(key);
});
return keys;
}
});
/*---------------Queue structure--------------------------------*/
var Queue = kendo.Class.extend({
init: function () {
this._tail = null;
this._head = null;
this.length = 0;
},
/**
* Enqueues an object to the end of the queue.
*/
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++;
},
/**
* Removes and returns the object at top of the queue.
*/
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;
}
});
/**
* While other data structures can have multiple times the same item a Set owns only
* once a particular item.
* @type {*}
*/
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;
}
},
/**
* Returns the hash of the item.
* @param item
* @returns {*}
*/
hash: function (item) {
return this._hashTable._hash(item);
},
/**
* Removes the given item from the set. No exception is thrown if the item is not in the Set.
* @param item
*/
remove: function (item) {
if (this.contains(item)) {
this._hashTable.remove(item);
this.length--;
this.trigger('changed');
}
},
/**
* Foreach with an iterator working on the key-value pairs.
* @param func
*/
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;
}
});
/*----------------Node-------------------------------*/
/**
* Defines the node (vertex) of a Graph.
*/
var Node = kendo.Class.extend({
init: function (id, shape) {
/**
* Holds all the links incident with the current node.
* Do not use this property to manage the incoming links, use the appropriate add/remove methods instead.
*/
this.links = [];
/**
* Holds the links from the current one to another Node .
* Do not use this property to manage the incoming links, use the appropriate add/remove methods instead.
*/
this.outgoing = [];
/**
* Holds the links from another Node to the current one.
* Do not use this property to manage the incoming links, use the appropriate add/remove methods instead.
*/
this.incoming = [];
/**
* Holds the weight of this Node.
*/
this.weight = 1;
if (Utils.isDefined(id)) {
this.id = id;
}
else {
this.id = randomId();
}
if (Utils.isDefined(shape)) {
this.associatedShape = shape;
// transfer the shape's bounds to the runtime props
var b = shape.bounds();
this.width = b.width;
this.height = b.height;
this.x = b.x;
this.y = b.y;
}
else {
this.associatedShape = null;
}
/**
* The payload of the node.
* @type {null}
*/
this.data = null;
this.type = "Node";
this.shortForm = "Node '" + this.id + "'";
/**
* Whether this is an injected node during the analysis or layout process.
* @type {boolean}
*/
this.isVirtual = false;
},
/**
* Returns whether this node has no links attached.
*/
isIsolated: function () {
return Utils.isEmpty(this.links);
},
/**
* Gets or sets the bounding rectangle of this node.
* This should be considered as runtime data, the property is not hotlinked to a SVG item.
*/
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;
},
/**
* Returns whether there is at least one link with the given (complementary) node. This can be either an
* incoming or outgoing link.
*/
isLinkedTo: function (node) {
var that = this;
return Utils.any(that.links, function (link) {
return link.getComplement(that) === node;
});
},
/**
* Gets the children of this node, defined as the adjacent nodes with a link from this node to the adjacent one.
* @returns {Array}
*/
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;
},
/**
* Gets the parents of this node, defined as the adjacent nodes with a link from the adjacent node to this one.
* @returns {Array}
*/
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;
},
/**
* Returns a clone of the Node. Note that the identifier is not cloned since it's a different Node instance.
* @returns {Node}
*/
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;
},
/**
* Returns whether there is a link from the current node to the given node.
*/
adjacentTo: function (node) {
return this.isLinkedTo(node) !== null;
},
/**
* Removes the given link from the link collection this node owns.
* @param link
*/
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;
}
},
/**
* Returns whether there is a (outgoing) link from the current node to the given one.
*/
hasLinkTo: function (node) {
return Utils.any(this.outgoing, function (link) {
return link.target === node;
});
},
/**
* Returns the degree of this node, i.e. the sum of incoming and outgoing links.
*/
degree: function () {
return this.links.length;
},
/**
* Returns whether this node is either the source or the target of the given link.
*/
incidentWith: function (link) {
return contains(this.links, link);
},
/**
* Returns the links between this node and the given one.
*/
getLinksWith: function (node) {
return Utils.all(this.links, function (link) {
return link.getComplement(this) === node;
}, this);
},
/**
* Returns the nodes (either parent or child) which are linked to the current one.
*/
getNeighbors: function () {
v