/*! JointJS v0.9.7 - JavaScript diagramming library 2016-04-20 This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. */ (function(root, factory) { if (typeof define === 'function' && define.amd) { // AMD. Register as an anonymous module. define([], factory); } else if (typeof exports === 'object') { // Node. Does not work with strict CommonJS, but // only CommonJS-like environments that support module.exports, // like Node. module.exports = factory(); } else { // Browser globals. root.g = factory(); } }(this, function() { // Geometry library. // (c) 2011-2015 client IO var g = (function() { // Declare shorthands to the most used math functions. var math = Math; var abs = math.abs; var cos = math.cos; var sin = math.sin; var sqrt = math.sqrt; var mmin = math.min; var mmax = math.max; var atan = math.atan; var atan2 = math.atan2; var acos = math.acos; var round = math.round; var floor = math.floor; var PI = math.PI; var random = math.random; var toDeg = function(rad) { return (180 * rad / PI) % 360; }; var toRad = function(deg, over360) { over360 = over360 || false; deg = over360 ? deg : (deg % 360); return deg * PI / 180; }; var snapToGrid = function(val, gridSize) { return gridSize * Math.round(val / gridSize); }; var normalizeAngle = function(angle) { return (angle % 360) + (angle < 0 ? 360 : 0); }; // Point // ----- // Point is the most basic object consisting of x/y coordinate,. // Possible instantiations are: // * `point(10, 20)` // * `new point(10, 20)` // * `point('10 20')` // * `point(point(10, 20))` function point(x, y) { if (!(this instanceof point)) return new point(x, y); var xy; if (y === undefined && Object(x) !== x) { xy = x.split(x.indexOf('@') === -1 ? ' ' : '@'); this.x = parseInt(xy[0], 10); this.y = parseInt(xy[1], 10); } else if (Object(x) === x) { this.x = x.x; this.y = x.y; } else { this.x = x; this.y = y; } } point.prototype = { toString: function() { return this.x + '@' + this.y; }, // If point lies outside rectangle `r`, return the nearest point on the boundary of rect `r`, // otherwise return point itself. // (see Squeak Smalltalk, Point>>adhereTo:) adhereToRect: function(r) { if (r.containsPoint(this)) { return this; } this.x = mmin(mmax(this.x, r.x), r.x + r.width); this.y = mmin(mmax(this.y, r.y), r.y + r.height); return this; }, // Compute the angle between me and `p` and the x axis. // (cartesian-to-polar coordinates conversion) // Return theta angle in degrees. theta: function(p) { p = point(p); // Invert the y-axis. var y = -(p.y - this.y); var x = p.x - this.x; // Makes sure that the comparison with zero takes rounding errors into account. var PRECISION = 10; // Note that `atan2` is not defined for `x`, `y` both equal zero. var rad = (y.toFixed(PRECISION) == 0 && x.toFixed(PRECISION) == 0) ? 0 : atan2(y, x); // Correction for III. and IV. quadrant. if (rad < 0) { rad = 2 * PI + rad; } return 180 * rad / PI; }, // Returns distance between me and point `p`. distance: function(p) { return line(this, p).length(); }, // Returns a manhattan (taxi-cab) distance between me and point `p`. manhattanDistance: function(p) { return abs(p.x - this.x) + abs(p.y - this.y); }, // Offset me by the specified amount. offset: function(dx, dy) { this.x += dx || 0; this.y += dy || 0; return this; }, magnitude: function() { return sqrt((this.x * this.x) + (this.y * this.y)) || 0.01; }, update: function(x, y) { this.x = x || 0; this.y = y || 0; return this; }, round: function(decimals) { this.x = decimals ? this.x.toFixed(decimals) : round(this.x); this.y = decimals ? this.y.toFixed(decimals) : round(this.y); return this; }, // Scale the line segment between (0,0) and me to have a length of len. normalize: function(len) { var s = (len || 1) / this.magnitude(); this.x = s * this.x; this.y = s * this.y; return this; }, difference: function(p) { return point(this.x - p.x, this.y - p.y); }, // Return the bearing between me and point `p`. bearing: function(p) { return line(this, p).bearing(); }, // Converts rectangular to polar coordinates. // An origin can be specified, otherwise it's 0@0. toPolar: function(o) { o = (o && point(o)) || point(0, 0); var x = this.x; var y = this.y; this.x = sqrt((x - o.x) * (x - o.x) + (y - o.y) * (y - o.y)); // r this.y = toRad(o.theta(point(x, y))); return this; }, // Rotate point by angle around origin o. rotate: function(o, angle) { angle = (angle + 360) % 360; this.toPolar(o); this.y += toRad(angle); var p = point.fromPolar(this.x, this.y, o); this.x = p.x; this.y = p.y; return this; }, // Move point on line starting from ref ending at me by // distance distance. move: function(ref, distance) { var theta = toRad(point(ref).theta(this)); return this.offset(cos(theta) * distance, -sin(theta) * distance); }, // Scale point with origin at point `o`. scale: function(sx, sy, o) { o = (o && point(o)) || point(0, 0); this.x = o.x + sx * (this.x - o.x); this.y = o.y + sy * (this.y - o.y); return this; }, // Returns change in angle from my previous position (-dx, -dy) to my new position // relative to ref point. changeInAngle: function(dx, dy, ref) { // Revert the translation and measure the change in angle around x-axis. return point(this).offset(-dx, -dy).theta(ref) - this.theta(ref); }, equals: function(p) { return this.x === p.x && this.y === p.y; }, snapToGrid: function(gx, gy) { this.x = snapToGrid(this.x, gx); this.y = snapToGrid(this.y, gy || gx); return this; }, // Returns a point that is the reflection of me with // the center of inversion in ref point. reflection: function(ref) { return point(ref).move(this, this.distance(ref)); }, clone: function() { return point(this); }, toJSON: function() { return { x: this.x, y: this.y }; } }; // Alternative constructor, from polar coordinates. // @param {number} r Distance. // @param {number} angle Angle in radians. // @param {point} [optional] o Origin. point.fromPolar = function(r, angle, o) { o = (o && point(o)) || point(0, 0); var x = abs(r * cos(angle)); var y = abs(r * sin(angle)); var deg = normalizeAngle(toDeg(angle)); if (deg < 90) { y = -y; } else if (deg < 180) { x = -x; y = -y; } else if (deg < 270) { x = -x; } return point(o.x + x, o.y + y); }; // Create a point with random coordinates that fall into the range `[x1, x2]` and `[y1, y2]`. point.random = function(x1, x2, y1, y2) { return point(floor(random() * (x2 - x1 + 1) + x1), floor(random() * (y2 - y1 + 1) + y1)); }; // Line. // ----- function line(p1, p2) { if (!(this instanceof line)) return new line(p1, p2); this.start = point(p1); this.end = point(p2); } line.prototype = { toString: function() { return this.start.toString() + ' ' + this.end.toString(); }, // @return {double} length of the line length: function() { return sqrt(this.squaredLength()); }, // @return {integer} length without sqrt // @note for applications where the exact length is not necessary (e.g. compare only) squaredLength: function() { var x0 = this.start.x; var y0 = this.start.y; var x1 = this.end.x; var y1 = this.end.y; return (x0 -= x1) * x0 + (y0 -= y1) * y0; }, // @return {point} my midpoint midpoint: function() { return point((this.start.x + this.end.x) / 2, (this.start.y + this.end.y) / 2); }, // @return {point} Point where I'm intersecting l. // @see Squeak Smalltalk, LineSegment>>intersectionWith: intersection: function(l) { var pt1Dir = point(this.end.x - this.start.x, this.end.y - this.start.y); var pt2Dir = point(l.end.x - l.start.x, l.end.y - l.start.y); var det = (pt1Dir.x * pt2Dir.y) - (pt1Dir.y * pt2Dir.x); var deltaPt = point(l.start.x - this.start.x, l.start.y - this.start.y); var alpha = (deltaPt.x * pt2Dir.y) - (deltaPt.y * pt2Dir.x); var beta = (deltaPt.x * pt1Dir.y) - (deltaPt.y * pt1Dir.x); if (det === 0 || alpha * det < 0 || beta * det < 0) { // No intersection found. return null; } if (det > 0) { if (alpha > det || beta > det) { return null; } } else { if (alpha < det || beta < det) { return null; } } return point(this.start.x + (alpha * pt1Dir.x / det), this.start.y + (alpha * pt1Dir.y / det)); }, // @return the bearing (cardinal direction) of the line. For example N, W, or SE. // @returns {String} One of the following bearings : NE, E, SE, S, SW, W, NW, N. bearing: function() { var lat1 = toRad(this.start.y); var lat2 = toRad(this.end.y); var lon1 = this.start.x; var lon2 = this.end.x; var dLon = toRad(lon2 - lon1); var y = sin(dLon) * cos(lat2); var x = cos(lat1) * sin(lat2) - sin(lat1) * cos(lat2) * cos(dLon); var brng = toDeg(atan2(y, x)); var bearings = ['NE', 'E', 'SE', 'S', 'SW', 'W', 'NW', 'N']; var index = brng - 22.5; if (index < 0) index += 360; index = parseInt(index / 45); return bearings[index]; }, // @return {point} my point at 't' <0,1> pointAt: function(t) { var x = (1 - t) * this.start.x + t * this.end.x; var y = (1 - t) * this.start.y + t * this.end.y; return point(x, y); }, // @return {number} the offset of the point `p` from the line. + if the point `p` is on the right side of the line, - if on the left and 0 if on the line. pointOffset: function(p) { // Find the sign of the determinant of vectors (start,end), where p is the query point. return ((this.end.x - this.start.x) * (p.y - this.start.y) - (this.end.y - this.start.y) * (p.x - this.start.x)) / 2; }, clone: function() { return line(this); } }; // Rectangle. // ---------- function rect(x, y, w, h) { if (!(this instanceof rect)) return new rect(x, y, w, h); if (y === undefined) { y = x.y; w = x.width; h = x.height; x = x.x; } this.x = x; this.y = y; this.width = w; this.height = h; } rect.prototype = { toString: function() { return this.origin().toString() + ' ' + this.corner().toString(); }, // @return {boolean} true if rectangles are equal. equals: function(r) { var mr = g.rect(this).normalize(); var nr = g.rect(r).normalize(); return mr.x === nr.x && mr.y === nr.y && mr.width === nr.width && mr.height === nr.height; }, origin: function() { return point(this.x, this.y); }, corner: function() { return point(this.x + this.width, this.y + this.height); }, topRight: function() { return point(this.x + this.width, this.y); }, bottomLeft: function() { return point(this.x, this.y + this.height); }, center: function() { return point(this.x + this.width / 2, this.y + this.height / 2); }, // @return {rect} if rectangles intersect, {null} if not. intersect: function(r) { var myOrigin = this.origin(); var myCorner = this.corner(); var rOrigin = r.origin(); var rCorner = r.corner(); // No intersection found if (rCorner.x <= myOrigin.x || rCorner.y <= myOrigin.y || rOrigin.x >= myCorner.x || rOrigin.y >= myCorner.y) return null; var x = Math.max(myOrigin.x, rOrigin.x); var y = Math.max(myOrigin.y, rOrigin.y); return rect(x, y, Math.min(myCorner.x, rCorner.x) - x, Math.min(myCorner.y, rCorner.y) - y); }, // @return {rect} representing the union of both rectangles. union: function(r) { var myOrigin = this.origin(); var myCorner = this.corner(); var rOrigin = r.origin(); var rCorner = r.corner(); var originX = Math.min(myOrigin.x, rOrigin.x); var originY = Math.min(myOrigin.y, rOrigin.y); var cornerX = Math.max(myCorner.x, rCorner.x); var cornerY = Math.max(myCorner.y, rCorner.y); return rect(originX, originY, cornerX - originX, cornerY - originY); }, // @return {string} (left|right|top|bottom) side which is nearest to point // @see Squeak Smalltalk, Rectangle>>sideNearestTo: sideNearestToPoint: function(p) { p = point(p); var distToLeft = p.x - this.x; var distToRight = (this.x + this.width) - p.x; var distToTop = p.y - this.y; var distToBottom = (this.y + this.height) - p.y; var closest = distToLeft; var side = 'left'; if (distToRight < closest) { closest = distToRight; side = 'right'; } if (distToTop < closest) { closest = distToTop; side = 'top'; } if (distToBottom < closest) { closest = distToBottom; side = 'bottom'; } return side; }, // @return {bool} true if point p is insight me containsPoint: function(p) { p = point(p); if (p.x >= this.x && p.x <= this.x + this.width && p.y >= this.y && p.y <= this.y + this.height) { return true; } return false; }, // @return {bool} true if rectangle `r` is inside me. containsRect: function(r) { var r0 = rect(this).normalize(); var r1 = rect(r).normalize(); var w0 = r0.width; var h0 = r0.height; var w1 = r1.width; var h1 = r1.height; if (!w0 || !h0 || !w1 || !h1) { // At least one of the dimensions is 0 return false; } var x0 = r0.x; var y0 = r0.y; var x1 = r1.x; var y1 = r1.y; w1 += x1; w0 += x0; h1 += y1; h0 += y0; return x0 <= x1 && w1 <= w0 && y0 <= y1 && h1 <= h0; }, // @return {point} a point on my boundary nearest to p // @see Squeak Smalltalk, Rectangle>>pointNearestTo: pointNearestToPoint: function(p) { p = point(p); if (this.containsPoint(p)) { var side = this.sideNearestToPoint(p); switch (side){ case 'right': return point(this.x + this.width, p.y); case 'left': return point(this.x, p.y); case 'bottom': return point(p.x, this.y + this.height); case 'top': return point(p.x, this.y); } } return p.adhereToRect(this); }, // Find point on my boundary where line starting // from my center ending in point p intersects me. // @param {number} angle If angle is specified, intersection with rotated rectangle is computed. intersectionWithLineFromCenterToPoint: function(p, angle) { p = point(p); var center = point(this.x + this.width / 2, this.y + this.height / 2); var result; if (angle) p.rotate(center, angle); // (clockwise, starting from the top side) var sides = [ line(this.origin(), this.topRight()), line(this.topRight(), this.corner()), line(this.corner(), this.bottomLeft()), line(this.bottomLeft(), this.origin()) ]; var connector = line(center, p); for (var i = sides.length - 1; i >= 0; --i) { var intersection = sides[i].intersection(connector); if (intersection !== null) { result = intersection; break; } } if (result && angle) result.rotate(center, -angle); return result; }, // Move and expand me. // @param r {rectangle} representing deltas moveAndExpand: function(r) { this.x += r.x || 0; this.y += r.y || 0; this.width += r.width || 0; this.height += r.height || 0; return this; }, round: function(decimals) { this.x = decimals ? this.x.toFixed(decimals) : round(this.x); this.y = decimals ? this.y.toFixed(decimals) : round(this.y); this.width = decimals ? this.width.toFixed(decimals) : round(this.width); this.height = decimals ? this.height.toFixed(decimals) : round(this.height); return this; }, // Normalize the rectangle; i.e., make it so that it has a non-negative width and height. // If width < 0 the function swaps the left and right corners, // and it swaps the top and bottom corners if height < 0 // like in http://qt-project.org/doc/qt-4.8/qrectf.html#normalized normalize: function() { var newx = this.x; var newy = this.y; var newwidth = this.width; var newheight = this.height; if (this.width < 0) { newx = this.x + this.width; newwidth = -this.width; } if (this.height < 0) { newy = this.y + this.height; newheight = -this.height; } this.x = newx; this.y = newy; this.width = newwidth; this.height = newheight; return this; }, // Find my bounding box when I'm rotated with the center of rotation in the center of me. // @return r {rectangle} representing a bounding box bbox: function(angle) { var theta = toRad(angle || 0); var st = abs(sin(theta)); var ct = abs(cos(theta)); var w = this.width * ct + this.height * st; var h = this.width * st + this.height * ct; return rect(this.x + (this.width - w) / 2, this.y + (this.height - h) / 2, w, h); }, // Scale rectangle with origin at point `o` scale: function(sx, sy, o) { var origin = this.origin().scale(sx, sy, o); this.x = origin.x; this.y = origin.y; this.width *= sx; this.height *= sy; return this; }, snapToGrid: function(gx, gy) { var origin = this.origin().snapToGrid(gx, gy); var corner = this.corner().snapToGrid(gx, gy); this.x = origin.x; this.y = origin.y; this.width = corner.x - origin.x; this.height = corner.y - origin.y; return this; }, clone: function() { return rect(this); }, toJSON: function() { return { x: this.x, y: this.y, width: this.width, height: this.height }; } }; // Ellipse. // -------- function ellipse(c, a, b) { if (!(this instanceof ellipse)) return new ellipse(c, a, b); c = point(c); this.x = c.x; this.y = c.y; this.a = a; this.b = b; } ellipse.prototype = { toString: function() { return point(this.x, this.y).toString() + ' ' + this.a + ' ' + this.b; }, bbox: function() { return rect(this.x - this.a, this.y - this.b, 2 * this.a, 2 * this.b); }, // Find point on me where line from my center to // point p intersects my boundary. // @param {number} angle If angle is specified, intersection with rotated ellipse is computed. intersectionWithLineFromCenterToPoint: function(p, angle) { p = point(p); if (angle) p.rotate(point(this.x, this.y), angle); var dx = p.x - this.x; var dy = p.y - this.y; var result; if (dx === 0) { result = this.bbox().pointNearestToPoint(p); if (angle) return result.rotate(point(this.x, this.y), -angle); return result; } var m = dy / dx; var mSquared = m * m; var aSquared = this.a * this.a; var bSquared = this.b * this.b; var x = sqrt(1 / ((1 / aSquared) + (mSquared / bSquared))); x = dx < 0 ? -x : x; var y = m * x; result = point(this.x + x, this.y + y); if (angle) return result.rotate(point(this.x, this.y), -angle); return result; }, clone: function() { return ellipse(this); } }; // Bezier curve. // ------------- var bezier = { // Cubic Bezier curve path through points. // Ported from C# implementation by Oleg V. Polikarpotchkin and Peter Lee (http://www.codeproject.com/KB/graphics/BezierSpline.aspx). // @param {array} points Array of points through which the smooth line will go. // @return {array} SVG Path commands as an array curveThroughPoints: function(points) { var controlPoints = this.getCurveControlPoints(points); var path = ['M', points[0].x, points[0].y]; for (var i = 0; i < controlPoints[0].length; i++) { path.push('C', controlPoints[0][i].x, controlPoints[0][i].y, controlPoints[1][i].x, controlPoints[1][i].y, points[i + 1].x, points[i + 1].y); } return path; }, // Get open-ended Bezier Spline Control Points. // @param knots Input Knot Bezier spline points (At least two points!). // @param firstControlPoints Output First Control points. Array of knots.length - 1 length. // @param secondControlPoints Output Second Control points. Array of knots.length - 1 length. getCurveControlPoints: function(knots) { var firstControlPoints = []; var secondControlPoints = []; var n = knots.length - 1; var i; // Special case: Bezier curve should be a straight line. if (n == 1) { // 3P1 = 2P0 + P3 firstControlPoints[0] = point((2 * knots[0].x + knots[1].x) / 3, (2 * knots[0].y + knots[1].y) / 3); // P2 = 2P1 – P0 secondControlPoints[0] = point(2 * firstControlPoints[0].x - knots[0].x, 2 * firstControlPoints[0].y - knots[0].y); return [firstControlPoints, secondControlPoints]; } // Calculate first Bezier control points. // Right hand side vector. var rhs = []; // Set right hand side X values. for (i = 1; i < n - 1; i++) { rhs[i] = 4 * knots[i].x + 2 * knots[i + 1].x; } rhs[0] = knots[0].x + 2 * knots[1].x; rhs[n - 1] = (8 * knots[n - 1].x + knots[n].x) / 2.0; // Get first control points X-values. var x = this.getFirstControlPoints(rhs); // Set right hand side Y values. for (i = 1; i < n - 1; ++i) { rhs[i] = 4 * knots[i].y + 2 * knots[i + 1].y; } rhs[0] = knots[0].y + 2 * knots[1].y; rhs[n - 1] = (8 * knots[n - 1].y + knots[n].y) / 2.0; // Get first control points Y-values. var y = this.getFirstControlPoints(rhs); // Fill output arrays. for (i = 0; i < n; i++) { // First control point. firstControlPoints.push(point(x[i], y[i])); // Second control point. if (i < n - 1) { secondControlPoints.push(point(2 * knots [i + 1].x - x[i + 1], 2 * knots[i + 1].y - y[i + 1])); } else { secondControlPoints.push(point((knots[n].x + x[n - 1]) / 2, (knots[n].y + y[n - 1]) / 2)); } } return [firstControlPoints, secondControlPoints]; }, // Solves a tridiagonal system for one of coordinates (x or y) of first Bezier control points. // @param rhs Right hand side vector. // @return Solution vector. getFirstControlPoints: function(rhs) { var n = rhs.length; // `x` is a solution vector. var x = []; var tmp = []; var b = 2.0; x[0] = rhs[0] / b; // Decomposition and forward substitution. for (var i = 1; i < n; i++) { tmp[i] = 1 / b; b = (i < n - 1 ? 4.0 : 3.5) - tmp[i]; x[i] = (rhs[i] - x[i - 1]) / b; } for (i = 1; i < n; i++) { // Backsubstitution. x[n - i - 1] -= tmp[n - i] * x[n - i]; } return x; }, // Solves an inversion problem -- Given the (x, y) coordinates of a point which lies on // a parametric curve x = x(t)/w(t), y = y(t)/w(t), find the parameter value t // which corresponds to that point. // @param control points (start, control start, control end, end) // @return a function accepts a point and returns t. getInversionSolver: function(p0, p1, p2, p3) { var pts = arguments; function l(i, j) { // calculates a determinant 3x3 // [p.x p.y 1] // [pi.x pi.y 1] // [pj.x pj.y 1] var pi = pts[i]; var pj = pts[j]; return function(p) { var w = (i % 3 ? 3 : 1) * (j % 3 ? 3 : 1); var lij = p.x * (pi.y - pj.y) + p.y * (pj.x - pi.x) + pi.x * pj.y - pi.y * pj.x; return w * lij; }; } return function solveInversion(p) { var ct = 3 * l(2, 3)(p1); var c1 = l(1, 3)(p0) / ct; var c2 = -l(2, 3)(p0) / ct; var la = c1 * l(3, 1)(p) + c2 * (l(3, 0)(p) + l(2, 1)(p)) + l(2, 0)(p); var lb = c1 * l(3, 0)(p) + c2 * l(2, 0)(p) + l(1, 0)(p); return lb / (lb - la); }; }, // Divide a Bezier curve into two at point defined by value 't' <0,1>. // Using deCasteljau algorithm. http://math.stackexchange.com/a/317867 // @param control points (start, control start, control end, end) // @return a function accepts t and returns 2 curves each defined by 4 control points. getCurveDivider: function(p0, p1, p2, p3) { return function divideCurve(t) { var l = line(p0, p1).pointAt(t); var m = line(p1, p2).pointAt(t); var n = line(p2, p3).pointAt(t); var p = line(l, m).pointAt(t); var q = line(m, n).pointAt(t); var r = line(p, q).pointAt(t); return [{ p0: p0, p1: l, p2: p, p3: r }, { p0: r, p1: q, p2: n, p3: p3 }]; }; } }; // Scale. var scale = { // Return the `value` from the `domain` interval scaled to the `range` interval. linear: function(domain, range, value) { var domainSpan = domain[1] - domain[0]; var rangeSpan = range[1] - range[0]; return (((value - domain[0]) / domainSpan) * rangeSpan + range[0]) || 0; } }; return { toDeg: toDeg, toRad: toRad, snapToGrid: snapToGrid, normalizeAngle: normalizeAngle, point: point, line: line, rect: rect, ellipse: ellipse, bezier: bezier, scale: scale }; })(); return g; }));