1 /*! JointJS v0.9.3 - JavaScript diagramming library 2015-05-22
4 This Source Code Form is subject to the terms of the Mozilla Public
5 License, v. 2.0. If a copy of the MPL was not distributed with this
6 file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 // (c) 2011-2013 client IO
11 (function(root, factory) {
13 if (typeof define === 'function' && define.amd) {
15 // AMD. Register as an anonymous module.
18 } else if (typeof exports === 'object') {
20 // Node. Does not work with strict CommonJS, but
21 // only CommonJS-like environments that support module.exports,
23 module.exports = factory();
34 // Declare shorthands to the most used math functions.
43 var atan2 = math.atan2;
45 var round = math.round;
46 var floor = math.floor;
48 var random = math.random;
49 var toDeg = function(rad) { return (180 * rad / PI) % 360; };
50 var toRad = function(deg, over360) {
51 over360 = over360 || false;
52 deg = over360 ? deg : (deg % 360);
53 return deg * PI / 180;
55 var snapToGrid = function(val, gridSize) { return gridSize * Math.round(val / gridSize); };
56 var normalizeAngle = function(angle) { return (angle % 360) + (angle < 0 ? 360 : 0); };
61 // Point is the most basic object consisting of x/y coordinate,.
63 // Possible instantiations are:
66 // * `new point(10, 20)`
68 // * `point(point(10, 20))`
69 function point(x, y) {
70 if (!(this instanceof point))
71 return new point(x, y);
73 if (y === undefined && Object(x) !== x) {
74 xy = x.split(x.indexOf('@') === -1 ? ' ' : '@');
75 this.x = parseInt(xy[0], 10);
76 this.y = parseInt(xy[1], 10);
77 } else if (Object(x) === x) {
87 toString: function() {
88 return this.x + '@' + this.y;
90 // If point lies outside rectangle `r`, return the nearest point on the boundary of rect `r`,
91 // otherwise return point itself.
92 // (see Squeak Smalltalk, Point>>adhereTo:)
93 adhereToRect: function(r) {
94 if (r.containsPoint(this)) {
97 this.x = mmin(mmax(this.x, r.x), r.x + r.width);
98 this.y = mmin(mmax(this.y, r.y), r.y + r.height);
101 // Compute the angle between me and `p` and the x axis.
102 // (cartesian-to-polar coordinates conversion)
103 // Return theta angle in degrees.
106 // Invert the y-axis.
107 var y = -(p.y - this.y);
108 var x = p.x - this.x;
109 // Makes sure that the comparison with zero takes rounding errors into account.
111 // Note that `atan2` is not defined for `x`, `y` both equal zero.
112 var rad = (y.toFixed(PRECISION) == 0 && x.toFixed(PRECISION) == 0) ? 0 : atan2(y, x);
114 // Correction for III. and IV. quadrant.
118 return 180 * rad / PI;
120 // Returns distance between me and point `p`.
121 distance: function(p) {
122 return line(this, p).length();
124 // Returns a manhattan (taxi-cab) distance between me and point `p`.
125 manhattanDistance: function(p) {
126 return abs(p.x - this.x) + abs(p.y - this.y);
128 // Offset me by the specified amount.
129 offset: function(dx, dy) {
134 magnitude: function() {
135 return sqrt((this.x * this.x) + (this.y * this.y)) || 0.01;
137 update: function(x, y) {
142 round: function(decimals) {
143 this.x = decimals ? this.x.toFixed(decimals) : round(this.x);
144 this.y = decimals ? this.y.toFixed(decimals) : round(this.y);
147 // Scale the line segment between (0,0) and me to have a length of len.
148 normalize: function(len) {
149 var s = (len || 1) / this.magnitude();
154 difference: function(p) {
155 return point(this.x - p.x, this.y - p.y);
157 // Return the bearing between me and point `p`.
158 bearing: function(p) {
159 return line(this, p).bearing();
161 // Converts rectangular to polar coordinates.
162 // An origin can be specified, otherwise it's 0@0.
163 toPolar: function(o) {
164 o = (o && point(o)) || point(0, 0);
167 this.x = sqrt((x - o.x) * (x - o.x) + (y - o.y) * (y - o.y)); // r
168 this.y = toRad(o.theta(point(x, y)));
171 // Rotate point by angle around origin o.
172 rotate: function(o, angle) {
173 angle = (angle + 360) % 360;
175 this.y += toRad(angle);
176 var p = point.fromPolar(this.x, this.y, o);
181 // Move point on line starting from ref ending at me by
182 // distance distance.
183 move: function(ref, distance) {
184 var theta = toRad(point(ref).theta(this));
185 return this.offset(cos(theta) * distance, -sin(theta) * distance);
187 // Returns change in angle from my previous position (-dx, -dy) to my new position
188 // relative to ref point.
189 changeInAngle: function(dx, dy, ref) {
190 // Revert the translation and measure the change in angle around x-axis.
191 return point(this).offset(-dx, -dy).theta(ref) - this.theta(ref);
193 equals: function(p) {
194 return this.x === p.x && this.y === p.y;
196 snapToGrid: function(gx, gy) {
197 this.x = snapToGrid(this.x, gx);
198 this.y = snapToGrid(this.y, gy || gx);
201 // Returns a point that is the reflection of me with
202 // the center of inversion in ref point.
203 reflection: function(ref) {
204 return point(ref).move(this, this.distance(ref));
207 // Alternative constructor, from polar coordinates.
208 // @param {number} r Distance.
209 // @param {number} angle Angle in radians.
210 // @param {point} [optional] o Origin.
211 point.fromPolar = function(r, angle, o) {
212 o = (o && point(o)) || point(0, 0);
213 var x = abs(r * cos(angle));
214 var y = abs(r * sin(angle));
215 var deg = normalizeAngle(toDeg(angle));
219 } else if (deg < 180) {
222 } else if (deg < 270) {
226 return point(o.x + x, o.y + y);
229 // Create a point with random coordinates that fall into the range `[x1, x2]` and `[y1, y2]`.
230 point.random = function(x1, x2, y1, y2) {
231 return point(floor(random() * (x2 - x1 + 1) + x1), floor(random() * (y2 - y1 + 1) + y1));
236 function line(p1, p2) {
237 if (!(this instanceof line))
238 return new line(p1, p2);
239 this.start = point(p1);
240 this.end = point(p2);
244 toString: function() {
245 return this.start.toString() + ' ' + this.end.toString();
247 // @return {double} length of the line
249 return sqrt(this.squaredLength());
251 // @return {integer} length without sqrt
252 // @note for applications where the exact length is not necessary (e.g. compare only)
253 squaredLength: function() {
254 var x0 = this.start.x;
255 var y0 = this.start.y;
258 return (x0 -= x1) * x0 + (y0 -= y1) * y0;
260 // @return {point} my midpoint
261 midpoint: function() {
262 return point((this.start.x + this.end.x) / 2,
263 (this.start.y + this.end.y) / 2);
265 // @return {point} Point where I'm intersecting l.
266 // @see Squeak Smalltalk, LineSegment>>intersectionWith:
267 intersection: function(l) {
268 var pt1Dir = point(this.end.x - this.start.x, this.end.y - this.start.y);
269 var pt2Dir = point(l.end.x - l.start.x, l.end.y - l.start.y);
270 var det = (pt1Dir.x * pt2Dir.y) - (pt1Dir.y * pt2Dir.x);
271 var deltaPt = point(l.start.x - this.start.x, l.start.y - this.start.y);
272 var alpha = (deltaPt.x * pt2Dir.y) - (deltaPt.y * pt2Dir.x);
273 var beta = (deltaPt.x * pt1Dir.y) - (deltaPt.y * pt1Dir.x);
278 // No intersection found.
282 if (alpha > det || beta > det) {
286 if (alpha < det || beta < det) {
290 return point(this.start.x + (alpha * pt1Dir.x / det),
291 this.start.y + (alpha * pt1Dir.y / det));
294 // @return the bearing (cardinal direction) of the line. For example N, W, or SE.
295 // @returns {String} One of the following bearings : NE, E, SE, S, SW, W, NW, N.
296 bearing: function() {
298 var lat1 = toRad(this.start.y);
299 var lat2 = toRad(this.end.y);
300 var lon1 = this.start.x;
301 var lon2 = this.end.x;
302 var dLon = toRad(lon2 - lon1);
303 var y = sin(dLon) * cos(lat2);
304 var x = cos(lat1) * sin(lat2) - sin(lat1) * cos(lat2) * cos(dLon);
305 var brng = toDeg(atan2(y, x));
307 var bearings = ['NE', 'E', 'SE', 'S', 'SW', 'W', 'NW', 'N'];
309 var index = brng - 22.5;
312 index = parseInt(index / 45);
314 return bearings[index];
317 // @return {point} my point at 't' <0,1>
318 pointAt: function(t) {
319 var x = (1 - t) * this.start.x + t * this.end.x;
320 var y = (1 - t) * this.start.y + t * this.end.y;
324 // @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.
325 pointOffset: function(p) {
326 // Find the sign of the determinant of vectors (start,end), where p is the query point.
327 return ((this.end.x - this.start.x) * (p.y - this.start.y) - (this.end.y - this.start.y) * (p.x - this.start.x)) / 2;
333 function rect(x, y, w, h) {
334 if (!(this instanceof rect))
335 return new rect(x, y, w, h);
336 if (y === undefined) {
349 toString: function() {
350 return this.origin().toString() + ' ' + this.corner().toString();
353 return point(this.x, this.y);
356 return point(this.x + this.width, this.y + this.height);
358 topRight: function() {
359 return point(this.x + this.width, this.y);
361 bottomLeft: function() {
362 return point(this.x, this.y + this.height);
365 return point(this.x + this.width / 2, this.y + this.height / 2);
367 // @return {boolean} true if rectangles intersect
368 intersect: function(r) {
369 var myOrigin = this.origin();
370 var myCorner = this.corner();
371 var rOrigin = r.origin();
372 var rCorner = r.corner();
374 if (rCorner.x <= myOrigin.x ||
375 rCorner.y <= myOrigin.y ||
376 rOrigin.x >= myCorner.x ||
377 rOrigin.y >= myCorner.y) return false;
380 // @return {string} (left|right|top|bottom) side which is nearest to point
381 // @see Squeak Smalltalk, Rectangle>>sideNearestTo:
382 sideNearestToPoint: function(p) {
384 var distToLeft = p.x - this.x;
385 var distToRight = (this.x + this.width) - p.x;
386 var distToTop = p.y - this.y;
387 var distToBottom = (this.y + this.height) - p.y;
388 var closest = distToLeft;
391 if (distToRight < closest) {
392 closest = distToRight;
395 if (distToTop < closest) {
399 if (distToBottom < closest) {
400 closest = distToBottom;
405 // @return {bool} true if point p is insight me
406 containsPoint: function(p) {
408 if (p.x >= this.x && p.x <= this.x + this.width &&
409 p.y >= this.y && p.y <= this.y + this.height) {
414 // Algorithm ported from java.awt.Rectangle from OpenJDK.
415 // @return {bool} true if rectangle `r` is inside me.
416 containsRect: function(r) {
417 var nr = rect(r).normalize();
424 if ((w | h | W | H) < 0) {
425 // At least one of the dimensions is negative...
428 // Note: if any dimension is zero, tests below must return false...
431 if (X < x || Y < y) {
437 // X+W overflowed or W was zero, return false if...
438 // either original w or W was zero or
439 // x+w did not overflow or
440 // the overflowed x+w is smaller than the overflowed X+W
441 if (w >= x || W > w) return false;
443 // X+W did not overflow and W was not zero, return false if...
444 // original w was zero or
445 // x+w did not overflow and x+w is smaller than X+W
446 if (w >= x && W > w) return false;
451 if (h >= y || H > h) return false;
453 if (h >= y && H > h) return false;
457 // @return {point} a point on my boundary nearest to p
458 // @see Squeak Smalltalk, Rectangle>>pointNearestTo:
459 pointNearestToPoint: function(p) {
461 if (this.containsPoint(p)) {
462 var side = this.sideNearestToPoint(p);
464 case 'right': return point(this.x + this.width, p.y);
465 case 'left': return point(this.x, p.y);
466 case 'bottom': return point(p.x, this.y + this.height);
467 case 'top': return point(p.x, this.y);
470 return p.adhereToRect(this);
472 // Find point on my boundary where line starting
473 // from my center ending in point p intersects me.
474 // @param {number} angle If angle is specified, intersection with rotated rectangle is computed.
475 intersectionWithLineFromCenterToPoint: function(p, angle) {
477 var center = point(this.x + this.width / 2, this.y + this.height / 2);
479 if (angle) p.rotate(center, angle);
481 // (clockwise, starting from the top side)
483 line(this.origin(), this.topRight()),
484 line(this.topRight(), this.corner()),
485 line(this.corner(), this.bottomLeft()),
486 line(this.bottomLeft(), this.origin())
488 var connector = line(center, p);
490 for (var i = sides.length - 1; i >= 0; --i) {
491 var intersection = sides[i].intersection(connector);
492 if (intersection !== null) {
493 result = intersection;
497 if (result && angle) result.rotate(center, -angle);
500 // Move and expand me.
501 // @param r {rectangle} representing deltas
502 moveAndExpand: function(r) {
505 this.width += r.width || 0;
506 this.height += r.height || 0;
509 round: function(decimals) {
510 this.x = decimals ? this.x.toFixed(decimals) : round(this.x);
511 this.y = decimals ? this.y.toFixed(decimals) : round(this.y);
512 this.width = decimals ? this.width.toFixed(decimals) : round(this.width);
513 this.height = decimals ? this.height.toFixed(decimals) : round(this.height);
516 // Normalize the rectangle; i.e., make it so that it has a non-negative width and height.
517 // If width < 0 the function swaps the left and right corners,
518 // and it swaps the top and bottom corners if height < 0
519 // like in http://qt-project.org/doc/qt-4.8/qrectf.html#normalized
520 normalize: function() {
523 var newwidth = this.width;
524 var newheight = this.height;
525 if (this.width < 0) {
526 newx = this.x + this.width;
527 newwidth = -this.width;
529 if (this.height < 0) {
530 newy = this.y + this.height;
531 newheight = -this.height;
535 this.width = newwidth;
536 this.height = newheight;
539 // Find my bounding box when I'm rotated with the center of rotation in the center of me.
540 // @return r {rectangle} representing a bounding box
541 bbox: function(angle) {
542 var theta = toRad(angle || 0);
543 var st = abs(sin(theta));
544 var ct = abs(cos(theta));
545 var w = this.width * ct + this.height * st;
546 var h = this.width * st + this.height * ct;
547 return rect(this.x + (this.width - w) / 2, this.y + (this.height - h) / 2, w, h);
553 function ellipse(c, a, b) {
554 if (!(this instanceof ellipse))
555 return new ellipse(c, a, b);
563 ellipse.prototype = {
564 toString: function() {
565 return point(this.x, this.y).toString() + ' ' + this.a + ' ' + this.b;
568 return rect(this.x - this.a, this.y - this.b, 2 * this.a, 2 * this.b);
570 // Find point on me where line from my center to
571 // point p intersects my boundary.
572 // @param {number} angle If angle is specified, intersection with rotated ellipse is computed.
573 intersectionWithLineFromCenterToPoint: function(p, angle) {
575 if (angle) p.rotate(point(this.x, this.y), angle);
576 var dx = p.x - this.x;
577 var dy = p.y - this.y;
580 result = this.bbox().pointNearestToPoint(p);
581 if (angle) return result.rotate(point(this.x, this.y), -angle);
585 var mSquared = m * m;
586 var aSquared = this.a * this.a;
587 var bSquared = this.b * this.b;
588 var x = sqrt(1 / ((1 / aSquared) + (mSquared / bSquared)));
592 result = point(this.x + x, this.y + y);
593 if (angle) return result.rotate(point(this.x, this.y), -angle);
601 // Cubic Bezier curve path through points.
602 // Ported from C# implementation by Oleg V. Polikarpotchkin and Peter Lee (http://www.codeproject.com/KB/graphics/BezierSpline.aspx).
603 // @param {array} points Array of points through which the smooth line will go.
604 // @return {array} SVG Path commands as an array
605 curveThroughPoints: function(points) {
606 var controlPoints = this.getCurveControlPoints(points);
607 var path = ['M', points[0].x, points[0].y];
609 for (var i = 0; i < controlPoints[0].length; i++) {
610 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);
615 // Get open-ended Bezier Spline Control Points.
616 // @param knots Input Knot Bezier spline points (At least two points!).
617 // @param firstControlPoints Output First Control points. Array of knots.length - 1 length.
618 // @param secondControlPoints Output Second Control points. Array of knots.length - 1 length.
619 getCurveControlPoints: function(knots) {
620 var firstControlPoints = [];
621 var secondControlPoints = [];
622 var n = knots.length - 1;
625 // Special case: Bezier curve should be a straight line.
628 firstControlPoints[0] = point((2 * knots[0].x + knots[1].x) / 3,
629 (2 * knots[0].y + knots[1].y) / 3);
631 secondControlPoints[0] = point(2 * firstControlPoints[0].x - knots[0].x,
632 2 * firstControlPoints[0].y - knots[0].y);
633 return [firstControlPoints, secondControlPoints];
636 // Calculate first Bezier control points.
637 // Right hand side vector.
640 // Set right hand side X values.
641 for (i = 1; i < n - 1; i++) {
642 rhs[i] = 4 * knots[i].x + 2 * knots[i + 1].x;
644 rhs[0] = knots[0].x + 2 * knots[1].x;
645 rhs[n - 1] = (8 * knots[n - 1].x + knots[n].x) / 2.0;
646 // Get first control points X-values.
647 var x = this.getFirstControlPoints(rhs);
649 // Set right hand side Y values.
650 for (i = 1; i < n - 1; ++i) {
651 rhs[i] = 4 * knots[i].y + 2 * knots[i + 1].y;
653 rhs[0] = knots[0].y + 2 * knots[1].y;
654 rhs[n - 1] = (8 * knots[n - 1].y + knots[n].y) / 2.0;
655 // Get first control points Y-values.
656 var y = this.getFirstControlPoints(rhs);
658 // Fill output arrays.
659 for (i = 0; i < n; i++) {
660 // First control point.
661 firstControlPoints.push(point(x[i], y[i]));
662 // Second control point.
664 secondControlPoints.push(point(2 * knots [i + 1].x - x[i + 1],
665 2 * knots[i + 1].y - y[i + 1]));
667 secondControlPoints.push(point((knots[n].x + x[n - 1]) / 2,
668 (knots[n].y + y[n - 1]) / 2));
671 return [firstControlPoints, secondControlPoints];
674 // Solves a tridiagonal system for one of coordinates (x or y) of first Bezier control points.
675 // @param rhs Right hand side vector.
676 // @return Solution vector.
677 getFirstControlPoints: function(rhs) {
679 // `x` is a solution vector.
685 // Decomposition and forward substitution.
686 for (var i = 1; i < n; i++) {
688 b = (i < n - 1 ? 4.0 : 3.5) - tmp[i];
689 x[i] = (rhs[i] - x[i - 1]) / b;
691 for (i = 1; i < n; i++) {
693 x[n - i - 1] -= tmp[n - i] * x[n - i];
698 // Solves an inversion problem -- Given the (x, y) coordinates of a point which lies on
699 // a parametric curve x = x(t)/w(t), y = y(t)/w(t), find the parameter value t
700 // which corresponds to that point.
701 // @param control points (start, control start, control end, end)
702 // @return a function accepts a point and returns t.
703 getInversionSolver: function(p0, p1, p2, p3) {
706 // calculates a determinant 3x3
713 var w = (i % 3 ? 3 : 1) * (j % 3 ? 3 : 1);
714 var lij = p.x * (pi.y - pj.y) + p.y * (pj.x - pi.x) + pi.x * pj.y - pi.y * pj.x;
718 return function solveInversion(p) {
719 var ct = 3 * l(2, 3)(p1);
720 var c1 = l(1, 3)(p0) / ct;
721 var c2 = -l(2, 3)(p0) / ct;
722 var la = c1 * l(3, 1)(p) + c2 * (l(3, 0)(p) + l(2, 1)(p)) + l(2, 0)(p);
723 var lb = c1 * l(3, 0)(p) + c2 * l(2, 0)(p) + l(1, 0)(p);
724 return lb / (lb - la);
728 // Divide a Bezier curve into two at point defined by value 't' <0,1>.
729 // Using deCasteljau algorithm. http://math.stackexchange.com/a/317867
730 // @param control points (start, control start, control end, end)
731 // @return a function accepts t and returns 2 curves each defined by 4 control points.
732 getCurveDivider: function(p0, p1, p2, p3) {
733 return function divideCurve(t) {
734 var l = line(p0, p1).pointAt(t);
735 var m = line(p1, p2).pointAt(t);
736 var n = line(p2, p3).pointAt(t);
737 var p = line(l, m).pointAt(t);
738 var q = line(m, n).pointAt(t);
739 var r = line(p, q).pointAt(t);
740 return [{ p0: p0, p1: l, p2: p, p3: r }, { p0: r, p1: q, p2: n, p3: p3 }];
748 // Return the `value` from the `domain` interval scaled to the `range` interval.
749 linear: function(domain, range, value) {
751 var domainSpan = domain[1] - domain[0];
752 var rangeSpan = range[1] - range[0];
753 return (((value - domain[0]) / domainSpan) * rangeSpan + range[0]) || 0;
760 snapToGrid: snapToGrid,
761 normalizeAngle: normalizeAngle,