reservation plugin - unbound request (unclean
[unfold.git] / portal / static / unbound_reservation_static / src / anchors.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 code for creating and manipulating anchors.
10  *
11  * Copyright (c) 2010 - 2013 Simon Porritt (simon.porritt@gmail.com)
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 ;(function() {  
20     
21     //
22         // manages anchors for all elements.
23         //
24         jsPlumb.AnchorManager = function(params) {
25                 var _amEndpoints = {},
26             continuousAnchors = {},
27             continuousAnchorLocations = {},
28             userDefinedContinuousAnchorLocations = {},        
29             continuousAnchorOrientations = {},
30             Orientation = { HORIZONTAL : "horizontal", VERTICAL : "vertical", DIAGONAL : "diagonal", IDENTITY:"identity" },
31                         connectionsByElementId = {},
32                         self = this,
33             anchorLists = {},
34             jsPlumbInstance = params.jsPlumbInstance,
35             jpcl = jsPlumb.CurrentLibrary,
36             floatingConnections = {},
37             // TODO this functions uses a crude method of determining orientation between two elements.
38             // 'diagonal' should be chosen when the angle of the line between the two centers is around
39             // one of 45, 135, 225 and 315 degrees. maybe +- 15 degrees.
40             // used by AnchorManager.redraw
41             calculateOrientation = function(sourceId, targetId, sd, td, sourceAnchor, targetAnchor) {
42         
43                 if (sourceId === targetId) return {
44                     orientation:Orientation.IDENTITY,
45                     a:["top", "top"]
46                 };
47         
48                 var theta = Math.atan2((td.centery - sd.centery) , (td.centerx - sd.centerx)),
49                     theta2 = Math.atan2((sd.centery - td.centery) , (sd.centerx - td.centerx)),
50                     h = ((sd.left <= td.left && sd.right >= td.left) || (sd.left <= td.right && sd.right >= td.right) ||
51                         (sd.left <= td.left && sd.right >= td.right) || (td.left <= sd.left && td.right >= sd.right)),
52                     v = ((sd.top <= td.top && sd.bottom >= td.top) || (sd.top <= td.bottom && sd.bottom >= td.bottom) ||
53                         (sd.top <= td.top && sd.bottom >= td.bottom) || (td.top <= sd.top && td.bottom >= sd.bottom)),
54                     possiblyTranslateEdges = function(edges) {
55                         // this function checks to see if either anchor is Continuous, and if so, runs the suggested edge
56                         // through the anchor: Continuous anchors can say which faces they support, and they get to choose 
57                         // whether a certain face is honoured, or, if not, which face to replace it with. the behaviour when
58                         // choosing an alternate face is to try for the opposite face first, then the next one clockwise, and then
59                         // the opposite of that one.
60                         return [
61                             sourceAnchor.isContinuous ? sourceAnchor.verifyEdge(edges[0]) : edges[0],    
62                             targetAnchor.isContinuous ? targetAnchor.verifyEdge(edges[1]) : edges[1]
63                         ];
64                     },
65                     out = {
66                         orientation:Orientation.DIAGONAL,
67                         theta:theta,
68                         theta2:theta2
69                     };                        
70                 
71                 if (! (h || v)) {                    
72                     if (td.left > sd.left && td.top > sd.top)
73                         out.a = ["right", "top"];
74                     else if (td.left > sd.left && sd.top > td.top)
75                         out.a = [ "top", "left"];
76                     else if (td.left < sd.left && td.top < sd.top)
77                         out.a = [ "top", "right"];
78                     else if (td.left < sd.left && td.top > sd.top)
79                         out.a = ["left", "top" ];                            
80                 }
81                 else if (h) {
82                     out.orientation = Orientation.HORIZONTAL;
83                     out.a = sd.top < td.top ? ["bottom", "top"] : ["top", "bottom"];                    
84                 }
85                 else {
86                     out.orientation = Orientation.VERTICAL;
87                     out.a = sd.left < td.left ? ["right", "left"] : ["left", "right"];
88                 }
89                 
90                 out.a = possiblyTranslateEdges(out.a);
91                 return out;
92             },
93                 // used by placeAnchors function
94             placeAnchorsOnLine = function(desc, elementDimensions, elementPosition,
95                             connections, horizontal, otherMultiplier, reverse) {
96                 var a = [], step = elementDimensions[horizontal ? 0 : 1] / (connections.length + 1);
97         
98                 for (var i = 0; i < connections.length; i++) {
99                     var val = (i + 1) * step, other = otherMultiplier * elementDimensions[horizontal ? 1 : 0];
100                     if (reverse)
101                       val = elementDimensions[horizontal ? 0 : 1] - val;
102         
103                     var dx = (horizontal ? val : other), x = elementPosition[0] + dx,  xp = dx / elementDimensions[0],
104                         dy = (horizontal ? other : val), y = elementPosition[1] + dy, yp = dy / elementDimensions[1];
105         
106                     a.push([ x, y, xp, yp, connections[i][1], connections[i][2] ]);
107                 }
108         
109                 return a;
110             },
111             // used by edgeSortFunctions        
112             currySort = function(reverseAngles) {
113                 return function(a,b) {
114                     var r = true;
115                     if (reverseAngles) {
116                         /*if (a[0][0] < b[0][0])
117                             r = true;
118                         else
119                             r = a[0][1] > b[0][1];*/
120                         r = a[0][0] < b[0][0];
121                     }
122                     else {
123                         /*if (a[0][0] > b[0][0])
124                             r= true;
125                         else
126                             r =a[0][1] > b[0][1];
127                         */
128                         r = a[0][0] > b[0][0];
129                     }
130                     return r === false ? -1 : 1;
131                 };
132             },
133                 // used by edgeSortFunctions
134             leftSort = function(a,b) {
135                 // first get adjusted values
136                 var p1 = a[0][0] < 0 ? -Math.PI - a[0][0] : Math.PI - a[0][0],
137                 p2 = b[0][0] < 0 ? -Math.PI - b[0][0] : Math.PI - b[0][0];
138                 if (p1 > p2) return 1;
139                 else return a[0][1] > b[0][1] ? 1 : -1;
140             },
141                 // used by placeAnchors
142             edgeSortFunctions = {
143                 "top":function(a, b) { return a[0] > b[0] ? 1 : -1; },
144                 "right":currySort(true),
145                 "bottom":currySort(true),
146                 "left":leftSort
147             },
148                 // used by placeAnchors
149             _sortHelper = function(_array, _fn) { return _array.sort(_fn); },
150                 // used by AnchorManager.redraw
151             placeAnchors = function(elementId, _anchorLists) {          
152                 var cd = jsPlumbInstance.getCachedData(elementId), sS = cd.s, sO = cd.o,
153                 placeSomeAnchors = function(desc, elementDimensions, elementPosition, unsortedConnections, isHorizontal, otherMultiplier, orientation) {
154                     if (unsortedConnections.length > 0) {
155                         var sc = _sortHelper(unsortedConnections, edgeSortFunctions[desc]), // puts them in order based on the target element's pos on screen
156                             reverse = desc === "right" || desc === "top",
157                             anchors = placeAnchorsOnLine(desc, elementDimensions,
158                                                      elementPosition, sc,
159                                                      isHorizontal, otherMultiplier, reverse );
160         
161                         // takes a computed anchor position and adjusts it for parent offset and scroll, then stores it.
162                         var _setAnchorLocation = function(endpoint, anchorPos) {
163                             var a = jsPlumbInstance.adjustForParentOffsetAndScroll([anchorPos[0], anchorPos[1]], endpoint.canvas);
164                             continuousAnchorLocations[endpoint.id] = [ a[0], a[1], anchorPos[2], anchorPos[3] ];
165                             continuousAnchorOrientations[endpoint.id] = orientation;
166                         };
167         
168                         for (var i = 0; i < anchors.length; i++) {
169                             var c = anchors[i][4], weAreSource = c.endpoints[0].elementId === elementId, weAreTarget = c.endpoints[1].elementId === elementId;
170                             if (weAreSource)
171                                 _setAnchorLocation(c.endpoints[0], anchors[i]);
172                             else if (weAreTarget)
173                                 _setAnchorLocation(c.endpoints[1], anchors[i]);
174                         }
175                     }
176                 };
177         
178                 placeSomeAnchors("bottom", sS, [sO.left,sO.top], _anchorLists.bottom, true, 1, [0,1]);
179                 placeSomeAnchors("top", sS, [sO.left,sO.top], _anchorLists.top, true, 0, [0,-1]);
180                 placeSomeAnchors("left", sS, [sO.left,sO.top], _anchorLists.left, false, 0, [-1,0]);
181                 placeSomeAnchors("right", sS, [sO.left,sO.top], _anchorLists.right, false, 1, [1,0]);
182             };
183
184         this.reset = function() {
185             _amEndpoints = {};
186             connectionsByElementId = {};
187             anchorLists = {};
188         };                      
189         this.addFloatingConnection = function(key, conn) {
190             floatingConnections[key] = conn;
191         };
192         this.removeFloatingConnection = function(key) {
193             delete floatingConnections[key];
194         };                                                 
195         this.newConnection = function(conn) {
196                         var sourceId = conn.sourceId, targetId = conn.targetId,
197                                 ep = conn.endpoints,
198                 doRegisterTarget = true,
199                 registerConnection = function(otherIndex, otherEndpoint, otherAnchor, elId, c) {
200                                         if ((sourceId == targetId) && otherAnchor.isContinuous){
201                        // remove the target endpoint's canvas.  we dont need it.
202                         jpcl.removeElement(ep[1].canvas);
203                         doRegisterTarget = false;
204                     }
205                                         jsPlumbUtil.addToList(connectionsByElementId, elId, [c, otherEndpoint, otherAnchor.constructor == jsPlumb.DynamicAnchor]);
206                             };
207
208                         registerConnection(0, ep[0], ep[0].anchor, targetId, conn);
209             if (doRegisterTarget)
210                 registerConnection(1, ep[1], ep[1].anchor, sourceId, conn);
211                 };
212         var removeEndpointFromAnchorLists = function(endpoint) {
213             (function(list, eId) {
214                 if (list) {  // transient anchors dont get entries in this list.
215                     var f = function(e) { return e[4] == eId; };
216                     jsPlumbUtil.removeWithFunction(list.top, f);
217                     jsPlumbUtil.removeWithFunction(list.left, f);
218                     jsPlumbUtil.removeWithFunction(list.bottom, f);
219                     jsPlumbUtil.removeWithFunction(list.right, f);
220                 }
221             })(anchorLists[endpoint.elementId], endpoint.id);
222         };
223                 this.connectionDetached = function(connInfo) {
224             var connection = connInfo.connection || connInfo,
225                             sourceId = connInfo.sourceId,
226                 targetId = connInfo.targetId,
227                                 ep = connection.endpoints,
228                                 removeConnection = function(otherIndex, otherEndpoint, otherAnchor, elId, c) {
229                                         if (otherAnchor != null && otherAnchor.constructor == jsPlumb.FloatingAnchor) {
230                                                 // no-op
231                                         }
232                                         else {
233                                                 jsPlumbUtil.removeWithFunction(connectionsByElementId[elId], function(_c) {
234                                                         return _c[0].id == c.id;
235                                                 });
236                                         }
237                                 };
238                                 
239                         removeConnection(1, ep[1], ep[1].anchor, sourceId, connection);
240                         removeConnection(0, ep[0], ep[0].anchor, targetId, connection);
241
242             // remove from anchorLists            
243             removeEndpointFromAnchorLists(connection.endpoints[0]);
244             removeEndpointFromAnchorLists(connection.endpoints[1]);
245
246             self.redraw(connection.sourceId);
247             self.redraw(connection.targetId);
248                 };
249                 this.add = function(endpoint, elementId) {
250                         jsPlumbUtil.addToList(_amEndpoints, elementId, endpoint);
251                 };
252                 this.changeId = function(oldId, newId) {
253                         connectionsByElementId[newId] = connectionsByElementId[oldId];
254                         _amEndpoints[newId] = _amEndpoints[oldId];
255                         delete connectionsByElementId[oldId];
256                         delete _amEndpoints[oldId];     
257                 };
258                 this.getConnectionsFor = function(elementId) {
259                         return connectionsByElementId[elementId] || [];
260                 };
261                 this.getEndpointsFor = function(elementId) {
262                         return _amEndpoints[elementId] || [];
263                 };
264                 this.deleteEndpoint = function(endpoint) {
265                         jsPlumbUtil.removeWithFunction(_amEndpoints[endpoint.elementId], function(e) {
266                                 return e.id == endpoint.id;
267                         });
268             removeEndpointFromAnchorLists(endpoint);
269                 };
270                 this.clearFor = function(elementId) {
271                         delete _amEndpoints[elementId];
272                         _amEndpoints[elementId] = [];
273                 };
274         // updates the given anchor list by either updating an existing anchor's info, or adding it. this function
275         // also removes the anchor from its previous list, if the edge it is on has changed.
276         // all connections found along the way (those that are connected to one of the faces this function
277         // operates on) are added to the connsToPaint list, as are their endpoints. in this way we know to repaint
278         // them wthout having to calculate anything else about them.
279         var _updateAnchorList = function(lists, theta, order, conn, aBoolean, otherElId, idx, reverse, edgeId, elId, connsToPaint, endpointsToPaint) {        
280             // first try to find the exact match, but keep track of the first index of a matching element id along the way.s
281             var exactIdx = -1,
282                 firstMatchingElIdx = -1,
283                 endpoint = conn.endpoints[idx],
284                 endpointId = endpoint.id,
285                 oIdx = [1,0][idx],
286                 values = [ [ theta, order ], conn, aBoolean, otherElId, endpointId ],
287                 listToAddTo = lists[edgeId],
288                 listToRemoveFrom = endpoint._continuousAnchorEdge ? lists[endpoint._continuousAnchorEdge] : null;
289
290             if (listToRemoveFrom) {
291                 var rIdx = jsPlumbUtil.findWithFunction(listToRemoveFrom, function(e) { return e[4] == endpointId; });
292                 if (rIdx != -1) {
293                     listToRemoveFrom.splice(rIdx, 1);
294                     // get all connections from this list
295                     for (var i = 0; i < listToRemoveFrom.length; i++) {
296                         jsPlumbUtil.addWithFunction(connsToPaint, listToRemoveFrom[i][1], function(c) { return c.id == listToRemoveFrom[i][1].id; });
297                         jsPlumbUtil.addWithFunction(endpointsToPaint, listToRemoveFrom[i][1].endpoints[idx], function(e) { return e.id == listToRemoveFrom[i][1].endpoints[idx].id; });
298                         jsPlumbUtil.addWithFunction(endpointsToPaint, listToRemoveFrom[i][1].endpoints[oIdx], function(e) { return e.id == listToRemoveFrom[i][1].endpoints[oIdx].id; });
299                     }
300                 }
301             }
302
303             for (i = 0; i < listToAddTo.length; i++) {
304                 if (params.idx == 1 && listToAddTo[i][3] === otherElId && firstMatchingElIdx == -1)
305                     firstMatchingElIdx = i;
306                 jsPlumbUtil.addWithFunction(connsToPaint, listToAddTo[i][1], function(c) { return c.id == listToAddTo[i][1].id; });                
307                 jsPlumbUtil.addWithFunction(endpointsToPaint, listToAddTo[i][1].endpoints[idx], function(e) { return e.id == listToAddTo[i][1].endpoints[idx].id; });
308                 jsPlumbUtil.addWithFunction(endpointsToPaint, listToAddTo[i][1].endpoints[oIdx], function(e) { return e.id == listToAddTo[i][1].endpoints[oIdx].id; });
309             }
310             if (exactIdx != -1) {
311                 listToAddTo[exactIdx] = values;
312             }
313             else {
314                 var insertIdx = reverse ? firstMatchingElIdx != -1 ? firstMatchingElIdx : 0 : listToAddTo.length; // of course we will get this from having looked through the array shortly.
315                 listToAddTo.splice(insertIdx, 0, values);
316             }
317
318             // store this for next time.
319             endpoint._continuousAnchorEdge = edgeId;
320         };
321
322         //
323         // find the entry in an endpoint's list for this connection and update its target endpoint
324         // with the current target in the connection.
325         // 
326         //
327         this.updateOtherEndpoint = function(elId, oldTargetId, newTargetId, connection) {
328             var sIndex = jsPlumbUtil.findWithFunction(connectionsByElementId[elId], function(i) {
329                     return i[0].id === connection.id;
330                 }),
331                 tIndex = jsPlumbUtil.findWithFunction(connectionsByElementId[oldTargetId], function(i) {
332                     return i[0].id === connection.id;
333                 });
334
335             // update or add data for source
336             if (sIndex != -1) {
337                 connectionsByElementId[elId][sIndex][0] = connection;
338                 connectionsByElementId[elId][sIndex][1] = connection.endpoints[1];
339                 connectionsByElementId[elId][sIndex][2] = connection.endpoints[1].anchor.constructor == jsPlumb.DynamicAnchor;
340             }
341
342             // remove entry for previous target (if there)
343             if (tIndex > -1) {
344
345                 connectionsByElementId[oldTargetId].splice(tIndex, 1);
346                 // add entry for new target
347                 jsPlumbUtil.addToList(connectionsByElementId, newTargetId, [connection, connection.endpoints[0], connection.endpoints[0].anchor.constructor == jsPlumb.DynamicAnchor]);         
348             }
349         };       
350         
351         //
352         // notification that the connection given has changed source from the originalId to the newId.
353         // This involves:
354         // 1. removing the connection from the list of connections stored for the originalId
355         // 2. updating the source information for the target of the connection
356         // 3. re-registering the connection in connectionsByElementId with the newId
357         //
358         this.sourceChanged = function(originalId, newId, connection) {            
359             // remove the entry that points from the old source to the target
360             jsPlumbUtil.removeWithFunction(connectionsByElementId[originalId], function(info) {
361                 return info[0].id === connection.id;
362             });
363             // find entry for target and update it
364             var tIdx = jsPlumbUtil.findWithFunction(connectionsByElementId[connection.targetId], function(i) {
365                 return i[0].id === connection.id;
366             });
367             if (tIdx > -1) {
368                 connectionsByElementId[connection.targetId][tIdx][0] = connection;
369                 connectionsByElementId[connection.targetId][tIdx][1] = connection.endpoints[0];
370                 connectionsByElementId[connection.targetId][tIdx][2] = connection.endpoints[0].anchor.constructor == jsPlumb.DynamicAnchor;
371             }
372             // add entry for new source
373             jsPlumbUtil.addToList(connectionsByElementId, newId, [connection, connection.endpoints[1], connection.endpoints[1].anchor.constructor == jsPlumb.DynamicAnchor]);         
374         };
375
376         //
377         // moves the given endpoint from `currentId` to `element`.
378         // This involves:
379         //
380         // 1. changing the key in _amEndpoints under which the endpoint is stored
381         // 2. changing the source or target values in all of the endpoint's connections
382         // 3. changing the array in connectionsByElementId in which the endpoint's connections
383         //    are stored (done by either sourceChanged or updateOtherEndpoint)
384         //
385         this.rehomeEndpoint = function(ep, currentId, element) {
386             var eps = _amEndpoints[currentId] || [], 
387                 elementId = jsPlumbInstance.getId(element);
388                 
389             if (elementId !== currentId) {
390                 var idx = jsPlumbUtil.indexOf(eps, ep);
391                 if (idx > -1) {
392                     var _ep = eps.splice(idx, 1)[0];
393                     self.add(_ep, elementId);
394                 }
395             }
396
397             for (var i = 0; i < ep.connections.length; i++) {                
398                 if (ep.connections[i].sourceId == currentId) {
399                     ep.connections[i].sourceId = ep.elementId;
400                     ep.connections[i].source = ep.element;                  
401                     self.sourceChanged(currentId, ep.elementId, ep.connections[i]);
402                 }
403                 else if(ep.connections[i].targetId == currentId) {
404                     ep.connections[i].targetId = ep.elementId;
405                     ep.connections[i].target = ep.element;   
406                     self.updateOtherEndpoint(ep.connections[i].sourceId, currentId, ep.elementId, ep.connections[i]);               
407                 }
408             }   
409         };
410
411                 this.redraw = function(elementId, ui, timestamp, offsetToUI, clearEdits, doNotRecalcEndpoint) {
412                 
413                         if (!jsPlumbInstance.isSuspendDrawing()) {
414                                 // get all the endpoints for this element
415                                 var ep = _amEndpoints[elementId] || [],
416                                         endpointConnections = connectionsByElementId[elementId] || [],
417                                         connectionsToPaint = [],
418                                         endpointsToPaint = [],
419                         anchorsToUpdate = [];
420                     
421                                 timestamp = timestamp || jsPlumbInstance.timestamp();
422                                 // offsetToUI are values that would have been calculated in the dragManager when registering
423                                 // an endpoint for an element that had a parent (somewhere in the hierarchy) that had been
424                                 // registered as draggable.
425                                 offsetToUI = offsetToUI || {left:0, top:0};
426                                 if (ui) {
427                                         ui = {
428                                                 left:ui.left + offsetToUI.left,
429                                                 top:ui.top + offsetToUI.top
430                                         };
431                                 }
432                                                                         
433                                 // valid for one paint cycle.
434                                 var myOffset = jsPlumbInstance.updateOffset( { elId : elementId, offset : ui, recalc : false, timestamp : timestamp }),
435                         orientationCache = {};
436                                 
437                                 // actually, first we should compute the orientation of this element to all other elements to which
438                                 // this element is connected with a continuous anchor (whether both ends of the connection have
439                                 // a continuous anchor or just one)
440                                 
441                     for (var i = 0; i < endpointConnections.length; i++) {
442                         var conn = endpointConnections[i][0],
443                                                 sourceId = conn.sourceId,
444                             targetId = conn.targetId,
445                             sourceContinuous = conn.endpoints[0].anchor.isContinuous,
446                             targetContinuous = conn.endpoints[1].anchor.isContinuous;
447         
448                         if (sourceContinuous || targetContinuous) {
449                                 var oKey = sourceId + "_" + targetId,
450                                     oKey2 = targetId + "_" + sourceId,
451                                     o = orientationCache[oKey],
452                                     oIdx = conn.sourceId == elementId ? 1 : 0;
453         
454                                 if (sourceContinuous && !anchorLists[sourceId]) anchorLists[sourceId] = { top:[], right:[], bottom:[], left:[] };
455                                 if (targetContinuous && !anchorLists[targetId]) anchorLists[targetId] = { top:[], right:[], bottom:[], left:[] };
456         
457                                 if (elementId != targetId) jsPlumbInstance.updateOffset( { elId : targetId, timestamp : timestamp }); 
458                                 if (elementId != sourceId) jsPlumbInstance.updateOffset( { elId : sourceId, timestamp : timestamp }); 
459         
460                                 var td = jsPlumbInstance.getCachedData(targetId),
461                                                         sd = jsPlumbInstance.getCachedData(sourceId);
462         
463                                 if (targetId == sourceId && (sourceContinuous || targetContinuous)) {
464                                     // here we may want to improve this by somehow determining the face we'd like
465                                                     // to put the connector on.  ideally, when drawing, the face should be calculated
466                                                     // by determining which face is closest to the point at which the mouse button
467                                                         // was released.  for now, we're putting it on the top face.                            
468                                     _updateAnchorList(
469                                 anchorLists[sourceId], 
470                                 -Math.PI / 2, 
471                                 0, 
472                                 conn, 
473                                 false, 
474                                 targetId, 
475                                 0, false, "top", sourceId, connectionsToPaint, endpointsToPaint);
476                                                 }
477                                 else {
478                                     if (!o) {
479                                         o = calculateOrientation(sourceId, targetId, sd.o, td.o, conn.endpoints[0].anchor, conn.endpoints[1].anchor);
480                                         orientationCache[oKey] = o;
481                                         // this would be a performance enhancement, but the computed angles need to be clamped to
482                                         //the (-PI/2 -> PI/2) range in order for the sorting to work properly.
483                                     /*  orientationCache[oKey2] = {
484                                             orientation:o.orientation,
485                                             a:[o.a[1], o.a[0]],
486                                             theta:o.theta + Math.PI,
487                                             theta2:o.theta2 + Math.PI
488                                         };*/
489                                     }
490                                     if (sourceContinuous) _updateAnchorList(anchorLists[sourceId], o.theta, 0, conn, false, targetId, 0, false, o.a[0], sourceId, connectionsToPaint, endpointsToPaint);
491                                     if (targetContinuous) _updateAnchorList(anchorLists[targetId], o.theta2, -1, conn, true, sourceId, 1, true, o.a[1], targetId, connectionsToPaint, endpointsToPaint);
492                                 }
493         
494                                 if (sourceContinuous) jsPlumbUtil.addWithFunction(anchorsToUpdate, sourceId, function(a) { return a === sourceId; });
495                                 if (targetContinuous) jsPlumbUtil.addWithFunction(anchorsToUpdate, targetId, function(a) { return a === targetId; });
496                                 jsPlumbUtil.addWithFunction(connectionsToPaint, conn, function(c) { return c.id == conn.id; });
497                                 if ((sourceContinuous && oIdx === 0) || (targetContinuous && oIdx === 1))
498                                         jsPlumbUtil.addWithFunction(endpointsToPaint, conn.endpoints[oIdx], function(e) { return e.id == conn.endpoints[oIdx].id; });
499                             }
500                     }                           
501                                 // place Endpoints whose anchors are continuous but have no Connections
502                                 for (i = 0; i < ep.length; i++) {
503                                         if (ep[i].connections.length === 0 && ep[i].anchor.isContinuous) {
504                                                 if (!anchorLists[elementId]) anchorLists[elementId] = { top:[], right:[], bottom:[], left:[] };
505                                                 _updateAnchorList(anchorLists[elementId], -Math.PI / 2, 0, {endpoints:[ep[i], ep[i]], paint:function(){}}, false, elementId, 0, false, "top", elementId, connectionsToPaint, endpointsToPaint);
506                                                 jsPlumbUtil.addWithFunction(anchorsToUpdate, elementId, function(a) { return a === elementId; });
507                                         }
508                                 }
509                     // now place all the continuous anchors we need to;
510                     for (i = 0; i < anchorsToUpdate.length; i++) {
511                                         placeAnchors(anchorsToUpdate[i], anchorLists[anchorsToUpdate[i]]);
512                                 }
513
514                                 // now that continuous anchors have been placed, paint all the endpoints for this element
515                     // TODO performance: add the endpoint ids to a temp array, and then when iterating in the next
516                     // loop, check that we didn't just paint that endpoint. we can probably shave off a few more milliseconds this way.
517                                 for (i = 0; i < ep.length; i++) {                               
518                     ep[i].paint( { timestamp : timestamp, offset : myOffset, dimensions : myOffset.s, recalc:doNotRecalcEndpoint !== true });
519                                 }
520                     // ... and any other endpoints we came across as a result of the continuous anchors.
521                     for (i = 0; i < endpointsToPaint.length; i++) {
522                     var cd = jsPlumbInstance.getCachedData(endpointsToPaint[i].elementId);
523                     // dont use timestamp for this endpoint, as it is not for the current element and we may 
524                     // have needed to recalculate anchor position due to the element for the endpoint moving.
525                     //endpointsToPaint[i].paint( { timestamp : null, offset : cd, dimensions : cd.s });
526
527                     endpointsToPaint[i].paint( { timestamp : timestamp, offset : cd, dimensions : cd.s });
528                                 }
529
530                                 // paint all the standard and "dynamic connections", which are connections whose other anchor is
531                                 // static and therefore does need to be recomputed; we make sure that happens only one time.
532         
533                                 // TODO we could have compiled a list of these in the first pass through connections; might save some time.
534                                 for (i = 0; i < endpointConnections.length; i++) {
535                                         var otherEndpoint = endpointConnections[i][1];
536                                         if (otherEndpoint.anchor.constructor == jsPlumb.DynamicAnchor) {                                                                                
537                                                 otherEndpoint.paint({ elementWithPrecedence:elementId, timestamp:timestamp });                                                          
538                             jsPlumbUtil.addWithFunction(connectionsToPaint, endpointConnections[i][0], function(c) { return c.id == endpointConnections[i][0].id; });
539                                                 // all the connections for the other endpoint now need to be repainted
540                                                 for (var k = 0; k < otherEndpoint.connections.length; k++) {
541                                                         if (otherEndpoint.connections[k] !== endpointConnections[i][0])                                                 
542                                     jsPlumbUtil.addWithFunction(connectionsToPaint, otherEndpoint.connections[k], function(c) { return c.id == otherEndpoint.connections[k].id; });
543                                                 }
544                                         } else if (otherEndpoint.anchor.constructor == jsPlumb.Anchor) {                                        
545                             jsPlumbUtil.addWithFunction(connectionsToPaint, endpointConnections[i][0], function(c) { return c.id == endpointConnections[i][0].id; });
546                                         }
547                                 }
548                                 // paint current floating connection for this element, if there is one.
549                                 var fc = floatingConnections[elementId];
550                                 if (fc) 
551                                         fc.paint({timestamp:timestamp, recalc:false, elId:elementId});
552                                                 
553                                 // paint all the connections
554                                 for (i = 0; i < connectionsToPaint.length; i++) {
555                                         // if not a connection between the two elements in question dont use the timestamp.
556                     var ts  =timestamp;// ((connectionsToPaint[i].sourceId == sourceId && connectionsToPaint[i].targetId == targetId) ||
557                                //(connectionsToPaint[i].sourceId == targetId && connectionsToPaint[i].targetId == sourceId)) ? timestamp : null;
558                     connectionsToPaint[i].paint({elId:elementId, timestamp:ts, recalc:false, clearEdits:clearEdits});
559                                 }
560                         }
561                 };        
562         
563         var ContinuousAnchor = function(anchorParams) {
564             jsPlumbUtil.EventGenerator.apply(this);
565             this.type = "Continuous";
566             this.isDynamic = true;
567             this.isContinuous = true;
568             var faces = anchorParams.faces || ["top", "right", "bottom", "left"],
569                 clockwise = !(anchorParams.clockwise === false),
570                 availableFaces = { },
571                 opposites = { "top":"bottom", "right":"left","left":"right","bottom":"top" },
572                 clockwiseOptions = { "top":"right", "right":"bottom","left":"top","bottom":"left" },
573                 antiClockwiseOptions = { "top":"left", "right":"top","left":"bottom","bottom":"right" },
574                 secondBest = clockwise ? clockwiseOptions : antiClockwiseOptions,
575                 lastChoice = clockwise ? antiClockwiseOptions : clockwiseOptions,
576                 cssClass = anchorParams.cssClass || "";
577             
578             for (var i = 0; i < faces.length; i++) { availableFaces[faces[i]] = true; }
579           
580             // if the given edge is supported, returns it. otherwise looks for a substitute that _is_
581             // supported. if none supported we also return the request edge.
582             this.verifyEdge = function(edge) {
583                 if (availableFaces[edge]) return edge;
584                 else if (availableFaces[opposites[edge]]) return opposites[edge];
585                 else if (availableFaces[secondBest[edge]]) return secondBest[edge];
586                 else if (availableFaces[lastChoice[edge]]) return lastChoice[edge];
587                 return edge; // we have to give them something.
588             };
589             
590             this.compute = function(params) {
591                 return userDefinedContinuousAnchorLocations[params.element.id] || continuousAnchorLocations[params.element.id] || [0,0];
592             };
593             this.getCurrentLocation = function(params) {
594                 return userDefinedContinuousAnchorLocations[params.element.id] || continuousAnchorLocations[params.element.id] || [0,0];
595             };
596             this.getOrientation = function(endpoint) {
597                 return continuousAnchorOrientations[endpoint.id] || [0,0];
598             };
599             this.clearUserDefinedLocation = function() { 
600                 delete userDefinedContinuousAnchorLocations[anchorParams.elementId]; 
601             };
602             this.setUserDefinedLocation = function(loc) { 
603                 userDefinedContinuousAnchorLocations[anchorParams.elementId] = loc; 
604             };            
605             this.getCssClass = function() { return cssClass; };
606             this.setCssClass = function(c) { cssClass = c; };
607         };        
608         
609         // continuous anchors
610         jsPlumbInstance.continuousAnchorFactory = {
611             get:function(params) {
612                 var existing = continuousAnchors[params.elementId];
613                 if (!existing) {
614                     existing = new ContinuousAnchor(params);                    
615                     continuousAnchors[params.elementId] = existing;
616                 }
617                 return existing;
618             },
619             clear:function(elementId) {
620                 delete continuousAnchors[elementId];
621             }
622         };
623         };
624     
625     /**
626      * Anchors model a position on some element at which an Endpoint may be located.  They began as a first class citizen of jsPlumb, ie. a user
627      * was required to create these themselves, but over time this has been replaced by the concept of referring to them either by name (eg. "TopMiddle"),
628      * or by an array describing their coordinates (eg. [ 0, 0.5, 0, -1 ], which is the same as "TopMiddle").  jsPlumb now handles all of the
629      * creation of Anchors without user intervention.
630      */
631     jsPlumb.Anchor = function(params) {       
632         this.x = params.x || 0;
633         this.y = params.y || 0;
634         this.elementId = params.elementId;  
635         this.cssClass = params.cssClass || "";      
636         this.userDefinedLocation = null;
637         this.orientation = params.orientation || [ 0, 0 ];
638
639         jsPlumbUtil.EventGenerator.apply(this);
640         
641         var jsPlumbInstance = params.jsPlumbInstance;//,
642             //lastTimestamp = null;//, lastReturnValue = null;
643         
644         this.lastReturnValue = null;
645         this.offsets = params.offsets || [ 0, 0 ];
646         this.timestamp = null;        
647         this.compute = function(params) {
648             
649             var xy = params.xy, wh = params.wh, element = params.element, timestamp = params.timestamp; 
650
651             if(params.clearUserDefinedLocation)
652                 this.userDefinedLocation = null;
653             
654             if (timestamp && timestamp === self.timestamp)
655                 return this.lastReturnValue;        
656             
657             if (this.userDefinedLocation != null) {
658                 this.lastReturnValue = this.userDefinedLocation;
659             }
660             else {                
661                 
662                 this.lastReturnValue = [ xy[0] + (this.x * wh[0]) + this.offsets[0], xy[1] + (this.y * wh[1]) + this.offsets[1] ];                    
663                 // adjust loc if there is an offsetParent
664                 this.lastReturnValue = jsPlumbInstance.adjustForParentOffsetAndScroll(this.lastReturnValue, element.canvas);
665             }
666             
667             this.timestamp = timestamp;
668             return this.lastReturnValue;
669         };
670
671         this.getCurrentLocation = function(params) { 
672             return (this.lastReturnValue == null || (params.timestamp != null && this.timestamp != params.timestamp)) ? this.compute(params) : this.lastReturnValue; 
673         };
674     };
675     jsPlumbUtil.extend(jsPlumb.Anchor, jsPlumbUtil.EventGenerator, {
676         equals : function(anchor) {
677             if (!anchor) return false;
678             var ao = anchor.getOrientation(),
679                 o = this.getOrientation();
680             return this.x == anchor.x && this.y == anchor.y && this.offsets[0] == anchor.offsets[0] && this.offsets[1] == anchor.offsets[1] && o[0] == ao[0] && o[1] == ao[1];
681         },
682         getUserDefinedLocation : function() { 
683             return this.userDefinedLocation;
684         },        
685         setUserDefinedLocation : function(l) {
686             this.userDefinedLocation = l;
687         },
688         clearUserDefinedLocation : function() {
689             this.userDefinedLocation = null;
690         },
691         getOrientation : function(_endpoint) { return this.orientation; },
692         getCssClass : function() { return this.cssClass; }
693     });
694
695     /**
696      * An Anchor that floats. its orientation is computed dynamically from
697      * its position relative to the anchor it is floating relative to.  It is used when creating 
698      * a connection through drag and drop.
699      * 
700      * TODO FloatingAnchor could totally be refactored to extend Anchor just slightly.
701      */
702     jsPlumb.FloatingAnchor = function(params) {
703         
704         jsPlumb.Anchor.apply(this, arguments);
705
706         // this is the anchor that this floating anchor is referenced to for
707         // purposes of calculating the orientation.
708         var ref = params.reference,
709             jpcl = jsPlumb.CurrentLibrary,
710             jsPlumbInstance = params.jsPlumbInstance,
711             // the canvas this refers to.
712             refCanvas = params.referenceCanvas,
713             size = jpcl.getSize(jpcl.getElementObject(refCanvas)),
714             // these are used to store the current relative position of our
715             // anchor wrt the reference anchor. they only indicate
716             // direction, so have a value of 1 or -1 (or, very rarely, 0). these
717             // values are written by the compute method, and read
718             // by the getOrientation method.
719             xDir = 0, yDir = 0,
720             // temporary member used to store an orientation when the floating
721             // anchor is hovering over another anchor.
722             orientation = null,
723             _lastResult = null;
724
725         // clear from parent. we want floating anchor orientation to always be computed.
726         this.orientation = null;
727
728         // set these to 0 each; they are used by certain types of connectors in the loopback case,
729         // when the connector is trying to clear the element it is on. but for floating anchor it's not
730         // very important.
731         this.x = 0; this.y = 0;
732
733         this.isFloating = true;
734
735         this.compute = function(params) {
736             var xy = params.xy, element = params.element,
737             result = [ xy[0] + (size[0] / 2), xy[1] + (size[1] / 2) ]; // return origin of the element. we may wish to improve this so that any object can be the drag proxy.
738                         
739             // adjust loc if there is an offsetParent
740             result = jsPlumbInstance.adjustForParentOffsetAndScroll(result, element.canvas);
741             
742             _lastResult = result;
743             return result;
744         };
745
746         this.getOrientation = function(_endpoint) {
747             if (orientation) return orientation;
748             else {
749                 var o = ref.getOrientation(_endpoint);
750                 // here we take into account the orientation of the other
751                 // anchor: if it declares zero for some direction, we declare zero too. this might not be the most awesome. perhaps we can come
752                 // up with a better way. it's just so that the line we draw looks like it makes sense. maybe this wont make sense.
753                 return [ Math.abs(o[0]) * xDir * -1,
754                         Math.abs(o[1]) * yDir * -1 ];
755             }
756         };
757
758         /**
759          * notification the endpoint associated with this anchor is hovering
760          * over another anchor; we want to assume that anchor's orientation
761          * for the duration of the hover.
762          */
763         this.over = function(anchor, endpoint) { 
764             orientation = anchor.getOrientation(endpoint); 
765         };
766
767         /**
768          * notification the endpoint associated with this anchor is no
769          * longer hovering over another anchor; we should resume calculating
770          * orientation as we normally do.
771          */
772         this.out = function() { orientation = null; };
773
774         this.getCurrentLocation = function(params) { return _lastResult == null ? this.compute(params) : _lastResult; };
775     };
776     jsPlumbUtil.extend(jsPlumb.FloatingAnchor, jsPlumb.Anchor);
777
778     var _convertAnchor = function(anchor, jsPlumbInstance, elementId) { 
779         return anchor.constructor == jsPlumb.Anchor ? anchor: jsPlumbInstance.makeAnchor(anchor, elementId, jsPlumbInstance); 
780     };
781
782     /* 
783      * A DynamicAnchor is an Anchor that contains a list of other Anchors, which it cycles
784      * through at compute time to find the one that is located closest to
785      * the center of the target element, and returns that Anchor's compute
786      * method result. this causes endpoints to follow each other with
787      * respect to the orientation of their target elements, which is a useful
788      * feature for some applications.
789      * 
790      */
791     jsPlumb.DynamicAnchor = function(params) {
792         jsPlumb.Anchor.apply(this, arguments);
793         
794         this.isSelective = true;
795         this.isDynamic = true;                  
796         this.anchors = [];
797         this.elementId = params.elementId;
798         this.jsPlumbInstance = params.jsPlumbInstance;
799
800         for (var i = 0; i < params.anchors.length; i++) 
801             this.anchors[i] = _convertAnchor(params.anchors[i], this.jsPlumbInstance, this.elementId);                  
802         this.addAnchor = function(anchor) { this.anchors.push(_convertAnchor(anchor, this.jsPlumbInstance, this.elementId)); };
803         this.getAnchors = function() { return this.anchors; };
804         this.locked = false;
805         var _curAnchor = this.anchors.length > 0 ? this.anchors[0] : null,
806             _curIndex = this.anchors.length > 0 ? 0 : -1,
807             _lastAnchor = _curAnchor,
808             self = this,
809         
810             // helper method to calculate the distance between the centers of the two elements.
811             _distance = function(anchor, cx, cy, xy, wh) {
812                 var ax = xy[0] + (anchor.x * wh[0]), ay = xy[1] + (anchor.y * wh[1]),                           
813                     acx = xy[0] + (wh[0] / 2), acy = xy[1] + (wh[1] / 2);
814                 return (Math.sqrt(Math.pow(cx - ax, 2) + Math.pow(cy - ay, 2)) +
815                         Math.sqrt(Math.pow(acx - ax, 2) + Math.pow(acy - ay, 2)));
816             },        
817             // default method uses distance between element centers.  you can provide your own method in the dynamic anchor
818             // constructor (and also to jsPlumb.makeDynamicAnchor). the arguments to it are four arrays: 
819             // xy - xy loc of the anchor's element
820             // wh - anchor's element's dimensions
821             // txy - xy loc of the element of the other anchor in the connection
822             // twh - dimensions of the element of the other anchor in the connection.
823             // anchors - the list of selectable anchors
824             _anchorSelector = params.selector || function(xy, wh, txy, twh, anchors) {
825                 var cx = txy[0] + (twh[0] / 2), cy = txy[1] + (twh[1] / 2);
826                 var minIdx = -1, minDist = Infinity;
827                 for ( var i = 0; i < anchors.length; i++) {
828                     var d = _distance(anchors[i], cx, cy, xy, wh);
829                     if (d < minDist) {
830                         minIdx = i + 0;
831                         minDist = d;
832                     }
833                 }
834                 return anchors[minIdx];
835             };
836         
837         this.compute = function(params) {                               
838             var xy = params.xy, wh = params.wh, timestamp = params.timestamp, txy = params.txy, twh = params.twh;                               
839             
840             if(params.clearUserDefinedLocation)
841                 userDefinedLocation = null;
842
843             this.timestamp = timestamp;            
844             
845             var udl = self.getUserDefinedLocation();
846             if (udl != null) {
847                 return udl;
848             }
849             
850             // if anchor is locked or an opposite element was not given, we
851             // maintain our state. anchor will be locked
852             // if it is the source of a drag and drop.
853             if (this.locked || txy == null || twh == null)
854                 return _curAnchor.compute(params);                              
855             else
856                 params.timestamp = null; // otherwise clear this, i think. we want the anchor to compute.
857             
858             _curAnchor = _anchorSelector(xy, wh, txy, twh, this.anchors);
859             this.x = _curAnchor.x;
860             this.y = _curAnchor.y;        
861
862             if (_curAnchor != _lastAnchor)
863                 this.fire("anchorChanged", _curAnchor);
864
865             _lastAnchor = _curAnchor;
866             
867             return _curAnchor.compute(params);
868         };
869
870         this.getCurrentLocation = function(params) {
871             return this.getUserDefinedLocation() || (_curAnchor != null ? _curAnchor.getCurrentLocation(params) : null);
872         };
873
874         this.getOrientation = function(_endpoint) { return _curAnchor != null ? _curAnchor.getOrientation(_endpoint) : [ 0, 0 ]; };
875         this.over = function(anchor, endpoint) { if (_curAnchor != null) _curAnchor.over(anchor, endpoint); };
876         this.out = function() { if (_curAnchor != null) _curAnchor.out(); };
877
878         this.getCssClass = function() { return (_curAnchor && _curAnchor.getCssClass()) || ""; };
879     };    
880     jsPlumbUtil.extend(jsPlumb.DynamicAnchor, jsPlumb.Anchor);        
881     
882 // -------- basic anchors ------------------    
883     var _curryAnchor = function(x, y, ox, oy, type, fnInit) {
884         jsPlumb.Anchors[type] = function(params) {
885             var a = params.jsPlumbInstance.makeAnchor([ x, y, ox, oy, 0, 0 ], params.elementId, params.jsPlumbInstance);
886             a.type = type;
887             if (fnInit) fnInit(a, params);
888             return a;
889         };
890     };
891         
892         _curryAnchor(0.5, 0, 0,-1, "TopCenter");
893     _curryAnchor(0.5, 1, 0, 1, "BottomCenter");
894     _curryAnchor(0, 0.5, -1, 0, "LeftMiddle");
895     _curryAnchor(1, 0.5, 1, 0, "RightMiddle");
896     // from 1.4.2: Top, Right, Bottom, Left
897     _curryAnchor(0.5, 0, 0,-1, "Top");
898     _curryAnchor(0.5, 1, 0, 1, "Bottom");
899     _curryAnchor(0, 0.5, -1, 0, "Left");
900     _curryAnchor(1, 0.5, 1, 0, "Right");
901     _curryAnchor(0.5, 0.5, 0, 0, "Center");
902     _curryAnchor(1, 0, 0,-1, "TopRight");
903     _curryAnchor(1, 1, 0, 1, "BottomRight");
904     _curryAnchor(0, 0, 0, -1, "TopLeft");
905     _curryAnchor(0, 1, 0, 1, "BottomLeft");
906     
907 // ------- dynamic anchors -------------------    
908                         
909     // default dynamic anchors chooses from Top, Right, Bottom, Left
910         jsPlumb.Defaults.DynamicAnchors = function(params) {
911                 return params.jsPlumbInstance.makeAnchors(["TopCenter", "RightMiddle", "BottomCenter", "LeftMiddle"], params.elementId, params.jsPlumbInstance);
912         };
913     
914     // default dynamic anchors bound to name 'AutoDefault'
915         jsPlumb.Anchors.AutoDefault  = function(params) { 
916                 var a = params.jsPlumbInstance.makeDynamicAnchor(jsPlumb.Defaults.DynamicAnchors(params));
917                 a.type = "AutoDefault";
918                 return a;
919         };      
920     
921 // ------- continuous anchors -------------------    
922     
923     var _curryContinuousAnchor = function(type, faces) {
924         jsPlumb.Anchors[type] = function(params) {
925             var a = params.jsPlumbInstance.makeAnchor(["Continuous", { faces:faces }], params.elementId, params.jsPlumbInstance);
926             a.type = type;
927             return a;
928         };
929     };
930     
931     jsPlumb.Anchors.Continuous = function(params) {
932                 return params.jsPlumbInstance.continuousAnchorFactory.get(params);
933         };
934                 
935     _curryContinuousAnchor("ContinuousLeft", ["left"]);    
936     _curryContinuousAnchor("ContinuousTop", ["top"]);                 
937     _curryContinuousAnchor("ContinuousBottom", ["bottom"]);                 
938     _curryContinuousAnchor("ContinuousRight", ["right"]); 
939     
940 // ------- position assign anchors -------------------    
941     
942     // this anchor type lets you assign the position at connection time.
943         _curryAnchor(0, 0, 0, 0, "Assign", function(anchor, params) {
944                 // find what to use as the "position finder". the user may have supplied a String which represents
945                 // the id of a position finder in jsPlumb.AnchorPositionFinders, or the user may have supplied the
946                 // position finder as a function.  we find out what to use and then set it on the anchor.
947                 var pf = params.position || "Fixed";
948                 anchor.positionFinder = pf.constructor == String ? params.jsPlumbInstance.AnchorPositionFinders[pf] : pf;
949                 // always set the constructor params; the position finder might need them later (the Grid one does,
950                 // for example)
951                 anchor.constructorParams = params;
952         });     
953
954     // these are the default anchor positions finders, which are used by the makeTarget function.  supplying
955     // a position finder argument to that function allows you to specify where the resulting anchor will
956     // be located
957         jsPlumbInstance.prototype.AnchorPositionFinders = {
958                 "Fixed": function(dp, ep, es, params) {
959                         return [ (dp.left - ep.left) / es[0], (dp.top - ep.top) / es[1] ];      
960                 },
961                 "Grid":function(dp, ep, es, params) {
962                         var dx = dp.left - ep.left, dy = dp.top - ep.top,
963                                 gx = es[0] / (params.grid[0]), gy = es[1] / (params.grid[1]),
964                                 mx = Math.floor(dx / gx), my = Math.floor(dy / gy);
965                         return [ ((mx * gx) + (gx / 2)) / es[0], ((my * gy) + (gy / 2)) / es[1] ];
966                 }
967         };
968     
969 // ------- perimeter anchors -------------------    
970                 
971         jsPlumb.Anchors.Perimeter = function(params) {
972                 params = params || {};
973                 var anchorCount = params.anchorCount || 60,
974                         shape = params.shape;
975                 
976                 if (!shape) throw new Error("no shape supplied to Perimeter Anchor type");              
977                 
978                 var _circle = function() {
979                 var r = 0.5, step = Math.PI * 2 / anchorCount, current = 0, a = [];
980                 for (var i = 0; i < anchorCount; i++) {
981                     var x = r + (r * Math.sin(current)),
982                         y = r + (r * Math.cos(current));                                
983                     a.push( [ x, y, 0, 0 ] );
984                     current += step;
985                 }
986                 return a;       
987             },
988             _path = function(segments) {
989                 var anchorsPerFace = anchorCount / segments.length, a = [],
990                     _computeFace = function(x1, y1, x2, y2, fractionalLength) {
991                         anchorsPerFace = anchorCount * fractionalLength;
992                         var dx = (x2 - x1) / anchorsPerFace, dy = (y2 - y1) / anchorsPerFace;
993                         for (var i = 0; i < anchorsPerFace; i++) {
994                             a.push( [
995                                 x1 + (dx * i),
996                                 y1 + (dy * i),
997                                 0,
998                                 0
999                             ]);
1000                         }
1001                     };
1002                                                                 
1003                 for (var i = 0; i < segments.length; i++)
1004                     _computeFace.apply(null, segments[i]);
1005                                                                                                                 
1006                 return a;                                       
1007             },
1008                         _shape = function(faces) {                                                                                              
1009                 var s = [];
1010                 for (var i = 0; i < faces.length; i++) {
1011                     s.push([faces[i][0], faces[i][1], faces[i][2], faces[i][3], 1 / faces.length]);
1012                 }
1013                 return _path(s);
1014                         },
1015                         _rectangle = function() {
1016                                 return _shape([
1017                                         [ 0, 0, 1, 0 ], [ 1, 0, 1, 1 ], [ 1, 1, 0, 1 ], [ 0, 1, 0, 0 ]
1018                                 ]);             
1019                         };
1020                 
1021                 var _shapes = {
1022                         "Circle":_circle,
1023                         "Ellipse":_circle,
1024                         "Diamond":function() {
1025                                 return _shape([
1026                                                 [ 0.5, 0, 1, 0.5 ], [ 1, 0.5, 0.5, 1 ], [ 0.5, 1, 0, 0.5 ], [ 0, 0.5, 0.5, 0 ]
1027                                 ]);
1028                         },
1029                         "Rectangle":_rectangle,
1030                         "Square":_rectangle,
1031                         "Triangle":function() {
1032                                 return _shape([
1033                                                 [ 0.5, 0, 1, 1 ], [ 1, 1, 0, 1 ], [ 0, 1, 0.5, 0]
1034                                 ]);     
1035                         },
1036                         "Path":function(params) {
1037                 var points = params.points, p = [], tl = 0;
1038                                 for (var i = 0; i < points.length - 1; i++) {
1039                     var l = Math.sqrt(Math.pow(points[i][2] - points[i][0]) + Math.pow(points[i][3] - points[i][1]));
1040                     tl += l;
1041                                         p.push([points[i][0], points[i][1], points[i+1][0], points[i+1][1], l]);                                                
1042                                 }
1043                 for (var j = 0; j < p.length; j++) {
1044                     p[j][4] = p[j][4] / tl;
1045                 }
1046                                 return _path(p);
1047                         }
1048                 },
1049         _rotate = function(points, amountInDegrees) {
1050             var o = [], theta = amountInDegrees / 180 * Math.PI ;
1051             for (var i = 0; i < points.length; i++) {
1052                 var _x = points[i][0] - 0.5,
1053                     _y = points[i][1] - 0.5;
1054                     
1055                 o.push([
1056                     0.5 + ((_x * Math.cos(theta)) - (_y * Math.sin(theta))),
1057                     0.5 + ((_x * Math.sin(theta)) + (_y * Math.cos(theta))),
1058                     points[i][2],
1059                     points[i][3]
1060                 ]);
1061             }
1062             return o;
1063         };
1064                 
1065                 if (!_shapes[shape]) throw new Error("Shape [" + shape + "] is unknown by Perimeter Anchor type");
1066                 
1067                 var da = _shapes[shape](params);
1068         if (params.rotation) da = _rotate(da, params.rotation);
1069         var a = params.jsPlumbInstance.makeDynamicAnchor(da);
1070                 a.type = "Perimeter";
1071                 return a;
1072         };
1073 })();