reservation plugin - unbound request (unclean
[unfold.git] / portal / static / unbound_reservation_static / src / renderers-canvas.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 HTML5 canvas renderers.  Support for canvas was dropped in 1.4.2.
10  * This is being kept around because canvas might make a comeback as a single-page solution
11  * that also supports node rendering.
12  *
13  * Copyright (c) 2010 - 2013 Simon Porritt (http://jsplumb.org)
14  * 
15  * http://jsplumb.org
16  * http://github.com/sporritt/jsplumb
17  * http://code.google.com/p/jsplumb
18  * 
19  * Dual licensed under the MIT and GPL2 licenses.
20  */
21
22 ;(function() {
23
24         
25 // ********************************* CANVAS RENDERERS FOR CONNECTORS AND ENDPOINTS *******************************************************************
26                 
27         // TODO refactor to renderer common script.  put a ref to jsPlumb.sizeCanvas in there too.
28         var _connectionBeingDragged = null,
29             _hasClass = function(el, clazz) { return jsPlumb.CurrentLibrary.hasClass(_getElementObject(el), clazz); },
30             _getElementObject = function(el) { return jsPlumb.CurrentLibrary.getElementObject(el); },
31             _getOffset = function(el) { return jsPlumb.CurrentLibrary.getOffset(_getElementObject(el)); },
32             _pageXY = function(el) { return jsPlumb.CurrentLibrary.getPageXY(el); },
33             _clientXY = function(el) { return jsPlumb.CurrentLibrary.getClientXY(el); };
34         
35         /*
36          * Class:CanvasMouseAdapter
37          * Provides support for mouse events on canvases.  
38          */
39         var CanvasMouseAdapter = window.CanvasMouseAdapter = function() {
40                 var self = this;
41                 this.overlayPlacements = [];
42                 jsPlumb.jsPlumbUIComponent.apply(this, arguments);
43                 jsPlumbUtil.EventGenerator.apply(this, arguments);
44                 /**
45                  * returns whether or not the given event is ojver a painted area of the canvas. 
46                  */
47             this._over = function(e) {                                                          
48                         var o = _getOffset(_getElementObject(self.canvas)),
49                                 pageXY = _pageXY(e),
50                                 x = pageXY[0] - o.left, y = pageXY[1] - o.top;
51                         if (x > 0 && y > 0 && x < self.canvas.width && y < self.canvas.height) {
52                                 // first check overlays
53                                 for ( var i = 0; i < self.overlayPlacements.length; i++) {
54                                         var p = self.overlayPlacements[i];
55                                         if (p && (p[0] <= x && p[1] >= x && p[2] <= y && p[3] >= y))
56                                                 return true;
57                                 }                       
58                                 // then the canvas
59                                 var d = self.canvas.getContext("2d").getImageData(parseInt(x, 10), parseInt(y, 10), 1, 1);
60                                 return d.data[0] !== 0 || d.data[1] !== 0 || d.data[2] !== 0 || d.data[3] !== 0;                  
61                         }
62                         return false;
63             };
64             
65             var _mouseover = false, _mouseDown = false, _posWhenMouseDown = null, _mouseWasDown = false,
66                     _nullSafeHasClass = function(el, clazz) {
67                         return el !== null && _hasClass(el, clazz);
68                     };
69             this.mousemove = function(e) {                  
70                 var pageXY = _pageXY(e), clientXY = _clientXY(e),          
71                 ee = document.elementFromPoint(clientXY[0], clientXY[1]),
72                 eventSourceWasOverlay = _nullSafeHasClass(ee, "_jsPlumb_overlay");              
73                         var _continue = _connectionBeingDragged === null && (_nullSafeHasClass(ee, "_jsPlumb_endpoint") || _nullSafeHasClass(ee, "_jsPlumb_connector"));
74                         if (!_mouseover && _continue && self._over(e)) {
75                                 _mouseover = true;
76                                 self.fire("mouseenter", self, e);               
77                                 return true;
78                         }
79                         // TODO here there is a remote chance that the overlay the mouse moved onto
80                         // is actually not an overlay for the current component. a more thorough check would
81                         // be to ensure the overlay belonged to the current component.  
82                         else if (_mouseover && (!self._over(e) || !_continue) && !eventSourceWasOverlay) {
83                                 _mouseover = false;
84                                 self.fire("mouseexit", self, e);                                
85                         }
86                         self.fire("mousemove", self, e);
87             };
88                                             
89             this.click = function(e) {                  
90                         if (_mouseover && self._over(e) && !_mouseWasDown) 
91                         self.fire("click", self, e);                    
92                 _mouseWasDown = false;
93             };
94             
95             this.dblclick = function(e) {
96                 if (_mouseover && self._over(e) && !_mouseWasDown) 
97                         self.fire("dblclick", self, e);                 
98                 _mouseWasDown = false;
99             };
100             
101             this.mousedown = function(e) {
102                 if(self._over(e) && !_mouseDown) {
103                         _mouseDown = true;                      
104                         _posWhenMouseDown = _getOffset(_getElementObject(self.canvas));
105                         self.fire("mousedown", self, e);
106                 }
107             };
108             
109             this.mouseup = function(e) {
110                 _mouseDown = false;
111                 self.fire("mouseup", self, e);
112             };
113
114         this.contextmenu = function(e) {
115           if (_mouseover && self._over(e) && !_mouseWasDown)
116             self.fire("contextmenu", self, e);
117           _mouseWasDown = false;
118         };
119         };
120         jsPlumbUtil.extend(CanvasMouseAdapter, [ jsPlumb.jsPlumbUIComponent, jsPlumbUtil.EventGenerator ]);             
121         
122         var _newCanvas = function(params) {
123                 var canvas = document.createElement("canvas");
124                 params._jsPlumb.instance.appendElement(canvas, params.parent);
125                 canvas.style.position = "absolute";
126                 if (params["class"]) canvas.className = params["class"];
127                 // set an id. if no id on the element and if uuid was supplied it
128                 // will be used, otherwise we'll create one.
129                 params._jsPlumb.instance.getId(canvas, params.uuid);
130                 if (params.tooltip) canvas.setAttribute("title", params.tooltip);
131
132                 return canvas;
133         };      
134
135         var CanvasComponent = window.CanvasComponent = function(params) {
136                 CanvasMouseAdapter.apply(this, arguments);
137
138                 var displayElements = [ ];
139                 this.getDisplayElements = function() { return displayElements; };
140                 this.appendDisplayElement = function(el) { displayElements.push(el); };
141         };
142         jsPlumbUtil.extend(CanvasComponent, CanvasMouseAdapter, {
143                 setVisible:function(state) {                    
144                         this.canvas.style.display = state ? "block" : "none";
145                 }
146         });
147         
148         var segmentMultipliers = [null, [1, -1], [1, 1], [-1, 1], [-1, -1] ];
149         var maybeMakeGradient = function(ctx, style, gradientFunction) {
150                 if (style.gradient) {
151                         var g = gradientFunction();
152                         for ( var i = 0; i < style.gradient.stops.length; i++)
153                                 g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]);
154                         ctx.strokeStyle = g;
155                 }
156         };
157         var segmentRenderer = function(segment, ctx, style, dx, dy) {   
158                 ({
159                         "Straight":function(segment, ctx, style, dx, dy) {
160                                 var d = segment.params;
161                                 ctx.save();
162                                 maybeMakeGradient(ctx, style, function() { return ctx.createLinearGradient(d.x1, d.y1, d.x2, d.y2); });
163                                 ctx.beginPath();
164                                 ctx.translate(dx, dy);                          
165                                 if (style.dashstyle && style.dashstyle.split(" ").length === 2) {                       
166                                         // only a very simple dashed style is supported - having two values, which define the stroke length 
167                                         // (as a multiple of the stroke width) and then the space length (also as a multiple of stroke width). 
168                                         var ds = style.dashstyle.split(" ");
169                                         if (ds.length !== 2) ds = [2, 2];
170                                         var dss = [ ds[0] * style.lineWidth, ds[1] * style.lineWidth ],
171                                                 m = (d.x2- d.x1) / (d.y2 - d.y1),
172                                                 s = jsPlumbUtil.segment([d.x1, d.y1], [ d.x2, d.y2 ]),
173                                                 sm = segmentMultipliers[s],
174                                                 theta = Math.atan(m),
175                                                 l = Math.sqrt(Math.pow(d.x2 - d.x1, 2) + Math.pow(d.y2 - d.y1, 2)),
176                                                 repeats = Math.floor(l / (dss[0] + dss[1])),
177                                                 curPos = [d.x1, d.y1];
178
179                                         
180                                         // TODO: the question here is why could we not support this in all connector types? it's really
181                                         // just a case of going along and asking jsPlumb for the next point on the path a few times, until it
182                                         // reaches the end. every type of connector supports that method, after all.  but right now its only the
183                                         // bezier connector that gives you back the new location on the path along with the x,y coordinates, which
184                                         // we would need. we'd start out at loc=0 and ask for the point along the path that is dss[0] pixels away.
185                                         // we then ask for the point that is (dss[0] + dss[1]) pixels away; and from that one we need not just the
186                                         // x,y but the location, cos we're gonna plug that location back in in order to find where that dash ends.
187                                         //
188                                         // it also strikes me that it should be trivial to support arbitrary dash styles (having more or less than two
189                                         // entries). you'd just iterate that array using a step size of 2, and generify the (rss[0] + rss[1])
190                                         // computation to be sum(rss[0]..rss[n]).                                       
191
192                                         for (var i = 0; i < repeats; i++) {
193                                                 ctx.moveTo(curPos[0], curPos[1]);
194
195                                                 var nextEndX = curPos[0] + (Math.abs(Math.sin(theta) * dss[0]) * sm[0]),
196                                                         nextEndY = curPos[1] + (Math.abs(Math.cos(theta) * dss[0]) * sm[1]),
197                                                         nextStartX = curPos[0] + (Math.abs(Math.sin(theta) * (dss[0] + dss[1]))  * sm[0]),
198                                                         nextStartY = curPos[1] + (Math.abs(Math.cos(theta) * (dss[0] + dss[1])) * sm[1]);
199
200                                                 ctx.lineTo(nextEndX, nextEndY);
201                                                 curPos = [nextStartX, nextStartY];                                      
202                                         }
203
204                                         // now draw the last bit
205                                         ctx.moveTo(curPos[0], curPos[1]);
206                                         ctx.lineTo(d.x2, d.y2);                                                 
207
208                                 }               
209                         else {
210                                         ctx.moveTo(d.x1, d.y1);
211                                         ctx.lineTo(d.x2, d.y2);
212                         }                               
213
214                                 ctx.stroke();
215
216                                 ctx.restore();
217                         },
218                         "Bezier":function(segment, ctx, style, dx, dy) {                                
219                                 var d = segment.params;
220                                 ctx.save();
221                                 maybeMakeGradient(ctx, style, function() { return ctx.createLinearGradient(d.x2 + dx, d.y2 + dy, d.x1 + dx, d.y1 + dy); });
222                                 ctx.beginPath();
223                                 ctx.translate(dx, dy);
224                                 ctx.moveTo(d.x1, d.y1);
225                                 ctx.bezierCurveTo(d.cp1x, d.cp1y, d.cp2x, d.cp2y, d.x2, d.y2);
226                                 ctx.stroke();
227                                 ctx.restore();
228                         },
229                         "Arc":function(segment, ctx, style, dx, dy) {
230                                 var d = segment.params;
231                                 ctx.save();
232                                 ctx.beginPath();
233                                 ctx.translate(dx, dy);                          
234                                 ctx.arc(d.cx, d.cy, d.r, segment.startAngle, segment.endAngle, d.ac);
235                                 ctx.stroke();
236                                 ctx.restore();
237                         }
238                 })[segment.type](segment, ctx, style, dx, dy);  
239         };
240         
241         /**
242          * Class:CanvasConnector
243          * Superclass for Canvas Connector renderers.
244          */
245         var CanvasConnector = jsPlumb.ConnectorRenderers.canvas = function(params) {
246                 CanvasComponent.apply(this, arguments);
247                 
248                 var _paintOneStyle = function(aStyle, dx, dy) {
249                         this.ctx.save();
250                         jsPlumb.extend(this.ctx, aStyle);
251
252                         var segments = this.getSegments();                              
253                         for (var i = 0; i < segments.length; i++) {
254                                 segmentRenderer(segments[i], this.ctx, aStyle, dx, dy);
255                         }
256                         this.ctx.restore();
257                 }.bind(this);
258
259                 var clazz = this._jsPlumb.instance.connectorClass + " " + (params.cssClass || "");
260                 this.canvas = _newCanvas({ 
261                         "class":clazz, 
262                         _jsPlumb:this._jsPlumb,
263                         parent:params.parent
264                 });     
265                 this.ctx = this.canvas.getContext("2d");
266                 
267                 this.appendDisplayElement(this.canvas);
268                 
269                 this.paint = function(style, anchor, extents) {                                         
270                         if (style != null) {                                                    
271
272                                 var xy = [ this.x, this.y ], wh = [ this.w, this.h ], p,
273                                         dx = 0, dy = 0;
274
275                                 if (extents != null) {
276                                         if (extents.xmin < 0) {
277                                                 xy[0] += extents.xmin;
278                                                 dx = -extents.xmin;
279                                         }
280                                         if (extents.ymin < 0) {
281                                                 xy[1] += extents.ymin;
282                                                 dy = -extents.ymin;
283                                         }
284                                         wh[0] = extents.xmax + ((extents.xmin < 0) ? -extents.xmin : 0);
285                                         wh[1] = extents.ymax + ((extents.ymin < 0) ? -extents.ymin : 0);
286                                 }
287
288                                 this.translateX = dx;
289                                 this.translateY = dy;
290                                 
291                                 jsPlumbUtil.sizeElement(this.canvas, xy[0], xy[1], wh[0], wh[1]);                               
292                                 
293                                 if (style.outlineColor != null) {
294                                         var outlineWidth = style.outlineWidth || 1,
295                                         outlineStrokeWidth = style.lineWidth + (2 * outlineWidth),
296                                         outlineStyle = {
297                                                 strokeStyle:style.outlineColor,
298                                                 lineWidth:outlineStrokeWidth
299                                         };
300                                         _paintOneStyle(outlineStyle, dx, dy);
301                                 }
302                                 _paintOneStyle(style, dx, dy);
303                         }
304                 };                              
305         };              
306         jsPlumbUtil.extend(CanvasConnector, CanvasComponent);
307                 
308         
309         /**
310          * Class:CanvasEndpoint
311          * Superclass for Canvas Endpoint renderers.
312          */
313         var CanvasEndpoint = function(params) {
314                 CanvasComponent.apply(this, arguments);         
315                 var clazz = this._jsPlumb.instance.endpointClass + " " + (params.cssClass || ""),
316                         canvasParams = { 
317                         "class":clazz, 
318                         _jsPlumb:this._jsPlumb,
319                         parent:params.parent,
320                         tooltip:self.tooltip
321                 };
322                 this.canvas = _newCanvas(canvasParams); 
323                 this.ctx = this.canvas.getContext("2d");
324
325                 this.appendDisplayElement(this.canvas);
326                 
327                 this.paint = function(style, anchor, extents) {
328                         jsPlumbUtil.sizeElement(this.canvas, this.x, this.y, this.w, this.h);                   
329                         if (style.outlineColor != null) {
330                                 var outlineWidth = style.outlineWidth || 1,
331                                 outlineStrokeWidth = style.lineWidth + (2 * outlineWidth);
332                                 var outlineStyle = {
333                                         strokeStyle:style.outlineColor,
334                                         lineWidth:outlineStrokeWidth
335                                 };
336                         }
337                         
338                         this._paint.apply(this, arguments);
339                 };
340         };
341         jsPlumbUtil.extend(CanvasEndpoint, CanvasComponent);
342         
343         jsPlumb.Endpoints.canvas.Dot = function(params) {               
344                 jsPlumb.Endpoints.Dot.apply(this, arguments);
345                 CanvasEndpoint.apply(this, arguments);
346                 var self = this,                
347                 parseValue = function(value) {
348                         try { return parseInt(value, 10); }
349                         catch(e) {
350                                 if (value.substring(value.length - 1) == '%')
351                                         return parseInt(value.substring(0, value - 1), 10);
352                         }
353                 },                                              
354                 calculateAdjustments = function(gradient) {
355                         var offsetAdjustment = self.defaultOffset, innerRadius = self.defaultInnerRadius;
356                         if (gradient.offset) offsetAdjustment = parseValue(gradient.offset);
357                 if (gradient.innerRadius) innerRadius = parseValue(gradient.innerRadius);
358                 return [offsetAdjustment, innerRadius];
359                 };
360                 this._paint = function(style) {
361                         if (style != null) {                    
362                                 var ctx = self.canvas.getContext('2d'), 
363                                         orientation = params.endpoint.anchor.getOrientation(params.endpoint);
364
365                                 jsPlumb.extend(ctx, style);                                                     
366                     if (style.gradient) {               
367                         var adjustments = calculateAdjustments(style.gradient), 
368                         yAdjust = orientation[1] == 1 ? adjustments[0] * -1 : adjustments[0],
369                         xAdjust = orientation[0] == 1 ? adjustments[0] * -1:  adjustments[0],
370                         g = ctx.createRadialGradient(self.radius, self.radius, self.radius, self.radius + xAdjust, self.radius + yAdjust, adjustments[1]);
371                             for (var i = 0; i < style.gradient.stops.length; i++)
372                                 g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]);
373                             ctx.fillStyle = g;
374                     }                           
375                                 ctx.beginPath();    
376                                 //ctx.translate(dx, dy);                                                
377                                 ctx.arc(self.radius, self.radius, self.radius, 0, Math.PI*2, true);
378                                 ctx.closePath();                                
379                                 if (style.fillStyle || style.gradient) ctx.fill();
380                                 if (style.strokeStyle) ctx.stroke();
381                         }
382         };
383         };      
384         jsPlumbUtil.extend(jsPlumb.Endpoints.canvas.Dot, [ jsPlumb.Endpoints.Dot, CanvasEndpoint ]);
385                 
386         jsPlumb.Endpoints.canvas.Rectangle = function(params) {
387                 
388                 var self = this;
389                 jsPlumb.Endpoints.Rectangle.apply(this, arguments);
390                 CanvasEndpoint.apply(this, arguments);                          
391                 
392         this._paint = function(style) {
393                                 
394                         var ctx = self.canvas.getContext("2d"), 
395                                 orientation = params.endpoint.anchor.getOrientation(params.endpoint);
396
397                         jsPlumb.extend(ctx, style);
398                         
399                         /* canvas gradient */
400                     if (style.gradient) {
401                         // first figure out which direction to run the gradient in (it depends on the orientation of the anchors)
402                         var y1 = orientation[1] == 1 ? self.h : orientation[1] === 0 ? self.h / 2 : 0;
403                                 var y2 = orientation[1] == -1 ? self.h : orientation[1] === 0 ? self.h / 2 : 0;
404                                 var x1 = orientation[0] == 1 ? self.w : orientation[0] === 0 ? self.w / 2 : 0;
405                                 var x2 = orientation[0] == -1 ? self.w : orientation[0] === 0 ? self.w / 2 : 0;
406                             var g = ctx.createLinearGradient(x1,y1,x2,y2);
407                             for (var i = 0; i < style.gradient.stops.length; i++)
408                         g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]);
409                     ctx.fillStyle = g;
410                     }
411                         
412                         ctx.beginPath();
413                         ctx.rect(0, 0, self.w, self.h);
414                         ctx.closePath();                                
415                         if (style.fillStyle || style.gradient) ctx.fill();
416                         if (style.strokeStyle) ctx.stroke();
417         };
418         };              
419         jsPlumbUtil.extend(jsPlumb.Endpoints.canvas.Rectangle, [ jsPlumb.Endpoints.Rectangle, CanvasEndpoint ]);
420         
421         jsPlumb.Endpoints.canvas.Triangle = function(params) {
422                                         
423                 var self = this;
424                 jsPlumb.Endpoints.Triangle.apply(this, arguments);
425                 CanvasEndpoint.apply(this, arguments);                  
426                 
427         this._paint = function(style) {                                         
428                         var ctx = self.canvas.getContext('2d'),
429                                 offsetX = 0, offsetY = 0, angle = 0,
430                                 orientation = params.endpoint.anchor.getOrientation(params.endpoint);
431                         
432                         if( orientation[0] == 1 ) {
433                                 offsetX = self.width;
434                                 offsetY = self.height;
435                                 angle = 180;
436                         }
437                         if( orientation[1] == -1 ) {
438                                 offsetX = self.width;
439                                 angle = 90;
440                         }
441                         if( orientation[1] == 1 ) {
442                                 offsetY = self.height;
443                                 angle = -90;
444                         }
445                         
446                         ctx.fillStyle = style.fillStyle;
447                         
448                         ctx.translate(offsetX, offsetY);
449                         ctx.rotate(angle * Math.PI/180);
450
451                         ctx.beginPath();
452                         ctx.moveTo(0, 0);
453                         ctx.lineTo(self.width/2, self.height/2);
454                         ctx.lineTo(0, self.height);
455                         ctx.closePath();
456                         if (style.fillStyle || style.gradient) ctx.fill();
457                         if (style.strokeStyle) ctx.stroke();                            
458         };
459         };      
460         jsPlumbUtil.extend(jsPlumb.Endpoints.canvas.Triangle, [ jsPlumb.Endpoints.Triangle, CanvasEndpoint ]);
461         
462         /*
463          * Canvas Image Endpoint: uses the default version, which creates an <img> tag.
464          */
465         jsPlumb.Endpoints.canvas.Image = jsPlumb.Endpoints.Image;
466         
467         /*
468          * Blank endpoint in all renderers is just the default Blank endpoint.
469          */
470         jsPlumb.Endpoints.canvas.Blank = jsPlumb.Endpoints.Blank;
471                 
472 // ********************************* END OF CANVAS RENDERERS *******************************************************************    
473     
474     jsPlumb.Overlays.canvas.Label = jsPlumb.Overlays.Label;
475         jsPlumb.Overlays.canvas.Custom = jsPlumb.Overlays.Custom;
476     
477     /**
478      * a placeholder right now, really just exists to mirror the fact that there are SVG and VML versions of this. 
479      */
480     var CanvasOverlay = function() { 
481         jsPlumb.jsPlumbUIComponent.apply(this, arguments);
482     };
483     jsPlumbUtil.extend(CanvasOverlay, jsPlumb.jsPlumbUIComponent, {
484         setVisible : function(state) {
485             this.visible = state;
486             this.component.repaint();
487         }
488     });
489     
490     var AbstractCanvasArrowOverlay = function(superclass, originalArgs) {
491         superclass.apply(this, originalArgs);
492         CanvasOverlay.apply(this, originalArgs);
493         this.paint = function(params, containerExtents) {
494                 var ctx = params.component.ctx, d = params.d;
495                 
496                 if (d) {
497                         ctx.save();
498                                 ctx.lineWidth = params.lineWidth;
499                                 ctx.beginPath();
500                                 ctx.translate(params.component.translateX, params.component.translateY);
501                                 ctx.moveTo(d.hxy.x, d.hxy.y);
502                                 ctx.lineTo(d.tail[0].x, d.tail[0].y);
503                                 ctx.lineTo(d.cxy.x, d.cxy.y);
504                                 ctx.lineTo(d.tail[1].x, d.tail[1].y);
505                                 ctx.lineTo(d.hxy.x, d.hxy.y);
506                                 ctx.closePath();                                                
507                                                         
508                                 if (params.strokeStyle) {
509                                         ctx.strokeStyle = params.strokeStyle;
510                                         ctx.stroke();
511                                 }
512                                 if (params.fillStyle) {
513                                         ctx.fillStyle = params.fillStyle;                       
514                                         ctx.fill();
515                                 }
516                                 ctx.restore();
517                         }
518         };
519     }; 
520     
521     jsPlumb.Overlays.canvas.Arrow = function() {
522         AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]);            
523     };
524     jsPlumbUtil.extend(jsPlumb.Overlays.canvas.Arrow, [ jsPlumb.Overlays.Arrow, CanvasOverlay ] );
525     
526     jsPlumb.Overlays.canvas.PlainArrow = function() {
527         AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]);       
528     };
529     jsPlumbUtil.extend(jsPlumb.Overlays.canvas.PlainArrow, [ jsPlumb.Overlays.PlainArrow, CanvasOverlay ] );
530     
531     jsPlumb.Overlays.canvas.Diamond = function() {
532         AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]);          
533     };          
534     jsPlumbUtil.extend(jsPlumb.Overlays.canvas.Diamond, [ jsPlumb.Overlays.Diamond, CanvasOverlay ] );
535 })();