reservation plugin - unbound request (unclean
[unfold.git] / portal / static / unbound_reservation_static / src / defaults.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 default Connectors, Endpoint and Overlay definitions.
10  *
11  * Copyright (c) 2010 - 2013 Simon Porritt (http://jsplumb.org)
12  * 
13  * http://jsplumb.org
14  * http://github.com/sporritt/jsplumb
15  * http://code.google.com/p/jsplumb
16  * 
17  * Dual licensed under the MIT and GPL2 licenses.
18  */
19
20 ;(function() {  
21                                 
22         /**
23          * 
24          * Helper class to consume unused mouse events by components that are DOM elements and
25          * are used by all of the different rendering modes.
26          * 
27          */
28         jsPlumb.DOMElementComponent = jsPlumbUtil.extend(jsPlumb.jsPlumbUIComponent, function(params) {         
29                 // when render mode is canvas, these functions may be called by the canvas mouse handler.  
30                 // this component is safe to pipe this stuff to /dev/null.
31                 this.mousemove = 
32                 this.dblclick  = 
33                 this.click = 
34                 this.mousedown = 
35                 this.mouseup = function(e) { };                                 
36         });
37         
38         jsPlumb.Segments = {
39                 
40         /*
41          * Class: AbstractSegment
42          * A Connector is made up of 1..N Segments, each of which has a Type, such as 'Straight', 'Arc',
43          * 'Bezier'. This is new from 1.4.2, and gives us a lot more flexibility when drawing connections: things such
44          * as rounded corners for flowchart connectors, for example, or a straight line stub for Bezier connections, are
45          * much easier to do now.
46          *
47          * A Segment is responsible for providing coordinates for painting it, and also must be able to report its length.
48          * 
49          */ 
50         AbstractSegment : function(params) { 
51             this.params = params;
52             
53             /**
54             * Function: findClosestPointOnPath
55             * Finds the closest point on this segment to the given [x, y], 
56             * returning both the x and y of the point plus its distance from
57             * the supplied point, and its location along the length of the
58             * path inscribed by the segment.  This implementation returns
59             * Infinity for distance and null values for everything else;
60             * subclasses are expected to override.
61             */
62             this.findClosestPointOnPath = function(x, y) {
63                 return {
64                     d:Infinity,
65                     x:null,
66                     y:null,
67                     l:null
68                 };
69             };
70
71             this.getBounds = function() {
72                 return {
73                     minX:Math.min(params.x1, params.x2),
74                     minY:Math.min(params.y1, params.y2),
75                     maxX:Math.max(params.x1, params.x2),
76                     maxY:Math.max(params.y1, params.y2)
77                 };
78             };
79         },
80         Straight : function(params) {
81             var _super = jsPlumb.Segments.AbstractSegment.apply(this, arguments),
82                 length, m, m2, x1, x2, y1, y2,
83                 _recalc = function() {
84                     length = Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
85                     m = jsPlumbGeom.gradient({x:x1, y:y1}, {x:x2, y:y2});
86                     m2 = -1 / m;                
87                 };
88                 
89             this.type = "Straight";
90             
91             this.getLength = function() { return length; };
92             this.getGradient = function() { return m; };
93                 
94             this.getCoordinates = function() {
95                 return { x1:x1,y1:y1,x2:x2,y2:y2 };
96             };
97             this.setCoordinates = function(coords) {
98                 x1 = coords.x1; y1 = coords.y1; x2 = coords.x2; y2 = coords.y2;
99                 _recalc();
100             };
101             this.setCoordinates({x1:params.x1, y1:params.y1, x2:params.x2, y2:params.y2});
102
103             this.getBounds = function() {
104                 return {
105                     minX:Math.min(x1, x2),
106                     minY:Math.min(y1, y2),
107                     maxX:Math.max(x1, x2),
108                     maxY:Math.max(y1, y2)
109                 };
110             };
111             
112             /**
113              * returns the point on the segment's path that is 'location' along the length of the path, where 'location' is a decimal from
114              * 0 to 1 inclusive. for the straight line segment this is simple maths.
115              */
116              this.pointOnPath = function(location, absolute) {
117                 if (location === 0 && !absolute)
118                     return { x:x1, y:y1 };
119                 else if (location == 1 && !absolute)
120                     return { x:x2, y:y2 };
121                 else {
122                     var l = absolute ? location > 0 ? location : length + location : location * length;
123                     return jsPlumbGeom.pointOnLine({x:x1, y:y1}, {x:x2, y:y2}, l);
124                 }
125             };
126             
127             /**
128              * returns the gradient of the segment at the given point - which for us is constant.
129              */
130             this.gradientAtPoint = function(_) {
131                 return m;
132             };
133             
134             /**
135              * returns the point on the segment's path that is 'distance' along the length of the path from 'location', where 
136              * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels.
137              * this hands off to jsPlumbUtil to do the maths, supplying two points and the distance.
138              */            
139             this.pointAlongPathFrom = function(location, distance, absolute) {            
140                 var p = this.pointOnPath(location, absolute),
141                     farAwayPoint = distance <= 0 ? {x:x1, y:y1} : {x:x2, y:y2 };
142
143                 /*
144                 location == 1 ? {
145                                         x:x1 + ((x2 - x1) * 10),
146                                         y:y1 + ((y1 - y2) * 10)
147                                     } : 
148                 */
149     
150                 if (distance <= 0 && Math.abs(distance) > 1) distance *= -1;
151     
152                 return jsPlumbGeom.pointOnLine(p, farAwayPoint, distance);
153             };
154             
155             // is c between a and b?
156             var within = function(a,b,c) {
157                 return c >= Math.min(a,b) && c <= Math.max(a,b); 
158             };
159             // find which of a and b is closest to c
160             var closest = function(a,b,c) {
161                 return Math.abs(c - a) < Math.abs(c - b) ? a : b;
162             };
163             
164             /**
165                 Function: findClosestPointOnPath
166                 Finds the closest point on this segment to [x,y]. See
167                 notes on this method in AbstractSegment.
168             */
169             this.findClosestPointOnPath = function(x, y) {
170                 var out = {
171                     d:Infinity,
172                     x:null,
173                     y:null,
174                     l:null,
175                     x1:x1,
176                     x2:x2,
177                     y1:y1,
178                     y2:y2
179                 };
180
181                 if (m === 0) {                  
182                     out.y = y1;
183                     out.x = within(x1, x2, x) ? x : closest(x1, x2, x);
184                 }
185                 else if (m == Infinity || m == -Infinity) {
186                     out.x = x1;                
187                     out.y = within(y1, y2, y) ? y : closest(y1, y2, y);
188                 }
189                 else {
190                     // closest point lies on normal from given point to this line.  
191                     var b = y1 - (m * x1),
192                         b2 = y - (m2 * x),                    
193                     // y1 = m.x1 + b and y1 = m2.x1 + b2
194                     // so m.x1 + b = m2.x1 + b2
195                     // x1(m - m2) = b2 - b
196                     // x1 = (b2 - b) / (m - m2)
197                         _x1 = (b2 -b) / (m - m2),
198                         _y1 = (m * _x1) + b;
199                                         
200                     out.x = within(x1,x2,_x1) ? _x1 : closest(x1,x2,_x1);//_x1;
201                     out.y = within(y1,y2,_y1) ? _y1 : closest(y1,y2,_y1);//_y1;                    
202                 }
203
204                 var fractionInSegment = jsPlumbGeom.lineLength([ out.x, out.y ], [ x1, y1 ]);
205                 out.d = jsPlumbGeom.lineLength([x,y], [out.x, out.y]);
206                 out.l = fractionInSegment / length;            
207                 return out;
208             };        
209         },
210         
211         /*
212             Arc Segment. You need to supply:
213     
214             r   -   radius
215             cx  -   center x for the arc
216             cy  -   center y for the arc
217             ac  -   whether the arc is anticlockwise or not. default is clockwise.
218     
219             and then either:
220     
221             startAngle  -   startAngle for the arc.
222             endAngle    -   endAngle for the arc.
223     
224             or:
225     
226             x1          -   x for start point
227             y1          -   y for start point
228             x2          -   x for end point
229             y2          -   y for end point
230     
231         */
232         Arc : function(params) {
233             var _super = jsPlumb.Segments.AbstractSegment.apply(this, arguments),
234                 _calcAngle = function(_x, _y) {
235                     return jsPlumbGeom.theta([params.cx, params.cy], [_x, _y]);    
236                 },
237                 _calcAngleForLocation = function(segment, location) {
238                     if (segment.anticlockwise) {
239                         var sa = segment.startAngle < segment.endAngle ? segment.startAngle + TWO_PI : segment.startAngle,
240                             s = Math.abs(sa - segment.endAngle);
241                         return sa - (s * location);                    
242                     }
243                     else {
244                         var ea = segment.endAngle < segment.startAngle ? segment.endAngle + TWO_PI : segment.endAngle,
245                             ss = Math.abs (ea - segment.startAngle);
246                     
247                         return segment.startAngle + (ss * location);
248                     }
249                 },
250                 TWO_PI = 2 * Math.PI;
251             
252             this.radius = params.r;
253             this.anticlockwise = params.ac;                     
254             this.type = "Arc";
255                 
256             if (params.startAngle && params.endAngle) {
257                 this.startAngle = params.startAngle;
258                 this.endAngle = params.endAngle;            
259                 this.x1 = params.cx + (this.radius * Math.cos(params.startAngle));     
260                 this.y1 = params.cy + (this.radius * Math.sin(params.startAngle));            
261                 this.x2 = params.cx + (this.radius * Math.cos(params.endAngle));     
262                 this.y2 = params.cy + (this.radius * Math.sin(params.endAngle));                        
263             }
264             else {
265                 this.startAngle = _calcAngle(params.x1, params.y1);
266                 this.endAngle = _calcAngle(params.x2, params.y2);            
267                 this.x1 = params.x1;
268                 this.y1 = params.y1;
269                 this.x2 = params.x2;
270                 this.y2 = params.y2;            
271             }
272             
273             if (this.endAngle < 0) this.endAngle += TWO_PI;
274             if (this.startAngle < 0) this.startAngle += TWO_PI;   
275
276             // segment is used by vml     
277             this.segment = jsPlumbGeom.quadrant([this.x1, this.y1], [this.x2, this.y2]);
278             
279             // we now have startAngle and endAngle as positive numbers, meaning the
280             // absolute difference (|d|) between them is the sweep (s) of this arc, unless the
281             // arc is 'anticlockwise' in which case 's' is given by 2PI - |d|.
282             
283             var ea = this.endAngle < this.startAngle ? this.endAngle + TWO_PI : this.endAngle;
284             this.sweep = Math.abs (ea - this.startAngle);
285             if (this.anticlockwise) this.sweep = TWO_PI - this.sweep;
286             var circumference = 2 * Math.PI * this.radius,
287                 frac = this.sweep / TWO_PI,
288                 length = circumference * frac;
289             
290             this.getLength = function() {
291                 return length;
292             };
293
294             this.getBounds = function() {
295                 return {
296                     minX:params.cx - params.r,
297                     maxX:params.cx + params.r,
298                     minY:params.cy - params.r,
299                     maxY:params.cy + params.r
300                 };
301             };
302             
303             var VERY_SMALL_VALUE = 0.0000000001,
304                 gentleRound = function(n) {
305                     var f = Math.floor(n), r = Math.ceil(n);
306                     if (n - f < VERY_SMALL_VALUE) 
307                         return f;    
308                     else if (r - n < VERY_SMALL_VALUE)
309                         return r;
310                     return n;
311                 };
312             
313             /**
314              * returns the point on the segment's path that is 'location' along the length of the path, where 'location' is a decimal from
315              * 0 to 1 inclusive. 
316              */
317             this.pointOnPath = function(location, absolute) {            
318                 
319                 if (location === 0) {
320                     return { x:this.x1, y:this.y1, theta:this.startAngle };    
321                 }
322                 else if (location == 1) {
323                     return { x:this.x2, y:this.y2, theta:this.endAngle };                    
324                 }
325                 
326                 if (absolute) {
327                     location = location / length;
328                 }
329     
330                 var angle = _calcAngleForLocation(this, location),
331                     _x = params.cx + (params.r * Math.cos(angle)),
332                     _y  = params.cy + (params.r * Math.sin(angle));                                     
333     
334                 return { x:gentleRound(_x), y:gentleRound(_y), theta:angle };
335             };
336             
337             /**
338              * returns the gradient of the segment at the given point.
339              */
340             this.gradientAtPoint = function(location, absolute) {
341                 var p = this.pointOnPath(location, absolute);
342                 var m = jsPlumbGeom.normal( [ params.cx, params.cy ], [p.x, p.y ] );
343                 if (!this.anticlockwise && (m == Infinity || m == -Infinity)) m *= -1;
344                 return m;
345             };                
346                     
347             this.pointAlongPathFrom = function(location, distance, absolute) {
348                 var p = this.pointOnPath(location, absolute),
349                     arcSpan = distance / circumference * 2 * Math.PI,
350                     dir = this.anticlockwise ? -1 : 1,
351                     startAngle = p.theta + (dir * arcSpan),                             
352                     startX = params.cx + (this.radius * Math.cos(startAngle)),
353                     startY = params.cy + (this.radius * Math.sin(startAngle));  
354     
355                 return {x:startX, y:startY};
356             };              
357         },
358         
359         Bezier : function(params) {
360             var _super = jsPlumb.Segments.AbstractSegment.apply(this, arguments),
361                 curve = [       
362                     { x:params.x1, y:params.y1},
363                     { x:params.cp1x, y:params.cp1y },
364                     { x:params.cp2x, y:params.cp2y },
365                     { x:params.x2, y:params.y2 }
366                 ],
367                 // although this is not a strictly rigorous determination of bounds
368                 // of a bezier curve, it works for the types of curves that this segment
369                 // type produces.
370                 bounds = {
371                     minX:Math.min(params.x1, params.x2, params.cp1x, params.cp2x),
372                     minY:Math.min(params.y1, params.y2, params.cp1y, params.cp2y),
373                     maxX:Math.max(params.x1, params.x2, params.cp1x, params.cp2x),
374                     maxY:Math.max(params.y1, params.y2, params.cp1y, params.cp2y)
375                 };
376                 
377             this.type = "Bezier";            
378             
379             var _translateLocation = function(_curve, location, absolute) {
380                 if (absolute)
381                     location = jsBezier.locationAlongCurveFrom(_curve, location > 0 ? 0 : 1, location);
382     
383                 return location;
384             };          
385             
386             /**
387              * returns the point on the segment's path that is 'location' along the length of the path, where 'location' is a decimal from
388              * 0 to 1 inclusive. 
389              */
390             this.pointOnPath = function(location, absolute) {
391                 location = _translateLocation(curve, location, absolute);                
392                 return jsBezier.pointOnCurve(curve, location);
393             };
394             
395             /**
396              * returns the gradient of the segment at the given point.
397              */
398             this.gradientAtPoint = function(location, absolute) {
399                 location = _translateLocation(curve, location, absolute);
400                 return jsBezier.gradientAtPoint(curve, location);               
401             };                
402             
403             this.pointAlongPathFrom = function(location, distance, absolute) {
404                 location = _translateLocation(curve, location, absolute);
405                 return jsBezier.pointAlongCurveFrom(curve, location, distance);
406             };
407             
408             this.getLength = function() {
409                 return jsBezier.getLength(curve);                               
410             };
411
412             this.getBounds = function() {
413                 return bounds;
414             };
415         }
416     };
417
418     /*
419         Class: AbstractComponent
420         Superclass for AbstractConnector and AbstractEndpoint.
421     */
422     var AbstractComponent = function() {        
423         this.resetBounds = function() {
424             this.bounds = { minX:Infinity, minY:Infinity, maxX:-Infinity, maxY:-Infinity };
425         };
426         this.resetBounds();        
427     };
428         
429         /*
430          * Class: AbstractConnector
431          * Superclass for all Connectors; here is where Segments are managed.  This is exposed on jsPlumb just so it
432          * can be accessed from other files. You should not try to instantiate one of these directly.
433          *
434          * When this class is asked for a pointOnPath, or gradient etc, it must first figure out which segment to dispatch
435          * that request to. This is done by keeping track of the total connector length as segments are added, and also
436          * their cumulative ratios to the total length.  Then when the right segment is found it is a simple case of dispatching
437          * the request to it (and adjusting 'location' so that it is relative to the beginning of that segment.)
438          */ 
439         jsPlumb.Connectors.AbstractConnector = function(params) {
440                 
441         AbstractComponent.apply(this, arguments);        
442
443                 var //self = this, 
444             segments = [],
445             editing = false,
446                         totalLength = 0,
447                         segmentProportions = [],
448                         segmentProportionalLengths = [],        
449             stub = params.stub || 0, 
450             sourceStub = jsPlumbUtil.isArray(stub) ? stub[0] : stub,
451             targetStub = jsPlumbUtil.isArray(stub) ? stub[1] : stub,
452             gap = params.gap || 0,
453             sourceGap = jsPlumbUtil.isArray(gap) ? gap[0] : gap,
454             targetGap = jsPlumbUtil.isArray(gap) ? gap[1] : gap,
455             userProvidedSegments = null,
456             edited = false,
457             paintInfo = null;            
458         
459         // subclasses should override.
460         this.isEditable = function() { return false; };                        
461         this.setEdited = function(ed) { edited = ed; };
462
463         // to be overridden by subclasses.
464         this.getPath = function() { };
465         this.setPath = function(path) { };
466         
467         /**
468         * Function: findSegmentForPoint
469         * Returns the segment that is closest to the given [x,y],
470         * null if nothing found.  This function returns a JS 
471         * object with:
472         *
473         *   d   -   distance from segment
474         *   l   -   proportional location in segment
475         *   x   -   x point on the segment
476         *   y   -   y point on the segment
477         *   s   -   the segment itself.
478         */ 
479         this.findSegmentForPoint = function(x, y) {
480             var out = { d:Infinity, s:null, x:null, y:null, l:null };
481             for (var i = 0; i < segments.length; i++) {
482                 var _s = segments[i].findClosestPointOnPath(x, y);
483                 if (_s.d < out.d) {
484                     out.d = _s.d; 
485                     out.l = _s.l; 
486                     out.x = _s.x;
487                     out.y = _s.y; 
488                     out.s = segments[i];
489                     out.x1 = _s.x1;
490                     out.x2 = _s.x2;
491                     out.y1 = _s.y1;
492                     out.y2 = _s.y2;
493                     out.index = i;
494                 }
495             }
496             
497             return out;                
498         };
499                         
500                 var _updateSegmentProportions = function() {
501                 var curLoc = 0;
502                 for (var i = 0; i < segments.length; i++) {
503                     var sl = segments[i].getLength();
504                     segmentProportionalLengths[i] = sl / totalLength;
505                     segmentProportions[i] = [curLoc, (curLoc += (sl / totalLength)) ];
506                 }
507             },
508                 
509             /**
510              * returns [segment, proportion of travel in segment, segment index] for the segment 
511              * that contains the point which is 'location' distance along the entire path, where 
512              * 'location' is a decimal between 0 and 1 inclusive. in this connector type, paths 
513              * are made up of a list of segments, each of which contributes some fraction to
514              * the total length. 
515              * From 1.3.10 this also supports the 'absolute' property, which lets us specify a location
516              * as the absolute distance in pixels, rather than a proportion of the total path. 
517              */
518             _findSegmentForLocation = function(location, absolute) {
519                 if (absolute) {
520                     location = location > 0 ? location / totalLength : (totalLength + location) / totalLength;
521                 }
522     
523                 var idx = segmentProportions.length - 1, inSegmentProportion = 1;
524                 //if (location < 1) {
525                     for (var i = 0; i < segmentProportions.length; i++) {
526                         if (segmentProportions[i][1] >= location) {
527                             idx = i;
528                             // todo is this correct for all connector path types?
529                             inSegmentProportion = location == 1 ? 1 : location === 0 ? 0 : (location - segmentProportions[i][0]) / segmentProportionalLengths[i];                    
530                             break;
531                         }
532                     }
533                 //}
534                 return { segment:segments[idx], proportion:inSegmentProportion, index:idx };
535             },          
536             _addSegment = function(conn, type, params) {
537                 if (params.x1 == params.x2 && params.y1 == params.y2) return;
538                 var s = new jsPlumb.Segments[type](params);
539                 segments.push(s);
540                 totalLength += s.getLength();   
541                 conn.updateBounds(s);                                   
542             },                                  
543             _clearSegments = function() {
544                 totalLength = 0;
545                 segments.splice(0, segments.length);
546                 segmentProportions.splice(0, segmentProportions.length);
547                 segmentProportionalLengths.splice(0, segmentProportionalLengths.length);
548             };
549         
550         this.setSegments = function(_segs) {
551             userProvidedSegments = [];
552             totalLength = 0;
553             for (var i = 0; i < _segs.length; i++) {      
554                 userProvidedSegments.push(_segs[i]);
555                 totalLength += _segs[i].getLength();                                
556             }            
557         };  
558         
559         var _prepareCompute = function(params) {
560             this.lineWidth = params.lineWidth;
561             var segment = jsPlumbGeom.quadrant(params.sourcePos, params.targetPos),
562                 swapX = params.targetPos[0] < params.sourcePos[0],
563                 swapY = params.targetPos[1] < params.sourcePos[1],
564                 lw = params.lineWidth || 1,       
565                 so = params.sourceEndpoint.anchor.getOrientation(params.sourceEndpoint), 
566                 to = params.targetEndpoint.anchor.getOrientation(params.targetEndpoint),
567                 x = swapX ? params.targetPos[0] : params.sourcePos[0], 
568                 y = swapY ? params.targetPos[1] : params.sourcePos[1],
569                 w = Math.abs(params.targetPos[0] - params.sourcePos[0]),
570                 h = Math.abs(params.targetPos[1] - params.sourcePos[1]);
571             
572             // if either anchor does not have an orientation set, we derive one from their relative
573             // positions.  we fix the axis to be the one in which the two elements are further apart, and
574             // point each anchor at the other element.  this is also used when dragging a new connection.
575             if (so[0] === 0 && so[1] === 0 || to[0] === 0 && to[1] === 0) {
576                 var index = w > h ? 0 : 1, oIndex = [1,0][index];
577                 so = []; to = [];
578                 so[index] = params.sourcePos[index] > params.targetPos[index] ? -1 : 1;
579                 to[index] = params.sourcePos[index] > params.targetPos[index] ? 1 : -1;
580                 so[oIndex] = 0; to[oIndex] = 0;
581             }                    
582             
583             var sx = swapX ? w + (sourceGap * so[0])  : sourceGap * so[0], 
584                 sy = swapY ? h + (sourceGap * so[1])  : sourceGap * so[1], 
585                 tx = swapX ? targetGap * to[0] : w + (targetGap * to[0]),
586                 ty = swapY ? targetGap * to[1] : h + (targetGap * to[1]),
587                 oProduct = ((so[0] * to[0]) + (so[1] * to[1]));        
588             
589             var result = {
590                 sx:sx, sy:sy, tx:tx, ty:ty, lw:lw, 
591                 xSpan:Math.abs(tx - sx),
592                 ySpan:Math.abs(ty - sy),                
593                 mx:(sx + tx) / 2,
594                 my:(sy + ty) / 2,                
595                 so:so, to:to, x:x, y:y, w:w, h:h,
596                 segment : segment,
597                 startStubX : sx + (so[0] * sourceStub), 
598                 startStubY : sy + (so[1] * sourceStub),
599                 endStubX : tx + (to[0] * targetStub), 
600                 endStubY : ty + (to[1] * targetStub),
601                 isXGreaterThanStubTimes2 : Math.abs(sx - tx) > (sourceStub + targetStub),
602                 isYGreaterThanStubTimes2 : Math.abs(sy - ty) > (sourceStub + targetStub),
603                 opposite:oProduct == -1,
604                 perpendicular:oProduct === 0,
605                 orthogonal:oProduct == 1,
606                 sourceAxis : so[0] === 0 ? "y" : "x",
607                 points:[x, y, w, h, sx, sy, tx, ty ]
608             };
609             result.anchorOrientation = result.opposite ? "opposite" : result.orthogonal ? "orthogonal" : "perpendicular";
610             return result;
611         };
612                 
613                 this.getSegments = function() { return segments; };
614
615         this.updateBounds = function(segment) {
616             var segBounds = segment.getBounds();
617             this.bounds.minX = Math.min(this.bounds.minX, segBounds.minX);
618             this.bounds.maxX = Math.max(this.bounds.maxX, segBounds.maxX);
619             this.bounds.minY = Math.min(this.bounds.minY, segBounds.minY);
620             this.bounds.maxY = Math.max(this.bounds.maxY, segBounds.maxY);              
621         };
622         
623         var dumpSegmentsToConsole = function() {
624             console.log("SEGMENTS:");
625             for (var i = 0; i < segments.length; i++) {
626                 console.log(segments[i].type, segments[i].getLength(), segmentProportions[i]);
627             }
628         };
629                 
630                 this.pointOnPath = function(location, absolute) {
631             var seg = _findSegmentForLocation(location, absolute);      
632             return seg.segment && seg.segment.pointOnPath(seg.proportion, absolute) || [0,0];
633         };
634         
635         this.gradientAtPoint = function(location) {
636             var seg = _findSegmentForLocation(location, absolute);          
637             return seg.segment && seg.segment.gradientAtPoint(seg.proportion, absolute) || 0;
638         };
639         
640         this.pointAlongPathFrom = function(location, distance, absolute) {
641             var seg = _findSegmentForLocation(location, absolute);
642             // TODO what happens if this crosses to the next segment?
643             return seg.segment && seg.segment.pointAlongPathFrom(seg.proportion, distance, false) || [0,0];
644         };
645                 
646                 this.compute = function(params)  {
647             if (!edited)
648                 paintInfo = _prepareCompute.call(this, params);
649             
650             _clearSegments();
651             this._compute(paintInfo, params);
652             this.x = paintInfo.points[0];
653             this.y = paintInfo.points[1];
654             this.w = paintInfo.points[2];
655             this.h = paintInfo.points[3];               
656             this.segment = paintInfo.segment;         
657             _updateSegmentProportions();            
658                 };
659                 
660                 return {
661                         addSegment:_addSegment,
662             prepareCompute:_prepareCompute,
663             sourceStub:sourceStub,
664             targetStub:targetStub,
665             maxStub:Math.max(sourceStub, targetStub),            
666             sourceGap:sourceGap,
667             targetGap:targetGap,
668             maxGap:Math.max(sourceGap, targetGap)
669                 };              
670         };
671     jsPlumbUtil.extend(jsPlumb.Connectors.AbstractConnector, AbstractComponent);
672         
673     /**
674      * Class: Connectors.Straight
675      * The Straight connector draws a simple straight line between the two anchor points.  It does not have any constructor parameters.
676      */
677     var Straight = jsPlumb.Connectors.Straight = function() {
678         this.type = "Straight";
679                 var _super =  jsPlumb.Connectors.AbstractConnector.apply(this, arguments);              
680
681         this._compute = function(paintInfo, _) {                        
682             _super.addSegment(this, "Straight", {x1:paintInfo.sx, y1:paintInfo.sy, x2:paintInfo.startStubX, y2:paintInfo.startStubY});                                                
683             _super.addSegment(this, "Straight", {x1:paintInfo.startStubX, y1:paintInfo.startStubY, x2:paintInfo.endStubX, y2:paintInfo.endStubY});                        
684             _super.addSegment(this, "Straight", {x1:paintInfo.endStubX, y1:paintInfo.endStubY, x2:paintInfo.tx, y2:paintInfo.ty});                                    
685         };                    
686     };
687     jsPlumbUtil.extend(jsPlumb.Connectors.Straight, jsPlumb.Connectors.AbstractConnector);
688     jsPlumb.registerConnectorType(Straight, "Straight");
689                     
690     /**
691      * Class:Connectors.Bezier
692      * This Connector draws a Bezier curve with two control points.  You can provide a 'curviness' value which gets applied to jsPlumb's
693      * internal voodoo machine and ends up generating locations for the two control points.  See the constructor documentation below.
694      */
695     /**
696      * Function:Constructor
697      * 
698      * Parameters:
699      *  curviness - How 'curvy' you want the curve to be! This is a directive for the placement of control points, not endpoints of the curve, so your curve does not 
700      * actually touch the given point, but it has the tendency to lean towards it.  The larger this value, the greater the curve is pulled from a straight line.
701      * Optional; defaults to 150.
702      * stub - optional value for a distance to travel from the connector's endpoint before beginning the Bezier curve. defaults to 0.
703      * 
704      */
705     var Bezier = function(params) {
706         params = params || {};
707
708         var _super =  jsPlumb.Connectors.AbstractConnector.apply(this, arguments),
709             stub = params.stub || 50,
710             majorAnchor = params.curviness || 150,
711             minorAnchor = 10;            
712
713         this.type = "Bezier";   
714         this.getCurviness = function() { return majorAnchor; }; 
715         
716         this._findControlPoint = function(point, sourceAnchorPosition, targetAnchorPosition, sourceEndpoint, targetEndpoint) {
717                 // determine if the two anchors are perpendicular to each other in their orientation.  we swap the control 
718                 // points around if so (code could be tightened up)
719                 var soo = sourceEndpoint.anchor.getOrientation(sourceEndpoint), 
720                         too = targetEndpoint.anchor.getOrientation(targetEndpoint),
721                         perpendicular = soo[0] != too[0] || soo[1] == too[1],
722                 p = [];                
723                 
724             if (!perpendicular) {
725                 if (soo[0] === 0) // X
726                     p.push(sourceAnchorPosition[0] < targetAnchorPosition[0] ? point[0] + minorAnchor : point[0] - minorAnchor);
727                 else p.push(point[0] - (majorAnchor * soo[0]));
728                                  
729                 if (soo[1] === 0) // Y
730                         p.push(sourceAnchorPosition[1] < targetAnchorPosition[1] ? point[1] + minorAnchor : point[1] - minorAnchor);
731                 else p.push(point[1] + (majorAnchor * too[1]));
732             }
733              else {
734                 if (too[0] === 0) // X
735                         p.push(targetAnchorPosition[0] < sourceAnchorPosition[0] ? point[0] + minorAnchor : point[0] - minorAnchor);
736                 else p.push(point[0] + (majorAnchor * too[0]));
737                 
738                 if (too[1] === 0) // Y
739                         p.push(targetAnchorPosition[1] < sourceAnchorPosition[1] ? point[1] + minorAnchor : point[1] - minorAnchor);
740                 else p.push(point[1] + (majorAnchor * soo[1]));
741              }
742
743             return p;                
744         };        
745
746         this._compute = function(paintInfo, p) {                                
747                         var sp = p.sourcePos,
748                                 tp = p.targetPos,                               
749                 _w = Math.abs(sp[0] - tp[0]),
750                 _h = Math.abs(sp[1] - tp[1]),            
751                 _sx = sp[0] < tp[0] ? _w : 0,
752                 _sy = sp[1] < tp[1] ? _h : 0,
753                 _tx = sp[0] < tp[0] ? 0 : _w,
754                 _ty = sp[1] < tp[1] ? 0 : _h,
755                 _CP = this._findControlPoint([_sx, _sy], sp, tp, p.sourceEndpoint, p.targetEndpoint),
756                 _CP2 = this._findControlPoint([_tx, _ty], tp, sp, p.targetEndpoint, p.sourceEndpoint);
757
758                         _super.addSegment(this, "Bezier", {
759                                 x1:_sx, y1:_sy, x2:_tx, y2:_ty,
760                                 cp1x:_CP[0], cp1y:_CP[1], cp2x:_CP2[0], cp2y:_CP2[1]
761                         });                    
762         };               
763     };    
764     jsPlumbUtil.extend(Bezier, jsPlumb.Connectors.AbstractConnector);
765     jsPlumb.registerConnectorType(Bezier, "Bezier");
766     
767  // ********************************* END OF CONNECTOR TYPES *******************************************************************
768     
769  // ********************************* ENDPOINT TYPES *******************************************************************
770     
771     jsPlumb.Endpoints.AbstractEndpoint = function(params) {
772         AbstractComponent.apply(this, arguments);
773         var compute = this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) {    
774             var out = this._compute.apply(this, arguments);
775             this.x = out[0];
776             this.y = out[1];
777             this.w = out[2];
778             this.h = out[3];
779             this.bounds.minX = this.x;
780             this.bounds.minY = this.y;
781             this.bounds.maxX = this.x + this.w;
782             this.bounds.maxY = this.y + this.h;
783             return out;
784         };
785         return {
786             compute:compute,
787             cssClass:params.cssClass
788         };
789     };
790     jsPlumbUtil.extend(jsPlumb.Endpoints.AbstractEndpoint, AbstractComponent);
791     
792     /**
793      * Class: Endpoints.Dot
794      * A round endpoint, with default radius 10 pixels.
795      */         
796         
797         /**
798          * Function: Constructor
799          * 
800          * Parameters:
801          * 
802          *      radius  -       radius of the endpoint.  defaults to 10 pixels.
803          */
804         jsPlumb.Endpoints.Dot = function(params) {        
805                 this.type = "Dot";
806                 var _super = jsPlumb.Endpoints.AbstractEndpoint.apply(this, arguments);
807                 params = params || {};                          
808                 this.radius = params.radius || 10;
809                 this.defaultOffset = 0.5 * this.radius;
810                 this.defaultInnerRadius = this.radius / 3;                      
811                 
812                 this._compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) {
813                         this.radius = endpointStyle.radius || this.radius;
814                         var     x = anchorPoint[0] - this.radius,
815                                 y = anchorPoint[1] - this.radius,
816                 w = this.radius * 2,
817                 h = this.radius * 2;
818
819             if (endpointStyle.strokeStyle) {
820                 var lw = endpointStyle.lineWidth || 1;
821                 x -= lw;
822                 y -= lw;
823                 w += (lw * 2);
824                 h += (lw * 2);
825             }
826                         return [ x, y, w, h, this.radius ];
827                 };
828         };
829     jsPlumbUtil.extend(jsPlumb.Endpoints.Dot, jsPlumb.Endpoints.AbstractEndpoint);
830         
831         /**
832          * Class: Endpoints.Rectangle
833          * A Rectangular Endpoint, with default size 20x20.
834          */
835         /**
836          * Function: Constructor
837          * 
838          * Parameters:
839          * 
840          *      width   - width of the endpoint. defaults to 20 pixels.
841          *      height  - height of the endpoint. defaults to 20 pixels.        
842          */
843         jsPlumb.Endpoints.Rectangle = function(params) {
844                 this.type = "Rectangle";
845                 var _super = jsPlumb.Endpoints.AbstractEndpoint.apply(this, arguments);
846                 params = params || {};
847                 this.width = params.width || 20;
848                 this.height = params.height || 20;
849                 
850                 this._compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) {
851                         var width = endpointStyle.width || this.width,
852                                 height = endpointStyle.height || this.height,
853                                 x = anchorPoint[0] - (width/2),
854                                 y = anchorPoint[1] - (height/2);
855                 
856                         return [ x, y, width, height];
857                 };
858         };
859     jsPlumbUtil.extend(jsPlumb.Endpoints.Rectangle, jsPlumb.Endpoints.AbstractEndpoint);
860         
861
862     var DOMElementEndpoint = function(params) {
863         jsPlumb.DOMElementComponent.apply(this, arguments);        
864         this._jsPlumb.displayElements = [  ];                
865     };
866     jsPlumbUtil.extend(DOMElementEndpoint, jsPlumb.DOMElementComponent, {
867        // jsPlumb.Endpoints.AbstractEndpoint
868         getDisplayElements : function() { 
869             return this._jsPlumb.displayElements; 
870         },        
871         appendDisplayElement : function(el) {
872             this._jsPlumb.displayElements.push(el);
873         }
874     });
875
876         /**
877          * Class: Endpoints.Image
878          * Draws an image as the Endpoint.
879          */
880         /**
881          * Function: Constructor
882          * 
883          * Parameters:
884          * 
885          *      src     -       location of the image to use.
886
887     TODO: multiple references to self. not sure quite how to get rid of them entirely. perhaps self = null in the cleanup
888     function will suffice
889
890     TODO this class still leaks memory.
891
892          */
893         jsPlumb.Endpoints.Image = function(params) {
894                                 
895                 this.type = "Image";
896                 DOMElementEndpoint.apply(this, arguments);
897         jsPlumb.Endpoints.AbstractEndpoint.apply(this, arguments);
898                 
899                 var _onload = params.onload, 
900             src = params.src || params.url,
901             parent = params.parent,
902             clazz = params.cssClass ? " " + params.cssClass : "";
903
904         this._jsPlumb.img = new Image();                
905         this._jsPlumb.ready = false;
906         this._jsPlumb.initialized = false;
907         this._jsPlumb.deleted = false;
908         this._jsPlumb.widthToUse = params.width;
909         this._jsPlumb.heightToUse = params.height;
910         this._jsPlumb.endpoint = params.endpoint;
911
912                 this._jsPlumb.img.onload = function() {
913             // check we weren't actually discarded before use (in fact mostly happens in tests)
914             if (this._jsPlumb != null) {
915                         this._jsPlumb.ready = true;            
916                         this._jsPlumb.widthToUse = this._jsPlumb.widthToUse || this._jsPlumb.img.width;
917                         this._jsPlumb.heightToUse = this._jsPlumb.heightToUse || this._jsPlumb.img.height;
918                 if (_onload) {
919                     _onload(this);
920                 }
921             }
922                 }.bind(this);        
923
924         /*
925             Function: setImage
926             Sets the Image to use in this Endpoint.  
927
928             Parameters:
929             img         -   may be a URL or an Image object
930             onload      -   optional; a callback to execute once the image has loaded.
931         */
932         this._jsPlumb.endpoint.setImage = function(_img, onload) {
933             var s = _img.constructor == String ? _img : _img.src;
934             _onload = onload; 
935             this._jsPlumb.img.src = s;
936
937             if (this.canvas != null)
938                 this.canvas.setAttribute("src", this._jsPlumb.img.src);
939         }.bind(this);
940
941         this._jsPlumb.endpoint.setImage(src, _onload);
942         /*        
943             var s = src.constructor == String ? src : src.src;
944             //_onload = onload; 
945             this._jsPlumb.img.src = src;
946
947             if (this.canvas != null)
948                 this.canvas.setAttribute("src", this._jsPlumb.img.src);
949        // }.bind(this);
950
951         //this._jsPlumb.endpoint.setImage(src, _onload);*/
952
953                 this._compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) {
954                         this.anchorPoint = anchorPoint;
955                         if (this._jsPlumb.ready) return [anchorPoint[0] - this._jsPlumb.widthToUse / 2, anchorPoint[1] - this._jsPlumb.heightToUse / 2, 
956                                                                         this._jsPlumb.widthToUse, this._jsPlumb.heightToUse];
957                         else return [0,0,0,0];
958                 };
959                 
960                 this.canvas = document.createElement("img");
961                 this.canvas.style.margin = 0;
962                 this.canvas.style.padding = 0;
963                 this.canvas.style.outline = 0;
964                 this.canvas.style.position = "absolute";                
965                 this.canvas.className = this._jsPlumb.instance.endpointClass + clazz;
966                 if (this._jsPlumb.widthToUse) this.canvas.setAttribute("width", this._jsPlumb.widthToUse);
967                 if (this._jsPlumb.heightToUse) this.canvas.setAttribute("height", this._jsPlumb.heightToUse);           
968                 this._jsPlumb.instance.appendElement(this.canvas, parent);
969                 this.attachListeners(this.canvas, this);                
970                 
971                 this.actuallyPaint = function(d, style, anchor) {
972                         if (!this._jsPlumb.deleted) {
973                                 if (!this._jsPlumb.initialized) {
974                                         this.canvas.setAttribute("src", this._jsPlumb.img.src);
975                                         this.appendDisplayElement(this.canvas);
976                                         this._jsPlumb.initialized = true;
977                                 }
978                                 var x = this.anchorPoint[0] - (this._jsPlumb.widthToUse / 2),
979                                         y = this.anchorPoint[1] - (this._jsPlumb.heightToUse / 2);
980                                 jsPlumbUtil.sizeElement(this.canvas, x, y, this._jsPlumb.widthToUse, this._jsPlumb.heightToUse);
981                         }
982                 };
983                 
984                 this.paint = function(style, anchor) {
985             if (this._jsPlumb != null) {  // may have been deleted
986                         if (this._jsPlumb.ready) {
987                                 this.actuallyPaint(style, anchor);
988                         }
989                         else { 
990                                 window.setTimeout(function() {                                          
991                                         this.paint(style, anchor);
992                                 }.bind(this), 200);
993                         }
994             }
995                 };                              
996         };
997     jsPlumbUtil.extend(jsPlumb.Endpoints.Image, [ DOMElementEndpoint, jsPlumb.Endpoints.AbstractEndpoint ], {
998         cleanup : function() {            
999             this._jsPlumb.deleted = true;
1000             jsPlumbUtil.removeElement(this.canvas);
1001             this.canvas = null;
1002         } 
1003     });
1004         
1005         /*
1006          * Class: Endpoints.Blank
1007          * An Endpoint that paints nothing (visible) on the screen.  Supports cssClass and hoverClass parameters like all Endpoints.
1008          */
1009         jsPlumb.Endpoints.Blank = function(params) {
1010                 var _super = jsPlumb.Endpoints.AbstractEndpoint.apply(this, arguments);
1011                 this.type = "Blank";
1012                 DOMElementEndpoint.apply(this, arguments);              
1013                 this._compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) {
1014                         return [anchorPoint[0], anchorPoint[1],10,0];
1015                 };
1016                 
1017                 this.canvas = document.createElement("div");
1018                 this.canvas.style.display = "block";
1019                 this.canvas.style.width = "1px";
1020                 this.canvas.style.height = "1px";
1021                 this.canvas.style.background = "transparent";
1022                 this.canvas.style.position = "absolute";
1023                 this.canvas.className = this._jsPlumb.endpointClass;
1024                 jsPlumb.appendElement(this.canvas, params.parent);
1025                 
1026                 this.paint = function(style, anchor) {
1027                         jsPlumbUtil.sizeElement(this.canvas, this.x, this.y, this.w, this.h);   
1028                 };
1029         };
1030     jsPlumbUtil.extend(jsPlumb.Endpoints.Blank, [jsPlumb.Endpoints.AbstractEndpoint, DOMElementEndpoint], {
1031         cleanup:function() {
1032             if (this.canvas && this.canvas.parentNode) {
1033                 this.canvas.parentNode.removeChild(this.canvas);
1034             }
1035         }
1036     });
1037         
1038         /*
1039          * Class: Endpoints.Triangle
1040          * A triangular Endpoint.  
1041          */
1042         /*
1043          * Function: Constructor
1044          * 
1045          * Parameters:
1046          * 
1047          *      width   -       width of the triangle's base.  defaults to 55 pixels.
1048          *      height  -       height of the triangle from base to apex.  defaults to 55 pixels.
1049          */
1050         jsPlumb.Endpoints.Triangle = function(params) {        
1051                 this.type = "Triangle";
1052         var _super = jsPlumb.Endpoints.AbstractEndpoint.apply(this, arguments);
1053                 params = params || {  };
1054                 params.width = params.width || 55;
1055                 params.height = params.height || 55;
1056                 this.width = params.width;
1057                 this.height = params.height;
1058                 this._compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) {
1059                         var width = endpointStyle.width || self.width,
1060                         height = endpointStyle.height || self.height,
1061                         x = anchorPoint[0] - (width/2),
1062                         y = anchorPoint[1] - (height/2);
1063                         return [ x, y, width, height ];
1064                 };
1065         };
1066 // ********************************* END OF ENDPOINT TYPES *******************************************************************
1067         
1068
1069 // ********************************* OVERLAY DEFINITIONS ***********************************************************************    
1070
1071         var AbstractOverlay = jsPlumb.Overlays.AbstractOverlay = function(params) {
1072                 this.visible = true;
1073         this.isAppendedAtTopLevel = true;
1074                 this.component = params.component;
1075                 this.loc = params.location == null ? 0.5 : params.location;
1076         this.endpointLoc = params.endpointLocation == null ? [ 0.5, 0.5] : params.endpointLocation;             
1077         };
1078     AbstractOverlay.prototype = {
1079         cleanup:function() {  
1080            this.component = null;
1081            this.canvas = null;
1082            this.endpointLoc = null;
1083         },
1084         setVisible : function(val) { 
1085             this.visible = val;
1086             // TODO this is only actually necessary for canvas. so, the Canvas overlay should
1087             // override setVisible and call this.
1088             //this.component.repaint();
1089         },
1090         isVisible : function() { return this.visible; },
1091         hide : function() { this.setVisible(false); },
1092         show : function() { this.setVisible(true); },        
1093         incrementLocation : function(amount) {
1094             this.loc += amount;
1095             this.component.repaint();
1096         },
1097         setLocation : function(l) {
1098             this.loc = l;
1099             this.component.repaint();
1100         },
1101         getLocation : function() {
1102             return this.loc;
1103         }
1104     };
1105         
1106         
1107         /*
1108          * Class: Overlays.Arrow
1109          * 
1110          * An arrow overlay, defined by four points: the head, the two sides of the tail, and a 'foldback' point at some distance along the length
1111          * of the arrow that lines from each tail point converge into.  The foldback point is defined using a decimal that indicates some fraction
1112          * of the length of the arrow and has a default value of 0.623.  A foldback point value of 1 would mean that the arrow had a straight line
1113          * across the tail.  
1114          */
1115         /*
1116          * Function: Constructor
1117          * 
1118          * Parameters:
1119          * 
1120          *      length - distance in pixels from head to tail baseline. default 20.
1121          *      width - width in pixels of the tail baseline. default 20.
1122          *      fillStyle - style to use when filling the arrow.  defaults to "black".
1123          *      strokeStyle - style to use when stroking the arrow. defaults to null, which means the arrow is not stroked.
1124          *      lineWidth - line width to use when stroking the arrow. defaults to 1, but only used if strokeStyle is not null.
1125          *      foldback - distance (as a decimal from 0 to 1 inclusive) along the length of the arrow marking the point the tail points should fold back to.  defaults to 0.623.
1126          *      location - distance (as a decimal from 0 to 1 inclusive) marking where the arrow should sit on the connector. defaults to 0.5.
1127          *      direction - indicates the direction the arrow points in. valid values are -1 and 1; 1 is default.
1128          */
1129         jsPlumb.Overlays.Arrow = function(params) {
1130                 this.type = "Arrow";
1131                 AbstractOverlay.apply(this, arguments);
1132         this.isAppendedAtTopLevel = false;
1133                 params = params || {};
1134                 var _ju = jsPlumbUtil, _jg = jsPlumbGeom;
1135                 
1136         this.length = params.length || 20;
1137         this.width = params.width || 20;
1138         this.id = params.id;
1139         var direction = (params.direction || 1) < 0 ? -1 : 1,
1140             paintStyle = params.paintStyle || { lineWidth:1 },
1141             // how far along the arrow the lines folding back in come to. default is 62.3%.
1142             foldback = params.foldback || 0.623;
1143                 
1144         this.computeMaxSize = function() { return self.width * 1.5; };          
1145         //this.cleanup = function() { };  // nothing to clean up for Arrows    
1146         this.draw = function(component, currentConnectionPaintStyle) {
1147
1148             var hxy, mid, txy, tail, cxy;
1149             if (component.pointAlongPathFrom) {
1150
1151                 if (_ju.isString(this.loc) || this.loc > 1 || this.loc < 0) {                    
1152                     var l = parseInt(this.loc, 10),
1153                         fromLoc = this.loc < 0 ? 1 : 0;
1154                     hxy = component.pointAlongPathFrom(fromLoc, l, false);
1155                     mid = component.pointAlongPathFrom(fromLoc, l - (direction * this.length / 2), false);
1156                     txy = _jg.pointOnLine(hxy, mid, this.length);
1157                 }
1158                 else if (this.loc == 1) {                
1159                                         hxy = component.pointOnPath(this.loc);                                             
1160                     mid = component.pointAlongPathFrom(this.loc, -(this.length));
1161                                         txy = _jg.pointOnLine(hxy, mid, this.length);
1162                                         
1163                                         if (direction == -1) {
1164                                                 var _ = txy;
1165                                                 txy = hxy;
1166                                                 hxy = _;
1167                                         }
1168                 }
1169                 else if (this.loc === 0) {                                                          
1170                                         txy = component.pointOnPath(this.loc);                    
1171                                         mid = component.pointAlongPathFrom(this.loc, this.length);                    
1172                                         hxy = _jg.pointOnLine(txy, mid, this.length);                    
1173                                         if (direction == -1) {
1174                                                 var __ = txy;
1175                                                 txy = hxy;
1176                                                 hxy = __;
1177                                         }
1178                 }
1179                 else {                    
1180                             hxy = component.pointAlongPathFrom(this.loc, direction * this.length / 2);
1181                     mid = component.pointOnPath(this.loc);
1182                     txy = _jg.pointOnLine(hxy, mid, this.length);
1183                 }
1184
1185                 tail = _jg.perpendicularLineTo(hxy, txy, this.width);
1186                 cxy = _jg.pointOnLine(hxy, txy, foldback * this.length);                        
1187                         
1188                         var d = { hxy:hxy, tail:tail, cxy:cxy },
1189                             strokeStyle = paintStyle.strokeStyle || currentConnectionPaintStyle.strokeStyle,
1190                             fillStyle = paintStyle.fillStyle || currentConnectionPaintStyle.strokeStyle,
1191                             lineWidth = paintStyle.lineWidth || currentConnectionPaintStyle.lineWidth,
1192                     info = {
1193                         component:component, 
1194                         d:d, 
1195                         lineWidth:lineWidth, 
1196                         strokeStyle:strokeStyle, 
1197                         fillStyle:fillStyle,
1198                         minX:Math.min(hxy.x, tail[0].x, tail[1].x),
1199                         maxX:Math.max(hxy.x, tail[0].x, tail[1].x),
1200                         minY:Math.min(hxy.y, tail[0].y, tail[1].y),
1201                         maxY:Math.max(hxy.y, tail[0].y, tail[1].y)
1202                     };                          
1203                                                     
1204                 return info;
1205             }
1206             else return {component:component, minX:0,maxX:0,minY:0,maxY:0};
1207         };
1208     };    
1209     jsPlumbUtil.extend(jsPlumb.Overlays.Arrow, AbstractOverlay);      
1210     
1211     /*
1212      * Class: Overlays.PlainArrow
1213          * 
1214          * A basic arrow.  This is in fact just one instance of the more generic case in which the tail folds back on itself to some
1215          * point along the length of the arrow: in this case, that foldback point is the full length of the arrow.  so it just does
1216          * a 'call' to Arrow with foldback set appropriately.       
1217          */
1218     /*
1219      * Function: Constructor
1220      * See <Overlays.Arrow> for allowed parameters for this overlay.
1221      */
1222     jsPlumb.Overlays.PlainArrow = function(params) {
1223         params = params || {};          
1224         var p = jsPlumb.extend(params, {foldback:1});
1225         jsPlumb.Overlays.Arrow.call(this, p);
1226         this.type = "PlainArrow";
1227     };
1228     jsPlumbUtil.extend(jsPlumb.Overlays.PlainArrow, jsPlumb.Overlays.Arrow);
1229         
1230     /*
1231      * Class: Overlays.Diamond
1232      * 
1233          * A diamond. Like PlainArrow, this is a concrete case of the more generic case of the tail points converging on some point...it just
1234          * happens that in this case, that point is greater than the length of the the arrow.    
1235          * 
1236          *      this could probably do with some help with positioning...due to the way it reuses the Arrow paint code, what Arrow thinks is the
1237          *      center is actually 1/4 of the way along for this guy.  but we don't have any knowledge of pixels at this point, so we're kind of
1238          *      stuck when it comes to helping out the Arrow class. possibly we could pass in a 'transpose' parameter or something. the value
1239          *      would be -l/4 in this case - move along one quarter of the total length.
1240          */
1241     /*
1242      * Function: Constructor
1243      * See <Overlays.Arrow> for allowed parameters for this overlay.
1244      */
1245     jsPlumb.Overlays.Diamond = function(params) {
1246         params = params || {};          
1247         var l = params.length || 40,
1248             p = jsPlumb.extend(params, {length:l/2, foldback:2});
1249         jsPlumb.Overlays.Arrow.call(this, p);
1250         this.type = "Diamond";
1251     };
1252     jsPlumbUtil.extend(jsPlumb.Overlays.Diamond, jsPlumb.Overlays.Arrow);
1253
1254     var _getDimensions = function(component) {
1255         if (component._jsPlumb.cachedDimensions == null)
1256             component._jsPlumb.cachedDimensions = component.getDimensions();
1257         return component._jsPlumb.cachedDimensions;
1258     };      
1259         
1260         // abstract superclass for overlays that add an element to the DOM.
1261     var AbstractDOMOverlay = function(params) {
1262                 jsPlumb.DOMElementComponent.apply(this, arguments);
1263         AbstractOverlay.apply(this, arguments);
1264                 
1265                 var jpcl = jsPlumb.CurrentLibrary;              
1266                 this.id = params.id;
1267         this._jsPlumb.div = null;               
1268         this._jsPlumb.initialised = false;
1269         this._jsPlumb.component = params.component;
1270         this._jsPlumb.cachedDimensions = null;
1271         this._jsPlumb.create = params.create;
1272                 
1273                 this.getElement = function() {
1274                         if (this._jsPlumb.div == null) {
1275                 var div = this._jsPlumb.div = jpcl.getDOMElement(this._jsPlumb.create(this._jsPlumb.component));                
1276                 div.style.position   =   "absolute";     
1277                 var clazz = params._jsPlumb.overlayClass + " " + 
1278                     (this.cssClass ? this.cssClass : 
1279                     params.cssClass ? params.cssClass : "");        
1280                 div.className = clazz;
1281                 this._jsPlumb.instance.appendElement(div, this._jsPlumb.component.parent);
1282                 this._jsPlumb.instance.getId(div);      
1283                 this.attachListeners(div, this);
1284                 this.canvas = div;
1285                         }
1286                 return this._jsPlumb.div;
1287         };
1288                                 
1289                 this.draw = function(component, currentConnectionPaintStyle, absolutePosition) {
1290                 var td = _getDimensions(this);
1291                 if (td != null && td.length == 2) {
1292                                 var cxy = { x:0,y:0 };
1293
1294                 // absolutePosition would have been set by a call to connection.setAbsoluteOverlayPosition.
1295                 if (absolutePosition) {
1296                     cxy = { x:absolutePosition[0], y:absolutePosition[1] };
1297                 }
1298                 else if (component.pointOnPath) {
1299                     var loc = this.loc, absolute = false;
1300                     if (jsPlumbUtil.isString(this.loc) || this.loc < 0 || this.loc > 1) {
1301                         loc = parseInt(this.loc, 10);
1302                         absolute = true;
1303                     }
1304                     cxy = component.pointOnPath(loc, absolute);  // a connection
1305                 }
1306                 else {
1307                     var locToUse = this.loc.constructor == Array ? this.loc : this.endpointLoc;
1308                     cxy = { x:locToUse[0] * component.w,
1309                             y:locToUse[1] * component.h };      
1310                 } 
1311                            
1312                                 var minx = cxy.x - (td[0] / 2),
1313                                     miny = cxy.y - (td[1] / 2);
1314
1315                 return {
1316                     component:component, 
1317                     d:{ minx:minx, miny:miny, td:td, cxy:cxy },
1318                     minX:minx, 
1319                     maxX:minx + td[0], 
1320                     minY:miny, 
1321                     maxY:miny + td[1]
1322                 };                                                              
1323                 }
1324                 else return {minX:0,maxX:0,minY:0,maxY:0};
1325             };                          
1326         };
1327     jsPlumbUtil.extend(AbstractDOMOverlay, [jsPlumb.DOMElementComponent, AbstractOverlay], {
1328         getDimensions : function() {            
1329             return jsPlumb.CurrentLibrary.getSize(jsPlumb.CurrentLibrary.getElementObject(this.getElement()));            
1330         },
1331         setVisible : function(state) {
1332             this._jsPlumb.div.style.display = state ? "block" : "none";
1333         },
1334         /*
1335          * Function: clearCachedDimensions
1336          * Clears the cached dimensions for the label. As a performance enhancement, label dimensions are
1337          * cached from 1.3.12 onwards. The cache is cleared when you change the label text, of course, but
1338          * there are other reasons why the text dimensions might change - if you make a change through CSS, for
1339          * example, you might change the font size.  in that case you should explicitly call this method.
1340          */
1341         clearCachedDimensions : function() {
1342             this._jsPlumb.cachedDimensions = null;
1343         },
1344         cleanup : function() {
1345             if (this._jsPlumb.div != null) 
1346                 jsPlumb.CurrentLibrary.removeElement(this._jsPlumb.div);
1347         },
1348         computeMaxSize : function() {
1349             var td = _getDimensions(this);
1350             return Math.max(td[0], td[1]);
1351         },
1352         reattachListeners : function(connector) {
1353             if (this._jsPlumb.div) {
1354                 this.reattachListenersForElement(this._jsPlumb.div, this, connector);
1355             }
1356         },
1357         paint : function(p, containerExtents) {
1358             if (!this._jsPlumb.initialised) {
1359                 this.getElement();
1360                 p.component.appendDisplayElement(this._jsPlumb.div);
1361                 this.attachListeners(this._jsPlumb.div, p.component);
1362                 this._jsPlumb.initialised = true;
1363             }
1364             this._jsPlumb.div.style.left = (p.component.x + p.d.minx) + "px";
1365             this._jsPlumb.div.style.top = (p.component.y + p.d.miny) + "px";            
1366         }
1367     });
1368         
1369         /*
1370      * Class: Overlays.Custom
1371      * A Custom overlay. You supply a 'create' function which returns some DOM element, and jsPlumb positions it.
1372      * The 'create' function is passed a Connection or Endpoint.
1373      */
1374     /*
1375      * Function: Constructor
1376      * 
1377      * Parameters:
1378      *  create - function for jsPlumb to call that returns a DOM element.
1379      *  location - distance (as a decimal from 0 to 1 inclusive) marking where the label should sit on the connector. defaults to 0.5.
1380      *  id - optional id to use for later retrieval of this overlay.
1381      *  
1382      */
1383     jsPlumb.Overlays.Custom = function(params) {
1384         this.type = "Custom";           
1385         AbstractDOMOverlay.apply(this, arguments);                                                                      
1386     };
1387     jsPlumbUtil.extend(jsPlumb.Overlays.Custom, AbstractDOMOverlay);
1388
1389     jsPlumb.Overlays.GuideLines = function() {
1390         var self = this;
1391         self.length = 50;
1392         self.lineWidth = 5;
1393         this.type = "GuideLines";
1394         AbstractOverlay.apply(this, arguments);
1395         jsPlumb.jsPlumbUIComponent.apply(this, arguments);
1396         this.draw = function(connector, currentConnectionPaintStyle) {
1397
1398             var head = connector.pointAlongPathFrom(self.loc, self.length / 2),
1399                 mid = connector.pointOnPath(self.loc),
1400                 tail = jsPlumbGeom.pointOnLine(head, mid, self.length),
1401                 tailLine = jsPlumbGeom.perpendicularLineTo(head, tail, 40),
1402                 headLine = jsPlumbGeom.perpendicularLineTo(tail, head, 20);
1403
1404             return {
1405                 connector:connector,
1406                 head:head,
1407                 tail:tail,
1408                 headLine:headLine,
1409                 tailLine:tailLine,                
1410                 minX:Math.min(head.x, tail.x, headLine[0].x, headLine[1].x), 
1411                 minY:Math.min(head.y, tail.y, headLine[0].y, headLine[1].y), 
1412                 maxX:Math.max(head.x, tail.x, headLine[0].x, headLine[1].x), 
1413                 maxY:Math.max(head.y, tail.y, headLine[0].y, headLine[1].y)
1414             };
1415         };
1416
1417        // this.cleanup = function() { };  // nothing to clean up for GuideLines
1418     };
1419     
1420     /*
1421      * Class: Overlays.Label
1422      
1423      */
1424     /*
1425      * Function: Constructor
1426      * 
1427      * Parameters:
1428      *  cssClass - optional css class string to append to css class. This string is appended "as-is", so you can of course have multiple classes
1429      *             defined.  This parameter is preferred to using labelStyle, borderWidth and borderStyle.
1430      *  label - the label to paint.  May be a string or a function that returns a string.  Nothing will be painted if your label is null or your
1431      *         label function returns null.  empty strings _will_ be painted.
1432      *  location - distance (as a decimal from 0 to 1 inclusive) marking where the label should sit on the connector. defaults to 0.5.
1433      *  id - optional id to use for later retrieval of this overlay.
1434      * 
1435      *  
1436      */
1437     jsPlumb.Overlays.Label =  function(params) {                   
1438                 this.labelStyle = params.labelStyle;
1439         
1440         var labelWidth = null, labelHeight =  null, labelText = null, labelPadding = null;
1441                 this.cssClass = this.labelStyle != null ? this.labelStyle.cssClass : null;
1442                 var p = jsPlumb.extend({
1443             create : function() {
1444                 return document.createElement("div");
1445             }}, params);
1446         jsPlumb.Overlays.Custom.call(this, p);
1447                 this.type = "Label";            
1448         this.label = params.label || "";
1449         this.labelText = null;
1450         if (this.labelStyle) {
1451             var el = this.getElement();            
1452             this.labelStyle.font = this.labelStyle.font || "12px sans-serif";
1453             el.style.font = this.labelStyle.font;
1454             el.style.color = this.labelStyle.color || "black";
1455             if (this.labelStyle.fillStyle) el.style.background = this.labelStyle.fillStyle;
1456             if (this.labelStyle.borderWidth > 0) {
1457                 var dStyle = this.labelStyle.borderStyle ? this.labelStyle.borderStyle : "black";
1458                 el.style.border = this.labelStyle.borderWidth  + "px solid " + dStyle;
1459             }
1460             if (this.labelStyle.padding) el.style.padding = this.labelStyle.padding;            
1461         }
1462
1463     };
1464     jsPlumbUtil.extend(jsPlumb.Overlays.Label, jsPlumb.Overlays.Custom, {
1465         cleanup:function() {
1466             this.div = null;
1467             this.label = null;
1468             this.labelText = null;
1469             this.cssClass = null;
1470             this.labelStyle = null;
1471         },
1472         getLabel : function() {
1473             return this.label;
1474         },
1475         /*
1476          * Function: setLabel
1477          * sets the label's, um, label.  you would think i'd call this function
1478          * 'setText', but you can pass either a Function or a String to this, so
1479          * it makes more sense as 'setLabel'. This uses innerHTML on the label div, so keep
1480          * that in mind if you need escaped HTML.
1481          */
1482         setLabel : function(l) {
1483             this.label = l;
1484             this.labelText = null;
1485             this.clearCachedDimensions();
1486             this.update();
1487             this.component.repaint();
1488         },
1489         getDimensions : function() {                
1490             this.update();
1491             return AbstractDOMOverlay.prototype.getDimensions.apply(this, arguments);
1492         },
1493         update : function() {
1494             if (typeof this.label == "function") {
1495                 var lt = this.label(this);
1496                 this.getElement().innerHTML = lt.replace(/\r\n/g, "<br/>");
1497             }
1498             else {
1499                 if (this.labelText == null) {
1500                     this.labelText = this.label;
1501                     this.getElement().innerHTML = this.labelText.replace(/\r\n/g, "<br/>");
1502                 }
1503             }
1504         }
1505     });         
1506
1507  // ********************************* END OF OVERLAY DEFINITIONS ***********************************************************************
1508     
1509 })();