UNPKG

@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
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