reservation plugin - unbound request (unclean
[unfold.git] / portal / static / unbound_reservation_static / src / renderers-svg.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 SVG renderers.
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 /**
21  * SVG support for jsPlumb.
22  * 
23  * things to investigate:
24  * 
25  * gradients:  https://developer.mozilla.org/en/svg_in_html_introduction
26  * css:http://tutorials.jenkov.com/svg/svg-and-css.html
27  * text on a path: http://www.w3.org/TR/SVG/text.html#TextOnAPath
28  * pointer events: https://developer.mozilla.org/en/css/pointer-events
29  *
30  * IE9 hover jquery: http://forum.jquery.com/topic/1-6-2-broke-svg-hover-events
31  *
32  */
33 ;(function() {
34         
35 // ************************** SVG utility methods ********************************************  
36         
37         var svgAttributeMap = {
38                 "joinstyle":"stroke-linejoin",
39                 "stroke-linejoin":"stroke-linejoin",            
40                 "stroke-dashoffset":"stroke-dashoffset",
41                 "stroke-linecap":"stroke-linecap"
42         },
43         STROKE_DASHARRAY = "stroke-dasharray",
44         DASHSTYLE = "dashstyle",
45         LINEAR_GRADIENT = "linearGradient",
46         RADIAL_GRADIENT = "radialGradient",
47         FILL = "fill",
48         STOP = "stop",
49         STROKE = "stroke",
50         STROKE_WIDTH = "stroke-width",
51         STYLE = "style",
52         NONE = "none",
53         JSPLUMB_GRADIENT = "jsplumb_gradient_",
54         LINE_WIDTH = "lineWidth",
55         ns = {
56                 svg:"http://www.w3.org/2000/svg",
57                 xhtml:"http://www.w3.org/1999/xhtml"
58         },
59         _attr = function(node, attributes) {
60                 for (var i in attributes)
61                         node.setAttribute(i, "" + attributes[i]);
62         },      
63         _node = function(name, attributes) {
64                 var n = document.createElementNS(ns.svg, name);
65                 attributes = attributes || {};
66                 attributes.version = "1.1";
67                 attributes.xmlns = ns.xhtml;
68                 _attr(n, attributes);
69                 return n;
70         },
71         _pos = function(d) { return "position:absolute;left:" + d[0] + "px;top:" + d[1] + "px"; },      
72         _clearGradient = function(parent) {
73                 for (var i = 0; i < parent.childNodes.length; i++) {
74                         if (parent.childNodes[i].tagName == LINEAR_GRADIENT || parent.childNodes[i].tagName == RADIAL_GRADIENT)
75                                 parent.removeChild(parent.childNodes[i]);
76                 }
77         },              
78         _updateGradient = function(parent, node, style, dimensions, uiComponent) {
79                 var id = JSPLUMB_GRADIENT + uiComponent._jsPlumb.instance.idstamp();
80                 // first clear out any existing gradient
81                 _clearGradient(parent);
82                 // this checks for an 'offset' property in the gradient, and in the absence of it, assumes
83                 // we want a linear gradient. if it's there, we create a radial gradient.
84                 // it is possible that a more explicit means of defining the gradient type would be
85                 // better. relying on 'offset' means that we can never have a radial gradient that uses
86                 // some default offset, for instance.
87                 // issue 244 suggested the 'gradientUnits' attribute; without this, straight/flowchart connectors with gradients would
88                 // not show gradients when the line was perfectly horizontal or vertical.
89                 var g;
90                 if (!style.gradient.offset) {
91                         g = _node(LINEAR_GRADIENT, {id:id, gradientUnits:"userSpaceOnUse"});
92                 }
93                 else {
94                         g = _node(RADIAL_GRADIENT, {
95                                 id:id
96                         });                     
97                 }
98                 
99                 parent.appendChild(g);
100                 
101                 // the svg radial gradient seems to treat stops in the reverse 
102                 // order to how canvas does it.  so we want to keep all the maths the same, but
103                 // iterate the actual style declarations in reverse order, if the x indexes are not in order.
104                 for (var i = 0; i < style.gradient.stops.length; i++) {
105                         var styleToUse = uiComponent.segment == 1 ||  uiComponent.segment == 2 ? i: style.gradient.stops.length - 1 - i,                        
106                                 stopColor = jsPlumbUtil.convertStyle(style.gradient.stops[styleToUse][1], true),
107                                 s = _node(STOP, {"offset":Math.floor(style.gradient.stops[i][0] * 100) + "%", "stop-color":stopColor});
108
109                         g.appendChild(s);
110                 }
111                 var applyGradientTo = style.strokeStyle ? STROKE : FILL;
112         //document.location.toString()
113                 //node.setAttribute(STYLE, applyGradientTo + ":url(#" + id + ")");
114         node.setAttribute(STYLE, applyGradientTo + ":url(" + document.location.toString() + "#" + id + ")");
115         },
116         _applyStyles = function(parent, node, style, dimensions, uiComponent) {
117                 
118                 if (style.gradient) {
119                         _updateGradient(parent, node, style, dimensions, uiComponent);                  
120                 }
121                 else {
122                         // make sure we clear any existing gradient
123                         _clearGradient(parent);
124                         node.setAttribute(STYLE, "");
125                 }
126                 
127                 node.setAttribute(FILL, style.fillStyle ? jsPlumbUtil.convertStyle(style.fillStyle, true) : NONE);
128                 node.setAttribute(STROKE, style.strokeStyle ? jsPlumbUtil.convertStyle(style.strokeStyle, true) : NONE);                
129                 if (style.lineWidth) {
130                         node.setAttribute(STROKE_WIDTH, style.lineWidth);
131                 }
132         
133                 // in SVG there is a stroke-dasharray attribute we can set, and its syntax looks like
134                 // the syntax in VML but is actually kind of nasty: values are given in the pixel
135                 // coordinate space, whereas in VML they are multiples of the width of the stroked
136                 // line, which makes a lot more sense.  for that reason, jsPlumb is supporting both
137                 // the native svg 'stroke-dasharray' attribute, and also the 'dashstyle' concept from
138                 // VML, which will be the preferred method.  the code below this converts a dashstyle
139                 // attribute given in terms of stroke width into a pixel representation, by using the
140                 // stroke's lineWidth. 
141                 if (style[DASHSTYLE] && style[LINE_WIDTH] && !style[STROKE_DASHARRAY]) {
142                         var sep = style[DASHSTYLE].indexOf(",") == -1 ? " " : ",",
143                         parts = style[DASHSTYLE].split(sep),
144                         styleToUse = "";
145                         parts.forEach(function(p) {
146                                 styleToUse += (Math.floor(p * style.lineWidth) + sep);
147                         });
148                         node.setAttribute(STROKE_DASHARRAY, styleToUse);
149                 }               
150                 else if(style[STROKE_DASHARRAY]) {
151                         node.setAttribute(STROKE_DASHARRAY, style[STROKE_DASHARRAY]);
152                 }
153                 
154                 // extra attributes such as join type, dash offset.
155                 for (var i in svgAttributeMap) {
156                         if (style[i]) {
157                                 node.setAttribute(svgAttributeMap[i], style[i]);
158                         }
159                 }
160         },
161         _decodeFont = function(f) {
162                 var r = /([0-9].)(p[xt])\s(.*)/, 
163                         bits = f.match(r);
164
165                 return {size:bits[1] + bits[2], font:bits[3]};          
166         },
167         _classManip = function(el, add, clazz) {
168                 var classesToAddOrRemove = clazz.split(" "),
169                         className = el.className,
170                         curClasses = className.baseVal.split(" ");
171                         
172                 for (var i = 0; i < classesToAddOrRemove.length; i++) {
173                         if (add) {
174                                 if (curClasses.indexOf(classesToAddOrRemove[i]) == -1)
175                                         curClasses.push(classesToAddOrRemove[i]);
176                         }
177                         else {
178                                 var idx = curClasses.indexOf(classesToAddOrRemove[i]);
179                                 if (idx != -1)
180                                         curClasses.splice(idx, 1);
181                         }
182                 }
183                 
184                 el.className.baseVal = curClasses.join(" ");
185         },
186         _addClass = function(el, clazz) { _classManip(el, true, clazz); },
187         _removeClass = function(el, clazz) { _classManip(el, false, clazz); },
188         _appendAtIndex = function(svg, path, idx) {
189                 if (svg.childNodes.length > idx) {
190                         svg.insertBefore(path, svg.childNodes[idx]);
191                 }
192                 else svg.appendChild(path);
193         };
194         
195         /**
196                 utility methods for other objects to use.
197         */
198         jsPlumbUtil.svg = {
199                 addClass:_addClass,
200                 removeClass:_removeClass,
201                 node:_node,
202                 attr:_attr,
203                 pos:_pos
204         };
205         
206  // ************************** / SVG utility methods ********************************************       
207         
208         /*
209          * Base class for SVG components.
210          */     
211         var SvgComponent = function(params) {
212                 var pointerEventsSpec = params.pointerEventsSpec || "all", renderer = {};
213                         
214                 jsPlumb.jsPlumbUIComponent.apply(this, params.originalArgs);
215                 this.canvas = null;this.path = null;this.svg = null; 
216         
217                 var clazz = params.cssClass + " " + (params.originalArgs[0].cssClass || ""),            
218                         svgParams = {
219                                 "style":"",
220                                 "width":0,
221                                 "height":0,
222                                 "pointer-events":pointerEventsSpec,
223                                 "position":"absolute"
224                         };                              
225                 this.svg = _node("svg", svgParams);
226                 if (params.useDivWrapper) {
227                         this.canvas = document.createElement("div");
228                         this.canvas.style.position = "absolute";
229                         jsPlumbUtil.sizeElement(this.canvas,0,0,1,1);
230                         this.canvas.className = clazz;
231                 }
232                 else {
233                         _attr(this.svg, { "class":clazz });
234                         this.canvas = this.svg;
235                 }
236                         
237                 params._jsPlumb.appendElement(this.canvas, params.originalArgs[0].parent);
238                 if (params.useDivWrapper) this.canvas.appendChild(this.svg);
239                 
240                 // TODO this displayElement stuff is common between all components, across all
241                 // renderers.  would be best moved to jsPlumbUIComponent.
242                 var displayElements = [ this.canvas ];
243                 this.getDisplayElements = function() { 
244                         return displayElements; 
245                 };
246                 
247                 this.appendDisplayElement = function(el) {
248                         displayElements.push(el);
249                 };      
250                 
251                 this.paint = function(style, anchor, extents) {                         
252                         if (style != null) {
253                                 
254                                 var xy = [ this.x, this.y ], wh = [ this.w, this.h ], p;
255                                 if (extents != null) {
256                                         if (extents.xmin < 0) xy[0] += extents.xmin;
257                                         if (extents.ymin < 0) xy[1] += extents.ymin;
258                                         wh[0] = extents.xmax + ((extents.xmin < 0) ? -extents.xmin : 0);
259                                         wh[1] = extents.ymax + ((extents.ymin < 0) ? -extents.ymin : 0);
260                                 }
261
262                                 if (params.useDivWrapper) {                                     
263                                         jsPlumbUtil.sizeElement(this.canvas, xy[0], xy[1], wh[0], wh[1]);
264                                         xy[0] = 0; xy[1] = 0;
265                                         p = _pos([ 0, 0 ]);
266                                 }
267                                 else
268                                         p = _pos([ xy[0], xy[1] ]);
269                 
270                 renderer.paint.apply(this, arguments);                                          
271                 
272                         _attr(this.svg, {
273                                 "style":p,
274                                 "width": wh[0],
275                                 "height": wh[1]
276                         });                                                     
277                         }
278             };
279                 
280                 return {
281                         renderer:renderer
282                 };
283         };
284         jsPlumbUtil.extend(SvgComponent, jsPlumb.jsPlumbUIComponent, {
285                 cleanup:function() {
286                         jsPlumbUtil.removeElement(this.canvas);            
287                         this.svg = null;
288                         this.canvas = null;
289                         this.path = null;                       
290                 },
291                 setVisible:function(v) {
292                         if (this.canvas) {
293                                 this.canvas.style.display = v ? "block" : "none";
294                         }
295                         if (this.bgCanvas) {
296                                 this.bgCanvas.style.display = v ? "block" : "none";
297                         }
298                 }
299         });
300         
301         /*
302          * Base class for SVG connectors.
303          */ 
304         var SvgConnector = jsPlumb.ConnectorRenderers.svg = function(params) {
305                 var self = this,
306                         _super = SvgComponent.apply(this, [ { 
307                                 cssClass:params._jsPlumb.connectorClass, 
308                                 originalArgs:arguments, 
309                                 pointerEventsSpec:"none", 
310                                 _jsPlumb:params._jsPlumb
311                         } ]);   
312
313                 /*this.pointOnPath = function(location, absolute) {
314                         if (!self.path) return [0,0];
315                         var p = absolute ? location : location * self.path.getTotalLength();
316                         return self.path.getPointAtLength(p);
317                 };*/                    
318
319                 _super.renderer.paint = function(style, anchor, extents) {
320                         
321                         var segments = self.getSegments(), p = "", offset = [0,0];                      
322                         if (extents.xmin < 0) offset[0] = -extents.xmin;
323                         if (extents.ymin < 0) offset[1] = -extents.ymin;                        
324                         
325                         // create path from segments.   
326                         for (var i = 0; i < segments.length; i++) {
327                                 p += jsPlumb.Segments.svg.SegmentRenderer.getPath(segments[i]);
328                                 p += " ";
329                         }                       
330                         
331                         var a = { 
332                                         d:p,
333                                         transform:"translate(" + offset[0] + "," + offset[1] + ")",
334                                         "pointer-events":params["pointer-events"] || "visibleStroke"
335                                 }, 
336                 outlineStyle = null,
337                 d = [self.x,self.y,self.w,self.h];                                              
338                         
339                         // outline style.  actually means drawing an svg object underneath the main one.
340                         if (style.outlineColor) {
341                                 var outlineWidth = style.outlineWidth || 1,
342                                         outlineStrokeWidth = style.lineWidth + (2 * outlineWidth);
343                                 outlineStyle = jsPlumb.CurrentLibrary.extend({}, style);
344                                 outlineStyle.strokeStyle = jsPlumbUtil.convertStyle(style.outlineColor);
345                                 outlineStyle.lineWidth = outlineStrokeWidth;
346                                 
347                                 if (self.bgPath == null) {
348                                         self.bgPath = _node("path", a);
349                                 _appendAtIndex(self.svg, self.bgPath, 0);
350                                 self.attachListeners(self.bgPath, self);
351                                 }
352                                 else {
353                                         _attr(self.bgPath, a);
354                                 }
355                                 
356                                 _applyStyles(self.svg, self.bgPath, outlineStyle, d, self);
357                         }                       
358                         
359                 if (self.path == null) {
360                         self.path = _node("path", a);
361                         _appendAtIndex(self.svg, self.path, style.outlineColor ? 1 : 0);
362                         self.attachListeners(self.path, self);                                  
363                 }
364                 else {
365                         _attr(self.path, a);
366                 }
367                                 
368                 _applyStyles(self.svg, self.path, style, d, self);
369                 };
370                 
371                 this.reattachListeners = function() {
372                         if (this.bgPath) this.reattachListenersForElement(this.bgPath, this);
373                         if (this.path) this.reattachListenersForElement(this.path, this);
374                 };
375         };
376         jsPlumbUtil.extend(jsPlumb.ConnectorRenderers.svg, SvgComponent);
377
378 // ******************************* svg segment renderer *****************************************************   
379                 
380         jsPlumb.Segments.svg = {
381                 SegmentRenderer : {             
382                         getPath : function(segment) {
383                                 return ({
384                                         "Straight":function() {
385                                                 var d = segment.getCoordinates();
386                                                 return "M " + d.x1 + " " + d.y1 + " L " + d.x2 + " " + d.y2;    
387                                         },
388                                         "Bezier":function() {
389                                                 var d = segment.params;
390                                                 return "M " + d.x1 + " " + d.y1 + 
391                                                         " C " + d.cp1x + " " + d.cp1y + " " + d.cp2x + " " + d.cp2y + " " + d.x2 + " " + d.y2;                  
392                                         },
393                                         "Arc":function() {
394                                                 var d = segment.params,
395                                                         laf = segment.sweep > Math.PI ? 1 : 0,
396                                                         sf = segment.anticlockwise ? 0 : 1;                     
397
398                                                 return "M" + segment.x1 + " " + segment.y1 + " A " + segment.radius + " " + d.r + " 0 " + laf + "," + sf + " " + segment.x2 + " " + segment.y2;
399                                         }
400                                 })[segment.type]();     
401                         }
402                 }
403         };
404         
405 // ******************************* /svg segments *****************************************************
406    
407     /*
408          * Base class for SVG endpoints.
409          */
410         var SvgEndpoint = window.SvgEndpoint = function(params) {
411                 var _super = SvgComponent.apply(this, [ {
412                                 cssClass:params._jsPlumb.endpointClass, 
413                                 originalArgs:arguments, 
414                                 pointerEventsSpec:"all",
415                                 useDivWrapper:true,
416                                 _jsPlumb:params._jsPlumb
417                         } ]);
418                         
419                 _super.renderer.paint = function(style) {
420                         var s = jsPlumb.extend({}, style);
421                         if (s.outlineColor) {
422                                 s.strokeWidth = s.outlineWidth;
423                                 s.strokeStyle = jsPlumbUtil.convertStyle(s.outlineColor, true);
424                         }
425                         
426                         if (this.node == null) {
427                                 this.node = this.makeNode(s);
428                                 this.svg.appendChild(this.node);
429                                 this.attachListeners(this.node, this);
430                         }
431                         else if (this.updateNode != null) {
432                                 this.updateNode(this.node);
433                         }
434                         _applyStyles(this.svg, this.node, s, [ this.x, this.y, this.w, this.h ], this);
435                         _pos(this.node, [ this.x, this.y ]);
436                 }.bind(this);
437                                 
438         };
439         jsPlumbUtil.extend(SvgEndpoint, SvgComponent, {
440                 reattachListeners : function() {
441                         if (this.node) this.reattachListenersForElement(this.node, this);
442                 }
443         });
444         
445         /*
446          * SVG Dot Endpoint
447          */
448         jsPlumb.Endpoints.svg.Dot = function() {
449                 jsPlumb.Endpoints.Dot.apply(this, arguments);
450                 SvgEndpoint.apply(this, arguments);             
451                 this.makeNode = function(style) { 
452                         return _node("circle", {
453                 "cx"    :       this.w / 2,
454                 "cy"    :       this.h / 2,
455                 "r"             :       this.radius
456             });                 
457                 };
458                 this.updateNode = function(node) {
459                         _attr(node, {
460                                 "cx":this.w / 2,
461                                 "cy":this.h  / 2,
462                                 "r":this.radius
463                         });
464                 };
465         };
466         jsPlumbUtil.extend(jsPlumb.Endpoints.svg.Dot, [jsPlumb.Endpoints.Dot, SvgEndpoint]);
467         
468         /*
469          * SVG Rectangle Endpoint 
470          */
471         jsPlumb.Endpoints.svg.Rectangle = function() {
472                 jsPlumb.Endpoints.Rectangle.apply(this, arguments);
473                 SvgEndpoint.apply(this, arguments);             
474                 this.makeNode = function(style) {
475                         return _node("rect", {
476                                 "width"     :   this.w,
477                                 "height"    :   this.h
478                         });
479                 };
480                 this.updateNode = function(node) {
481                         _attr(node, {
482                                 "width":this.w,
483                                 "height":this.h
484                         });
485                 };                      
486         };              
487         jsPlumbUtil.extend(jsPlumb.Endpoints.svg.Rectangle, [jsPlumb.Endpoints.Rectangle, SvgEndpoint]);
488         
489         /*
490          * SVG Image Endpoint is the default image endpoint.
491          */
492         jsPlumb.Endpoints.svg.Image = jsPlumb.Endpoints.Image;
493         /*
494          * Blank endpoint in svg renderer is the default Blank endpoint.
495          */
496         jsPlumb.Endpoints.svg.Blank = jsPlumb.Endpoints.Blank;  
497         /*
498          * Label overlay in svg renderer is the default Label overlay.
499          */
500         jsPlumb.Overlays.svg.Label = jsPlumb.Overlays.Label;
501         /*
502          * Custom overlay in svg renderer is the default Custom overlay.
503          */
504         jsPlumb.Overlays.svg.Custom = jsPlumb.Overlays.Custom;
505                 
506         var AbstractSvgArrowOverlay = function(superclass, originalArgs) {
507         superclass.apply(this, originalArgs);
508         jsPlumb.jsPlumbUIComponent.apply(this, originalArgs);
509         this.isAppendedAtTopLevel = false;
510         var self = this;
511         this.path = null;
512         this.paint = function(params, containerExtents) {
513                 // only draws on connections, not endpoints.
514                 if (params.component.svg && containerExtents) {
515                         if (this.path == null) {
516                                 this.path = _node("path", {
517                                         "pointer-events":"all"  
518                                 });
519                                 params.component.svg.appendChild(this.path);
520                                 
521                                 this.attachListeners(this.path, params.component);
522                                 this.attachListeners(this.path, this);
523                         }
524                         var clazz = originalArgs && (originalArgs.length == 1) ? (originalArgs[0].cssClass || "") : "",
525                                 offset = [0,0];
526
527                         if (containerExtents.xmin < 0) offset[0] = -containerExtents.xmin;
528                         if (containerExtents.ymin < 0) offset[1] = -containerExtents.ymin;
529                         
530                         _attr(this.path, { 
531                                 "d"                     :       makePath(params.d),
532                                 "class"         :       clazz,
533                                 stroke          :       params.strokeStyle ? params.strokeStyle : null,
534                                 fill            :       params.fillStyle ? params.fillStyle : null,
535                                 transform       :       "translate(" + offset[0] + "," + offset[1] + ")"
536                         });             
537                 }
538         };
539         var makePath = function(d) {
540                 return "M" + d.hxy.x + "," + d.hxy.y +
541                                 " L" + d.tail[0].x + "," + d.tail[0].y + 
542                                 " L" + d.cxy.x + "," + d.cxy.y + 
543                                 " L" + d.tail[1].x + "," + d.tail[1].y + 
544                                 " L" + d.hxy.x + "," + d.hxy.y;
545         };
546         this.reattachListeners = function() {
547                         if (this.path) this.reattachListenersForElement(this.path, this);
548                 };              
549     };
550     jsPlumbUtil.extend(AbstractSvgArrowOverlay, [jsPlumb.jsPlumbUIComponent, jsPlumb.Overlays.AbstractOverlay], {
551         cleanup : function() {
552                 if (this.path != null) jsPlumb.CurrentLibrary.removeElement(this.path);
553         },
554         setVisible:function(v) {
555                 if(this.path != null) (this.path.style.display = (v ? "block" : "none"));
556         }
557     });
558     
559     jsPlumb.Overlays.svg.Arrow = function() {
560         AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]);       
561     };
562     jsPlumbUtil.extend(jsPlumb.Overlays.svg.Arrow, [ jsPlumb.Overlays.Arrow, AbstractSvgArrowOverlay ]);
563     
564     jsPlumb.Overlays.svg.PlainArrow = function() {
565         AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]);          
566     };
567     jsPlumbUtil.extend(jsPlumb.Overlays.svg.PlainArrow, [ jsPlumb.Overlays.PlainArrow, AbstractSvgArrowOverlay ]);
568     
569     jsPlumb.Overlays.svg.Diamond = function() {
570         AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]);     
571     };
572     jsPlumbUtil.extend(jsPlumb.Overlays.svg.Diamond, [ jsPlumb.Overlays.Diamond, AbstractSvgArrowOverlay ]);
573
574     // a test
575     jsPlumb.Overlays.svg.GuideLines = function() {
576         var path = null, self = this, p1_1, p1_2;        
577         jsPlumb.Overlays.GuideLines.apply(this, arguments);
578         this.paint = function(params, containerExtents) {
579                 if (path == null) {
580                         path = _node("path");
581                         params.connector.svg.appendChild(path);
582                         self.attachListeners(path, params.connector);
583                         self.attachListeners(path, self);
584
585                 p1_1 = _node("path");
586                         params.connector.svg.appendChild(p1_1);
587                         self.attachListeners(p1_1, params.connector);
588                         self.attachListeners(p1_1, self);
589
590                 p1_2 = _node("path");
591                         params.connector.svg.appendChild(p1_2);
592                         self.attachListeners(p1_2, params.connector);
593                         self.attachListeners(p1_2, self);
594                 }
595
596                 var offset =[0,0];
597                 if (containerExtents.xmin < 0) offset[0] = -containerExtents.xmin;
598                 if (containerExtents.ymin < 0) offset[1] = -containerExtents.ymin;
599
600                 _attr(path, {
601                         "d"             :       makePath(params.head, params.tail),
602                         stroke  :       "red",
603                         fill    :       null,
604                         transform:"translate(" + offset[0] + "," + offset[1] + ")"
605                 });
606
607             _attr(p1_1, {
608                         "d"             :       makePath(params.tailLine[0], params.tailLine[1]),
609                         stroke  :       "blue",
610                         fill    :       null,
611                         transform:"translate(" + offset[0] + "," + offset[1] + ")"
612                 });
613
614             _attr(p1_2, {
615                         "d"             :       makePath(params.headLine[0], params.headLine[1]),
616                         stroke  :       "green",
617                         fill    :       null,
618                         transform:"translate(" + offset[0] + "," + offset[1] + ")"
619                 });
620         };
621
622         var makePath = function(d1, d2) {
623             return "M " + d1.x + "," + d1.y +
624                    " L" + d2.x + "," + d2.y;
625         };        
626     };
627     jsPlumbUtil.extend(jsPlumb.Overlays.svg.GuideLines, jsPlumb.Overlays.GuideLines);
628 })();