UNPKG

@progress/kendo-ui

Version:

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

3,182 lines 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 () {
	            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;
	        }
	    });

	    /**
	     * Defines a directed link (edge, connection) of a Graph.
	     */
	    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 + "'";
	        },

	        /**
	         * Returns the complementary node of the given one, if any.
	         */
	        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;
	        },

	        /**
	         * Returns the overlap of the current link with the given one, if any.
	         */
	        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;
	        },

	        /**
	         * Returns whether the current link is bridging the given nodes.
	         */
	        isBridging: function (v1, v2) {
	            return this.source === v1 && this.target === v2 || this.source === v2 && this.target === v1;
	        },

	        /**
	         * Returns the source and target of this link as a tuple.
	         */
	        getNodes: function () {
	            return [this.source, this.target];
	        },

	        /**
	         * Returns whether the given node is either the source or the target of the current link.
	         */
	        incidentWith: function (node) {
	            return this.source === node || this.target === node;
	        },

	        /**
	         * Returns whether the given link is a continuation of the current one. This can be both
	         * via an incoming or outgoing link.
	         */
	        adjacentTo: function (link) {
	            return contains(this.source.links, link) || contains(this.target.links, link);
	        },

	        /**
	         * Changes the source-node of this 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;
	        },

	        /**
	         * Changes the target-node of this link.
	         * @param 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;
	        },

	        /**
	         * Changes both the source and the target nodes of this link.
	         */
	        changesNodes: function (v, w) {
	            if (this.source === v) {
	                this.changeSource(w);
	            }
	            else if (this.target === v) {
	                this.changeTarget(w);
	            }
	        },

	        /**
	         * Reverses the direction of this link.
	         */
	        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;
	        },

	        /**
	         * Ensures that the given target defines the endpoint of this link.
	         */
	        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();
	            }
	        },

	        /**
	         * Returns a reversed clone of this link.
	         */
	        createReverseEdge: function () {
	            var r = this.clone();
	            r.reverse();
	            r.reversed = true;
	            return r;
	        },

	        /**
	         * Returns a clone of this link.
	         */
	        clone: function () {
	            var clone = new Link(this.source, this.target);
	            return clone;
	        }
	    });

	    /*--------------Graph structure---------------------------------*/
	    /**
	     * Defines a directed graph structure.
	     * Note that the incidence structure resides in the nodes through the incoming and outgoing links collection, rahter than
	     * inside the Graph.
	     */
	    var Graph = kendo.Class.extend({
	        init: function (idOrDiagram) {
	            /**
	             * The links or edge collection of this Graph.
	             * @type {Array}
	             */
	            this.links = [];
	            /**
	             * The node or vertex collection of this Graph.
	             * @type {Array}
	             */
	            this.nodes = [];

	            this._nodeMap = new Dictionary();
	            /**
	             * The optional reference to the Diagram on which this Graph is based.
	             * @type {null}
	             */
	            this.diagram = null;

	            /**
	             * The root of this Graph. If not set explicitly the first Node with zero incoming links will be taken.
	             * @type {null}
	             * @private
	             */
	            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();
	            }

	            /**
	             * The bounds of this graph if the nodes have spatial extension defined.
	             * @type {Rect}
	             */
	            this.bounds = new Rect();
	            // keeps track whether the children & parents have been created
	            this._hasCachedRelationships = false;
	            this.type = "Graph";
	        },
	        /**
	         * Caches the relational information of parents and children in the 'parents' and 'children'
	         * properties.
	         * @param forceRebuild If set to true the relational info will be rebuild even if already present.
	         */
	        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;
	        },

	        /**
	         * Assigns tree-levels to the nodes assuming this is a tree graph.
	         * If not connected or not a tree the process will succeed but
	         * will have little meaning.
	         * @param startNode The node from where the level numbering starts, usually the root of the tree.
	         * @param visited The collection of visited nodes.
	         * @param offset The offset or starting counter of the level info.
	         */
	        assignLevels: function (startNode, offset, visited) {
	            if (!startNode) {
	                throw "Start node not specified.";
	            }
	            if (Utils.isUndefined(offset)) {
	                offset = 0;
	            }
	            // if not done before, cache the parents and children
	            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);
	            }
	        },

	        /**
	         * Gets or set the root of this graph.
	         * If not set explicitly the first Node with zero incoming links will be taken.
	         * @param value
	         * @returns {*}
	         */
	        root: function (value) {
	            if (Utils.isUndefined(value)) {
	                if (!this._root) {
	                    // TODO: better to use the longest path for the most probable 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;
	            }
	        },

	        /**
	         * Returns the connected components of this graph.
	         * Note that the returned graphs are made up of the nodes and links of this graph, i.e. a pointer to the items of this graph.
	         * If you alter the items of the components you'll alter the original graph and vice versa.
	         * @returns {Array}
	         */
	        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]);
	            }
	            // sorting the components in decreasing order of node count
	            components.sort(function (a, b) {
	                return b.nodes.length - a.nodes.length;
	            });
	            return components;
	        },

	        _collectConnectedNodes: function (setIds, nodeIndex) {
	            setIds[nodeIndex] = this.componentIndex; // part of the current component
	            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);
	        },

	        /**
	         * Calculates the bounds of this Graph if the Nodes have spatial dimensions defined.
	         * @returns {Rect}
	         */
	        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;
	        },

	        /**
	         * Creates a spanning tree for the current graph.
	         * Important: this will not return a spanning forest if the graph is disconnected.
	         * Prim's algorithm  finds a minimum-cost spanning tree of an edge-weighted, connected, undirected graph;
	         * see http://en.wikipedia.org/wiki/Prim%27s_algorithm .
	         * @param root The root of the spanning tree.
	         * @returns {Graph}
	         */
	        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;
	        },

	        /**
	         * Returns a random node in this graph.
	         * @param excludedNodes The collection of nodes which should not be considered.
	         * @param incidenceLessThan The maximum degree or incidence the random node should have.
	         * @returns {*}
	         */
	        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)];
	        },

	        /**
	         * Returns whether this is an empty graph.
	         */
	        isEmpty: function () {
	            return Utils.isEmpty(this.nodes);
	        },

	        /**
	         * Checks whether the endpoints of the links are all in the nodes collection.
	         */
	        isHealthy: function () {
	            return Utils.all(this.links, function (link) {
	                return contains(this.nodes, link.source) && contains(this.nodes, link.target);
	            }, this);
	        },

	        /**
	         * 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 (n) {
	            if (!this.hasNode(n)) {
	                throw "The given node is not part of this graph.";
	            }
	            return n.getParents();
	        },

	        /**
	         * 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 (n) {
	            if (!this.hasNode(n)) {
	                throw "The given node is not part of this graph.";
	            }
	            return n.getChildren();
	        },

	        /**
	         * Adds a new link to the graph between the given nodes.
	         */
	        addLink: function (sourceOrLink, target, owner) {

	            if (Utils.isUndefined(sourceOrLink)) {
	                throw "The source of the link is not defined.";
	            }
	            if (Utils.isUndefined(target)) {
	                // can only be undefined if the first one is a Link
	                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;
	            }

	            /*newLink.source.outgoing.push(newLink);
	             newLink.source.links.push(newLink);
	             newLink.target.incoming.push(newLink);
	             newLink.target.links.push(newLink);*/

	            this.links.push(newLink);

	            return newLink;
	        },

	        /**
	         * Removes all the links in this graph.
	         */
	        removeAllLinks: function () {
	            while (this.links.length > 0) {
	                var link = this.links[0];
	                this.removeLink(link);
	            }
	        },

	        /**
	         * Adds the given link to the current graph.
	         */
	        addExistingLink: function (link) {

	            if (this.hasLink(link)) {
	                return;
	            }
	            this.links.push(link);
	            if (this.hasNode(link.source.id)) {
	                // priority to the existing node with the id even if other props are different
	                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);
	            }

	            /*  if (!link.source.outgoing.contains(link)) {
	             link.source.outgoing.push(link);
	             }
	             if (!link.source.links.contains(link)) {
	             link.source.links.push(link);
	             }
	             if (!link.target.incoming.contains(link)) {
	             link.target.incoming.push(link);
	             }
	             if (!link.target.links.contains(link)) {
	             link.target.links.push(link);
	             }*/
	        },

	        /**
	         * Returns whether the given identifier or Link is part of this graph.
	         * @param linkOrId An identifier or a Link object.
	         * @returns {*}
	         */
	        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.";
	        },
	        /**
	         * Gets the node with the specified Id or null if not part of this graph.
	         */
	        getNode: function (nodeOrId) {
	            var id = nodeOrId.id || nodeOrId;
	            if (this._nodeMap.containsKey(id)) {
	                return this._nodeMap.get(id);
	            }
	        },

	        /**
	         * Returns whether the given node or node Id is part of this graph.
	         */
	        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);
	        },

	        /**
	         * Removes the given node from this graph.
	         * The node can be specified as an object or as an identifier (string).
	         */
	        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.";
	            }
	        },

	        /**
	         * Returns whether the given nodes are connected with a least one link independently of the direction.
	         */
	        areConnected: function (n1, n2) {
	            return Utils.any(this.links, function (link) {
	                return link.source == n1 && link.target == n2 || link.source == n2 && link.target == n1;
	            });
	        },

	        /**
	         * Removes the given link from this graph.
	         */
	        removeLink: function (link) {
	            /*    if (!this.links.contains(link)) {
	             throw "The given link is not part of the Graph.";
	             }
	             */
	            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);
	        },

	        /**
	         * Adds a new node to this graph, if not already present.
	         * The node can be an existing Node or the identifier of a new node.
	         * No error is thrown if the node is already there and the existing one is returned.
	         */
	        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);
	                }
	                // todo: ensure that the param is a Node?
	                newNode = nodeOrId;
	            }

	            if (Utils.isDefined(layoutRect)) {
	                newNode.bounds(layoutRect);
	            }

	            if (Utils.isDefined(owner)) {
	                newNode.owner = owner;
	            }
	            this._addNode(newNode);
	            return newNode;
	        },

	        /**
	         * Adds the given Node and its outgoing links.
	         */
	        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);
	        },

	        /**
	         * Sets the 'index' property on the links and nodes of this graph.
	         */
	        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;
	            }
	        },

	        /**
	         * Returns a clone of this graph.
	         */
	        clone: function (saveMapping) {
	            var copy = new Graph();
	            var save = Utils.isDefined(saveMapping) && saveMapping === true;
	            if (save) {
	                copy.nodeMap = new Dictionary();
	                copy.linkMap = new Dictionary();
	            }
	            // we need a map even if the saveMapping is not set
	            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;
	        },

	        /**
	         * The parsing allows a quick way to create graphs.
	         *  - ["n1->n2", "n2->n3"]: creates the three nodes and adds the links
	         *  - ["n1->n2", {id: "QSDF"}, "n2->n3"]: same as previous but also performs a deep extend of the link between n1 and n2 with the given object.
	         */
	        linearize: function (addIds) {
	            return Graph.Utils.linearize(this, addIds);
	        },

	        /**
	         * Performs a depth-first traversal starting at the given node.
	         * @param startNode a node or id of a node in this graph
	         * @param action
	         */
	        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);// case the given one is an Id
	            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);
	            }
	        },

	        /**
	         * Performs a breadth-first traversal starting at the given node.
	         * @param startNode a node or id of a node in this graph
	         * @param action
	         */
	        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);// case the given one is an Id
	            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);
	                }
	            }
	        },

	        /**
	         * This is the classic Tarjan algorithm for strongly connected components.
	         * See e.g. http://en.wikipedia.org/wiki/Tarjan's_strongly_connected_components_algorithm
	         * @param excludeSingleItems Whether isolated nodes should be excluded from the analysis.
	         * @param node The start node from which the analysis starts.
	         * @param indices  Numbers the nodes consecutively in the order in which they are discovered.
	         * @param lowLinks The smallest index of any node known to be reachable from the node, including the node itself
	         * @param connected The current component.
	         * @param stack The bookkeeping stack of things to visit.
	         * @param index The counter of visited nodes used to assign the indices.
	         * @private
	         */
	        _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 v is a root node, pop the stack and generate a strong component
	            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);
	                }
	            }
	        },

	        /**
	         * Returns the cycles found in this graph.
	         * The returned arrays consist of the nodes which are strongly coupled.
	         * @param excludeSingleItems Whether isolated nodes should be excluded.
	         * @returns {Array} The array of cycles found.
	         */
	        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;
	        },

	        /**
	         * Returns whether this graph is acyclic.
	         * @returns {*}
	         */
	        isAcyclic: function () {
	            return Utils.isEmpty(this.findCycles());
	        },

	        /**
	         * Returns whether the given graph is a subgraph of this one.
	         * @param other Another graph instance.
	         */
	        isSubGraph: function (other) {
	            var otherArray = other.linearize();
	            var thisArray = this.linearize();
	            return Utils.all(otherArray, function (s) {
	                return contains(thisArray, s);
	            });
	        },

	        /**
	         *  Makes an acyclic graph from the current (connected) one.
	         * * @returns {Array} The reversed links.
	         */
	        makeAcyclic: function () {
	            // if empty or almost empty
	            if (this.isEmpty() || this.nodes.length <= 1 || this.links.length <= 1) {
	                return [];
	            }
	            // singular case of just two nodes
	            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); // copy.nodeMap tells you the mapping
	            var N = this.nodes.length;

	            var intensityCatalog = new Dictionary();

	            /**
	             * If there are both incoming and outgoing links this will return the flow intensity (out-in).
	             * Otherwise the node acts as a flow source with N specifying the (equal) intensity.
	             * @param node
	             * @returns {number}
	             */
	            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;
	                }
	            };

	            /**
	             * Collects the nodes with the same intensity.
	             * @param node
	             * @param intensityCatalog
	             */
	            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); // nodes without outgoings
	                    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);
	                    }
	                }

	                // move sources to sourceStack
	                if (intensityCatalog.containsKey(N - 2)) {
	                    var sources = intensityCatalog.get(N - 2); // nodes without incomings
	                    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;
	        }
	    });

	    /**
	     * A collection of predefined graphs for demo and testing purposes.
	     */
	    Graph.Predefined = {
	        /**
	         * Eight-shapes graph all connected in a cycle.
	         * @returns {*}
	         * @constructor
	         */
	        EightGraph: function () {
	            return Graph.Utils.parse([ "1->2", "2->3", "3->4", "4->1", "3->5", "5->6", "6->7", "7->3"]);
	        },

	        /**
	         * Creates a typical mindmap diagram.
	         * @returns {*}
	         * @constructor
	         */
	        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"]);
	        },

	        /**
	         * Three nodes connected in a cycle.
	         * @returns {*}
	         * @constructor
	         */
	        ThreeGraph: function () {
	            return Graph.Utils.parse([ "1->2", "2->3", "3->1"]);
	        },

	        /**
	         * A tree with each node having two children.
	         * @param levels How many levels the binary tree should have.
	         * @returns {diagram.Graph}
	         * @constructor
	         */
	        BinaryTree: function (levels) {
	            if (Utils.isUndefined(levels)) {
	                levels = 5;
	            }
	            return Graph.Utils.createBalancedTree(levels, 2);
	        },

	        /**
	         * A linear graph (discrete line segment).
	         * @param length How many segments (the node count is hence (length+1)).
	         * @returns {diagram.Graph}
	         * @constructor
	         */
	        Linear: function (length) {
	            if (Utils.isUndefined(length)) {
	                length = 10;
	            }
	            return Graph.Utils.createBalancedTree(length, 1);
	        },

	        /**
	         * A standard tree-graph with the specified levels and children (siblings) count.
	         * Note that for a balanced tree of level N and sibling count s, counting the root as level zero:
	         *  - NodeCount = (1-s^(N+1))/(1-s)]
	         *  - LinkCount = s.(1-s^N)/(1-s)
	         * @param levels How many levels the tree should have.
	         * @param siblingsCount How many siblings each level should have.
	         * @returns {diagram.Graph}
	         * @constructor
	         */
	        Tree: function (levels, siblingsCount) {
	            return Graph.Utils.createBalancedTree(levels, siblingsCount);
	        },

	        /**
	         * Creates a forest.
	         * Note that for a balanced forest of level N, sibling count s and tree count t, counting the root as level zero:
	         *  - NodeCount = t.(1-s^(N+1))/(1-s)]
	         *  - LinkCount = t.s.(1-s^N)/(1-s)
	         * @param levels How many levels the tree should have.
	         * @param siblingsCount How many siblings each level should have.
	         * @param trees The amount of trees the forest should have.
	         * @returns {diagram.Graph}
	         * @constructor
	         */
	        Forest: function (levels, siblingsCount, trees) {
	            return Graph.Utils.createBalancedForest(levels, siblingsCount, trees);
	        },

	        /**
	         * A workflow-like graph with cycles.
	         * @returns {*}
	         * @constructor
	         */
	        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"]
	            );
	        },

	        /**
	         * A grid graph with the direction of the links avoiding cycles.
	         * Node count: (n+1).(m+1)
	         * Link count: n.(m+1) + m.(n+1)
	         * @param n Horizontal count of grid cells. If zero this will result in a linear graph.
	         * @param m Vertical count of grid cells. If zero this will result in a linear graph.
	         * @constructor
	         */
	        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++) {
	                    // using x-y coordinates to name the nodes
	                    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 generation and other utilities.
	     */
	    Graph.Utils = {
	        /**
	         * The parsing allows a quick way to create graphs.
	         *  - ["n1->n2", "n2->n3"]: creates the three nodes and adds the links
	         *  - ["n1->n2", {id: "id177"}, "n2->n3"]: same as previous but also performs a deep extend of the link between n1 and n2 with the given object.
	         */
	        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)) // link spec
	                {
	                    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;
	        },

	        /**
	         * Returns a linearized representation of the given Graph.
	         * See also the Graph.Utils.parse method for the inverse operation.
	         */
	        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;
	        },

	        /**
	         * The method used by the diagram creation to instantiate a shape.
	         * @param kendoDiagram The Kendo diagram where the diagram will be created.
	         * @param p The position at which to place the shape.
	         * @param shapeDefaults Optional Shape options.
	         * @param id Optional identifier of the shape.
	         * @returns {*}
	         * @private
	         */
	        _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);
	        },
	        /**
	         * The method used by the diagram creation to instantiate a connection.
	         * @param diagram he Kendo diagram where the diagram will be created.
	         * @param from The source shape.
	         * @param to The target shape.
	         * @param options Optional Connection options.
	         * @returns {*}
	         * @private
	         */
	        _addConnection: function (diagram, from, to, options) {
	            return diagram.connect(from, to, options);
	        },

	        /**
	         * Creates a diagram from the given Graph.
	         * @param diagram The Kendo diagram where the diagram will be created.
	         * @param graph The graph structure defining the diagram.
	         */
	        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") {
	                    /* kendo.deepExtend(opt,
	                     {
	                     fill: "Orange",
	                     data: 'circle',
	                     width: 100,
	                     height: 100,
	                     center: new Point(50, 50)
	                     });*/
	                }
	                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);
	                //shape.content(node.id);

	                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));
	                }
	            }
	        },

	        /**
	         * Creates a balanced tree with the specified number of levels and siblings count.
	         * Note that for a balanced tree of level N and sibling count s, counting the root as level zero:
	         *  - NodeCount = (1-s^(N+1))/(1-s)]
	         *  - LinkCount = s.(1-s^N)/(1-s)
	         * @param levels How many levels the tree should have.
	         * @param siblingsCount How many siblings each level should have.
	         * @returns {diagram.Graph}
	         */
	        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;
	        },

	        /**
	         * Creates a balanced tree with the specified number of levels and siblings count.
	         * Note that for a balanced forest of level N, sibling count s and tree count t, counting the root as level zero:
	         *  - NodeCount = t.(1-s^(N+1))/(1-s)]
	         *  - LinkCount = t.s.(1-s^N)/(1-s)
	         * @param levels How many levels the tree should have.
	         * @param siblingsCount How many siblings each level should have.
	         * @returns {diagram.Graph}
	         * @param treeCount The number of trees the forest should have.
	         */
	        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;
	        },

	        /**
	         * Creates a random graph (uniform distribution) with the specified amount of nodes.
	         * @param nodeCount The amount of nodes the random graph should have.
	         * @param maxIncidence The maximum allowed degree of the nodes.
	         * @param isTree Whether the return graph should be a tree (default: false).
	         * @returns {diagram.Graph}
	         */
	        createRandomConnectedGraph: function (nodeCount, maxIncidence, isTree) {

	            /* Swa's Mathematica export of random Bernoulli graphs
	             gr[n_,p_]:=Module[{g=RandomGraph[BernoulliGraphDistribution[n,p],VertexLabels->"Name",DirectedEdges->True]},
	             While[Not[ConnectedGraphQ[g]],g=RandomGraph[BernoulliGraphDistribution[n,p],VertexLabels->"Name",DirectedEdges->True]];g];
	             project[a_]:=("\""<>ToString[Part[#,1]]<>"->"<>ToString[Part[#,2]]<>"\"")&     @ a;
	             export[g_]:=project/@ EdgeList[g]
	             g = gr[12,.1]
	             export [g]
	             */

	            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) {
	                // random tree
	                for (var i = 1; i < nodeCount; i++) {
	                    var poolNode = g.takeRandomNode([], maxIncidence);
	                    if (!poolNode) {
	                        //failed to find one so the graph will have less nodes than specified
	                        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;
	            }
	        },

	        /**
	         * Generates a random diagram.
	         * @param diagram The host diagram.
	         * @param shapeCount The number of shapes the random diagram should contain.
	         * @param maxIncidence The maximum degree the shapes can have.
	         * @param isTree Whether the generated diagram should be a tree
	         * @param layoutType The optional layout type to apply after the diagram is generated.
	         */
	        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);

	}, __webpack_require__(3));


/***/ }),

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

	module.exports = require("./utils");

/***/ })

/******/ });