reservation plugin - unbound request (unclean
[unfold.git] / portal / static / unbound_reservation_static / src / connectors-statemachine.js
1 /*
2  * jsPlumb
3  *
4  * Title:jsPlumb 1.5.5
5  *
6  * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas
7  * elements, or VML.
8  *
9  * This file contains the state machine connectors.
10  *
11  * Thanks to Brainstorm Mobile Solutions for supporting the development of these.
12  *
13  * Copyright (c) 2010 - 2013 Simon Porritt (simon.porritt@gmail.com)
14  *
15  * http://jsplumb.org
16  * http://github.com/sporritt/jsplumb
17  * http://code.google.com/p/jsplumb
18  *
19  * Dual licensed under the MIT and GPL2 licenses.
20  */
21
22 ;(function() {
23
24         var Line = function(x1, y1, x2, y2) {
25
26                 this.m = (y2 - y1) / (x2 - x1);
27                 this.b = -1 * ((this.m * x1) - y1);
28         
29                 this.rectIntersect = function(x,y,w,h) {
30                         var results = [], xInt, yInt;
31                 
32                         //      try top face
33                         //      the equation of the top face is y = (0 * x) + b; y = b.
34                         xInt = (y - this.b) / this.m;
35                         // test that the X value is in the line's range.
36                         if (xInt >= x && xInt <= (x + w)) results.push([ xInt, (this.m * xInt) + this.b ]);
37                 
38                         // try right face
39                         yInt = (this.m * (x + w)) + this.b;
40                         if (yInt >= y && yInt <= (y + h)) results.push([ (yInt - this.b) / this.m, yInt ]);
41                 
42                         //      bottom face
43                         xInt = ((y + h) - this.b) / this.m;
44                         // test that the X value is in the line's range.
45                         if (xInt >= x && xInt <= (x + w)) results.push([ xInt, (this.m * xInt) + this.b ]);
46                 
47                         // try left face
48                         yInt = (this.m * x) + this.b;
49                         if (yInt >= y && yInt <= (y + h)) results.push([ (yInt - this.b) / this.m, yInt ]);
50
51                         if (results.length == 2) {
52                                 var midx = (results[0][0] + results[1][0]) / 2, midy = (results[0][1] + results[1][1]) / 2;
53                                 results.push([ midx,midy ]);
54                                 // now calculate the segment inside the rectangle where the midpoint lies.
55                                 var xseg = midx <= x + (w / 2) ? -1 : 1,
56                                         yseg = midy <= y + (h / 2) ? -1 : 1;
57                                 results.push([xseg, yseg]);
58                                 return results;
59                         }
60                 
61                         return null;
62
63                 };
64         },
65         _segment = function(x1, y1, x2, y2) {
66                 if (x1 <= x2 && y2 <= y1) return 1;
67                 else if (x1 <= x2 && y1 <= y2) return 2;
68                 else if (x2 <= x1 && y2 >= y1) return 3;
69                 return 4;
70         },
71                 
72                 // the control point we will use depends on the faces to which each end of the connection is assigned, specifically whether or not the
73                 // two faces are parallel or perpendicular.  if they are parallel then the control point lies on the midpoint of the axis in which they
74                 // are parellel and varies only in the other axis; this variation is proportional to the distance that the anchor points lie from the
75                 // center of that face.  if the two faces are perpendicular then the control point is at some distance from both the midpoints; the amount and
76                 // direction are dependent on the orientation of the two elements. 'seg', passed in to this method, tells you which segment the target element
77                 // lies in with respect to the source: 1 is top right, 2 is bottom right, 3 is bottom left, 4 is top left.
78                 //
79                 // sourcePos and targetPos are arrays of info about where on the source and target each anchor is located.  their contents are:
80                 //
81                 // 0 - absolute x
82                 // 1 - absolute y
83                 // 2 - proportional x in element (0 is left edge, 1 is right edge)
84                 // 3 - proportional y in element (0 is top edge, 1 is bottom edge)
85                 //      
86         _findControlPoint = function(midx, midy, segment, sourceEdge, targetEdge, dx, dy, distance, proximityLimit) {
87         // TODO (maybe)
88         // - if anchor pos is 0.5, make the control point take into account the relative position of the elements.
89         if (distance <= proximityLimit) return [midx, midy];
90
91         if (segment === 1) {
92             if (sourceEdge[3] <= 0 && targetEdge[3] >= 1) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ];
93             else if (sourceEdge[2] >= 1 && targetEdge[2] <= 0) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ];
94             else return [ midx + (-1 * dx) , midy + (-1 * dy) ];
95         }
96         else if (segment === 2) {
97             if (sourceEdge[3] >= 1 && targetEdge[3] <= 0) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ];
98             else if (sourceEdge[2] >= 1 && targetEdge[2] <= 0) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ];
99             else return [ midx + (1 * dx) , midy + (-1 * dy) ];
100         }
101         else if (segment === 3) {
102             if (sourceEdge[3] >= 1 && targetEdge[3] <= 0) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ];
103             else if (sourceEdge[2] <= 0 && targetEdge[2] >= 1) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ];
104             else return [ midx + (-1 * dx) , midy + (-1 * dy) ];
105         }
106         else if (segment === 4) {
107             if (sourceEdge[3] <= 0 && targetEdge[3] >= 1) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ];
108             else if (sourceEdge[2] <= 0 && targetEdge[2] >= 1) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ];
109             else return [ midx + (1 * dx) , midy + (-1 * dy) ];
110         }
111
112         };      
113         
114         /**
115      * Class: Connectors.StateMachine
116      * Provides 'state machine' connectors.
117      */
118         /*
119          * Function: Constructor
120          * 
121          * Parameters:
122          * curviness -  measure of how "curvy" the connectors will be.  this is translated as the distance that the
123      *                Bezier curve's control point is from the midpoint of the straight line connecting the two
124      *              endpoints, and does not mean that the connector is this wide.  The Bezier curve never reaches
125      *              its control points; they act as gravitational masses. defaults to 10.
126          * margin       -       distance from element to start and end connectors, in pixels.  defaults to 5.
127          * proximityLimit  -   sets the distance beneath which the elements are consider too close together to bother
128          *                                              with fancy curves. by default this is 80 pixels.
129          * loopbackRadius       -       the radius of a loopback connector.  optional; defaults to 25.
130          * showLoopback   -   If set to false this tells the connector that it is ok to paint connections whose source and target is the same element with a connector running through the element. The default value for this is true; the connector always makes a loopback connection loop around the element rather than passing through it.
131         */
132         var StateMachine = function(params) {
133                 params = params || {};
134                 this.type = "StateMachine";
135
136                 var self = this,
137                         _super =  jsPlumb.Connectors.AbstractConnector.apply(this, arguments),
138                         curviness = params.curviness || 10,
139                         margin = params.margin || 5,
140                         proximityLimit = params.proximityLimit || 80,
141                         clockwise = params.orientation && params.orientation === "clockwise",
142                         loopbackRadius = params.loopbackRadius || 25,
143                         showLoopback = params.showLoopback !== false;
144                 
145                 this._compute = function(paintInfo, params) {
146                         var w = Math.abs(params.sourcePos[0] - params.targetPos[0]),
147                                 h = Math.abs(params.sourcePos[1] - params.targetPos[1]),
148                                 x = Math.min(params.sourcePos[0], params.targetPos[0]),
149                                 y = Math.min(params.sourcePos[1], params.targetPos[1]);                         
150                 
151                         if (!showLoopback || (params.sourceEndpoint.elementId !== params.targetEndpoint.elementId)) {                            
152                                 var _sx = params.sourcePos[0] < params.targetPos[0] ? 0  : w,
153                                         _sy = params.sourcePos[1] < params.targetPos[1] ? 0:h,
154                                         _tx = params.sourcePos[0] < params.targetPos[0] ? w : 0,
155                                         _ty = params.sourcePos[1] < params.targetPos[1] ? h : 0;
156             
157                                 // now adjust for the margin
158                                 if (params.sourcePos[2] === 0) _sx -= margin;
159                 if (params.sourcePos[2] === 1) _sx += margin;
160                 if (params.sourcePos[3] === 0) _sy -= margin;
161                 if (params.sourcePos[3] === 1) _sy += margin;
162                 if (params.targetPos[2] === 0) _tx -= margin;
163                 if (params.targetPos[2] === 1) _tx += margin;
164                 if (params.targetPos[3] === 0) _ty -= margin;
165                 if (params.targetPos[3] === 1) _ty += margin;
166
167                 //
168                     // these connectors are quadratic bezier curves, having a single control point. if both anchors 
169                 // are located at 0.5 on their respective faces, the control point is set to the midpoint and you
170                     // get a straight line.  this is also the case if the two anchors are within 'proximityLimit', since
171                         // it seems to make good aesthetic sense to do that. outside of that, the control point is positioned 
172                         // at 'curviness' pixels away along the normal to the straight line connecting the two anchors.
173                     // 
174                         // there may be two improvements to this.  firstly, we might actually support the notion of avoiding nodes
175                 // in the UI, or at least making a good effort at doing so.  if a connection would pass underneath some node,
176                 // for example, we might increase the distance the control point is away from the midpoint in a bid to
177                 // steer it around that node.  this will work within limits, but i think those limits would also be the likely
178                 // limits for, once again, aesthetic good sense in the layout of a chart using these connectors.
179                 //
180                 // the second possible change is actually two possible changes: firstly, it is possible we should gradually
181                 // decrease the 'curviness' as the distance between the anchors decreases; start tailing it off to 0 at some
182                 // point (which should be configurable).  secondly, we might slightly increase the 'curviness' for connectors
183                 // with respect to how far their anchor is from the center of its respective face. this could either look cool,
184                 // or stupid, and may indeed work only in a way that is so subtle as to have been a waste of time.
185                 //
186
187                                 var _midx = (_sx + _tx) / 2, _midy = (_sy + _ty) / 2, 
188                     m2 = (-1 * _midx) / _midy, theta2 = Math.atan(m2),
189                     dy =  (m2 == Infinity || m2 == -Infinity) ? 0 : Math.abs(curviness / 2 * Math.sin(theta2)),
190                                     dx =  (m2 == Infinity || m2 == -Infinity) ? 0 : Math.abs(curviness / 2 * Math.cos(theta2)),
191                                     segment = _segment(_sx, _sy, _tx, _ty),
192                                     distance = Math.sqrt(Math.pow(_tx - _sx, 2) + Math.pow(_ty - _sy, 2)),                      
193                         // calculate the control point.  this code will be where we'll put in a rudimentary element avoidance scheme; it
194                         // will work by extending the control point to force the curve to be, um, curvier.
195                                         _controlPoint = _findControlPoint(_midx,
196                                                   _midy,
197                                                   segment,
198                                                   params.sourcePos,
199                                                   params.targetPos,
200                                                   curviness, curviness,
201                                                   distance,
202                                                   proximityLimit);
203
204                                 _super.addSegment(this, "Bezier", {
205                                         x1:_tx, y1:_ty, x2:_sx, y2:_sy,
206                                         cp1x:_controlPoint[0], cp1y:_controlPoint[1],
207                                         cp2x:_controlPoint[0], cp2y:_controlPoint[1]
208                                 });                             
209             }
210             else {
211                 // a loopback connector.  draw an arc from one anchor to the other.             
212                         var x1 = params.sourcePos[0], x2 = params.sourcePos[0], y1 = params.sourcePos[1] - margin, y2 = params.sourcePos[1] - margin,                           
213                                         cx = x1, cy = y1 - loopbackRadius,                              
214                                         // canvas sizing stuff, to ensure the whole painted area is visible.
215                                         _w = 2 * loopbackRadius, 
216                                         _h = 2 * loopbackRadius,
217                                         _x = cx - loopbackRadius, 
218                                         _y = cy - loopbackRadius;
219
220                                 paintInfo.points[0] = _x;
221                                 paintInfo.points[1] = _y;
222                                 paintInfo.points[2] = _w;
223                                 paintInfo.points[3] = _h;
224                                 
225                                 // ADD AN ARC SEGMENT.
226                                 _super.addSegment(this, "Arc", {
227                                         loopback:true,
228                                         x1:(x1 - _x) + 4,
229                                         y1:y1 - _y,
230                                         startAngle:0,
231                                         endAngle: 2 * Math.PI,
232                                         r:loopbackRadius,
233                                         ac:!clockwise,
234                                         x2:(x1 - _x) - 4,
235                                         y2:y1 - _y,
236                                         cx:cx - _x,
237                                         cy:cy - _y
238                                 });
239             }                           
240         };                        
241         };
242         jsPlumb.registerConnectorType(StateMachine, "StateMachine");
243 })();
244
245 /*
246         // a possible rudimentary avoidance scheme, old now, perhaps not useful.
247         //      if (avoidSelector) {
248                 //                  var testLine = new Line(sourcePos[0] + _sx,sourcePos[1] + _sy,sourcePos[0] + _tx,sourcePos[1] + _ty);
249                 //                  var sel = jsPlumb.getSelector(avoidSelector);
250                 //                  for (var i = 0; i < sel.length; i++) {
251                 //                          var id = jsPlumb.getId(sel[i]);
252                 //                          if (id != sourceEndpoint.elementId && id != targetEndpoint.elementId) {
253                 //                                  o = jsPlumb.getOffset(id), s = jsPlumb.getSize(id);
254 //
255 //                                                  if (o && s) {
256 //                                                          var collision = testLine.rectIntersect(o.left,o.top,s[0],s[1]);
257 //                                                          if (collision) {
258                                                                     // set the control point to be a certain distance from the midpoint of the two points that
259                                                                     // the line crosses on the rectangle.
260                                                                     // TODO where will this 75 number come from?
261                                         //                          _controlX = collision[2][0] + (75 * collision[3][0]);
262                                 //      /                           _controlY = collision[2][1] + (75 * collision[3][1]);
263 //                                                          }
264 //                                                  }
265                                         //  }
266         //                          }
267               //}
268     */