;(function() { // ********************************* CANVAS RENDERERS FOR CONNECTORS AND ENDPOINTS ******************************************************************* // TODO refactor to renderer common script. put a ref to jsPlumb.sizeCanvas in there too. var _connectionBeingDragged = null, _getAttribute = function(el, attName) { return jsPlumb.CurrentLibrary.getAttribute(_getElementObject(el), attName); }, _setAttribute = function(el, attName, attValue) { jsPlumb.CurrentLibrary.setAttribute(_getElementObject(el), attName, attValue); }, _addClass = function(el, clazz) { jsPlumb.CurrentLibrary.addClass(_getElementObject(el), clazz); }, _hasClass = function(el, clazz) { return jsPlumb.CurrentLibrary.hasClass(_getElementObject(el), clazz); }, _removeClass = function(el, clazz) { jsPlumb.CurrentLibrary.removeClass(_getElementObject(el), clazz); }, _getElementObject = function(el) { return jsPlumb.CurrentLibrary.getElementObject(el); }, _getOffset = function(el) { return jsPlumb.CurrentLibrary.getOffset(_getElementObject(el)); }, _getSize = function(el) { return jsPlumb.CurrentLibrary.getSize(_getElementObject(el)); }, _pageXY = function(el) { return jsPlumb.CurrentLibrary.getPageXY(el); }, _clientXY = function(el) { return jsPlumb.CurrentLibrary.getClientXY(el); }, _setOffset = function(el, o) { jsPlumb.CurrentLibrary.setOffset(el, o); }; /* * Class:CanvasMouseAdapter * Provides support for mouse events on canvases. */ var CanvasMouseAdapter = function() { var self = this; self.overlayPlacements = []; jsPlumb.jsPlumbUIComponent.apply(this, arguments); jsPlumb.EventGenerator.apply(this, arguments); /** * returns whether or not the given event is ojver a painted area of the canvas. */ this._over = function(e) { var o = _getOffset(_getElementObject(self.canvas)), pageXY = _pageXY(e), x = pageXY[0] - o.left, y = pageXY[1] - o.top; if (x > 0 && y > 0 && x < self.canvas.width && y < self.canvas.height) { // first check overlays for ( var i = 0; i < self.overlayPlacements.length; i++) { var p = self.overlayPlacements[i]; if (p && (p[0] <= x && p[1] >= x && p[2] <= y && p[3] >= y)) return true; } // then the canvas var d = self.canvas.getContext("2d").getImageData(parseInt(x), parseInt(y), 1, 1); return d.data[0] != 0 || d.data[1] != 0 || d.data[2] != 0 || d.data[3] != 0; } return false; }; var _mouseover = false; var _mouseDown = false, _posWhenMouseDown = null, _mouseWasDown = false; var _nullSafeHasClass = function(el, clazz) { return el != null && _hasClass(el, clazz); }; this.mousemove = function(e) { var pageXY = _pageXY(e), clientXY = _clientXY(e), ee = document.elementFromPoint(clientXY[0], clientXY[1]), eventSourceWasOverlay = _nullSafeHasClass(ee, "_jsPlumb_overlay"); var _continue = _connectionBeingDragged == null && (_nullSafeHasClass(ee, "_jsPlumb_endpoint") || _nullSafeHasClass(ee, "_jsPlumb_connector")); if (!_mouseover && _continue && self._over(e)) { _mouseover = true; self.fire("mouseenter", self, e); return true; } // TODO here there is a remote chance that the overlay the mouse moved onto // is actually not an overlay for the current component. a more thorough check would // be to ensure the overlay belonged to the current component. else if (_mouseover && (!self._over(e) || !_continue) && !eventSourceWasOverlay) { _mouseover = false; self.fire("mouseexit", self, e); } self.fire("mousemove", self, e); }; this.click = function(e) { if (_mouseover && self._over(e) && !_mouseWasDown) self.fire("click", self, e); _mouseWasDown = false; }; this.dblclick = function(e) { if (_mouseover && self._over(e) && !_mouseWasDown) self.fire("dblclick", self, e); _mouseWasDown = false; }; this.mousedown = function(e) { if(self._over(e) && !_mouseDown) { _mouseDown = true; _posWhenMouseDown = _getOffset(_getElementObject(self.canvas)); self.fire("mousedown", self, e); } }; this.mouseup = function(e) { //if (self == _connectionBeingDragged) _connectionBeingDragged = null; _mouseDown = false; self.fire("mouseup", self, e); }; }; var _newCanvas = function(params) { var canvas = document.createElement("canvas"); jsPlumb.appendElement(canvas, params.parent); canvas.style.position = "absolute"; if (params["class"]) canvas.className = params["class"]; // set an id. if no id on the element and if uuid was supplied it // will be used, otherwise we'll create one. params["_jsPlumb"].getId(canvas, params.uuid); return canvas; }; /** * Class:CanvasConnector * Superclass for Canvas Connector renderers. */ var CanvasConnector = jsPlumb.CanvasConnector = function(params) { CanvasMouseAdapter.apply(this, arguments); var _paintOneStyle = function(dim, aStyle) { self.ctx.save(); jsPlumb.extend(self.ctx, aStyle); if (aStyle.gradient) { var g = self.createGradient(dim, self.ctx); for ( var i = 0; i < aStyle.gradient.stops.length; i++) g.addColorStop(aStyle.gradient.stops[i][0], aStyle.gradient.stops[i][1]); self.ctx.strokeStyle = g; } self._paint(dim); self.ctx.restore(); }; var self = this, clazz = self._jsPlumb.connectorClass + " " + (params.cssClass || ""); self.canvas = _newCanvas({ "class":clazz, _jsPlumb:self._jsPlumb, parent:params.parent }); self.ctx = self.canvas.getContext("2d"); var displayElements = [ self.canvas ]; this.getDisplayElements = function() { return displayElements; }; this.appendDisplayElement = function(el) { displayElements.push(el); }; self.paint = function(dim, style) { if (style != null) { jsPlumb.sizeCanvas(self.canvas, dim[0], dim[1], dim[2], dim[3]); if (style.outlineColor != null) { var outlineWidth = style.outlineWidth || 1, outlineStrokeWidth = style.lineWidth + (2 * outlineWidth); var outlineStyle = { strokeStyle:style.outlineColor, lineWidth:outlineStrokeWidth }; _paintOneStyle(dim, outlineStyle); } _paintOneStyle(dim, style); } }; }; /** * Class:CanvasEndpoint * Superclass for Canvas Endpoint renderers. */ var CanvasEndpoint = function(params) { var self = this; CanvasMouseAdapter.apply(this, arguments); var clazz = self._jsPlumb.endpointClass + " " + (params.cssClass || ""); self.canvas = _newCanvas({ "class":clazz, _jsPlumb:self._jsPlumb, parent:params.parent }); self.ctx = self.canvas.getContext("2d"); this.paint = function(d, style, anchor) { jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); if (style.outlineColor != null) { var outlineWidth = style.outlineWidth || 1, outlineStrokeWidth = style.lineWidth + (2 * outlineWidth); var outlineStyle = { strokeStyle:style.outlineColor, lineWidth:outlineStrokeWidth }; // _paintOneStyle(d, outlineStyle); } self._paint.apply(this, arguments); }; }; jsPlumb.Endpoints.canvas.Dot = function(params) { var self = this; jsPlumb.Endpoints.Dot.apply(this, arguments); CanvasEndpoint.apply(this, arguments); var parseValue = function(value) { try { return parseInt(value); } catch(e) { if (value.substring(value.length - 1) == '%') return parseInt(value.substring(0, value - 1)); } }; var calculateAdjustments = function(gradient) { var offsetAdjustment = self.defaultOffset, innerRadius = self.defaultInnerRadius; gradient.offset && (offsetAdjustment = parseValue(gradient.offset)); gradient.innerRadius && (innerRadius = parseValue(gradient.innerRadius)); return [offsetAdjustment, innerRadius]; }; this._paint = function(d, style, anchor) { if (style != null) { var ctx = self.canvas.getContext('2d'), orientation = anchor.getOrientation(); jsPlumb.extend(ctx, style); if (style.gradient) { var adjustments = calculateAdjustments(style.gradient), yAdjust = orientation[1] == 1 ? adjustments[0] * -1 : adjustments[0], xAdjust = orientation[0] == 1 ? adjustments[0] * -1: adjustments[0], g = ctx.createRadialGradient(d[4], d[4], d[4], d[4] + xAdjust, d[4] + yAdjust, adjustments[1]); for (var i = 0; i < style.gradient.stops.length; i++) g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]); ctx.fillStyle = g; } ctx.beginPath(); ctx.arc(d[4], d[4], d[4], 0, Math.PI*2, true); ctx.closePath(); if (style.fillStyle || style.gradient) ctx.fill(); if (style.strokeStyle) ctx.stroke(); } }; }; jsPlumb.Endpoints.canvas.Rectangle = function(params) { var self = this; jsPlumb.Endpoints.Rectangle.apply(this, arguments); CanvasEndpoint.apply(this, arguments); this._paint = function(d, style, anchor) { var ctx = self.canvas.getContext("2d"), orientation = anchor.getOrientation(); jsPlumb.extend(ctx, style); /* canvas gradient */ if (style.gradient) { // first figure out which direction to run the gradient in (it depends on the orientation of the anchors) var y1 = orientation[1] == 1 ? d[3] : orientation[1] == 0 ? d[3] / 2 : 0; var y2 = orientation[1] == -1 ? d[3] : orientation[1] == 0 ? d[3] / 2 : 0; var x1 = orientation[0] == 1 ? d[2] : orientation[0] == 0 ? d[2] / 2 : 0; var x2 = orientation[0] == -1 ? d[2] : orientation[0] == 0 ? d[2] / 2 : 0; var g = ctx.createLinearGradient(x1,y1,x2,y2); for (var i = 0; i < style.gradient.stops.length; i++) g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]); ctx.fillStyle = g; } ctx.beginPath(); ctx.rect(0, 0, d[2], d[3]); ctx.closePath(); if (style.fillStyle || style.gradient) ctx.fill(); if (style.strokeStyle) ctx.stroke(); }; }; jsPlumb.Endpoints.canvas.Triangle = function(params) { var self = this; jsPlumb.Endpoints.Triangle.apply(this, arguments); CanvasEndpoint.apply(this, arguments); this._paint = function(d, style, anchor) { var width = d[2], height = d[3], x = d[0], y = d[1]; var ctx = self.canvas.getContext('2d'); var offsetX = 0, offsetY = 0, angle = 0; if( orientation[0] == 1 ) { offsetX = width; offsetY = height; angle = 180; } if( orientation[1] == -1 ) { offsetX = width; angle = 90; } if( orientation[1] == 1 ) { offsetY = height; angle = -90; } ctx.fillStyle = style.fillStyle; ctx.translate(offsetX, offsetY); ctx.rotate(angle * Math.PI/180); ctx.beginPath(); ctx.moveTo(0, 0); ctx.lineTo(width/2, height/2); ctx.lineTo(0, height); ctx.closePath(); if (style.fillStyle || style.gradient) ctx.fill(); if (style.strokeStyle) ctx.stroke(); }; }; /* * Canvas Image Endpoint: uses the default version, which creates an tag. */ jsPlumb.Endpoints.canvas.Image = jsPlumb.Endpoints.Image; /* * Blank endpoint in all renderers is just the default Blank endpoint. */ jsPlumb.Endpoints.canvas.Blank = jsPlumb.Endpoints.Blank; /* * Canvas Bezier Connector. Draws a Bezier curve onto a Canvas element. */ jsPlumb.Connectors.canvas.Bezier = function() { var self = this; jsPlumb.Connectors.Bezier.apply(this, arguments); CanvasConnector.apply(this, arguments); this._paint = function(dimensions) { self.ctx.beginPath(); self.ctx.moveTo(dimensions[4], dimensions[5]); self.ctx.bezierCurveTo(dimensions[8], dimensions[9], dimensions[10], dimensions[11], dimensions[6], dimensions[7]); self.ctx.stroke(); }; // TODO i doubt this handles the case that source and target are swapped. this.createGradient = function(dim, ctx, swap) { return /*(swap) ? self.ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]) : */self.ctx.createLinearGradient(dim[6], dim[7], dim[4], dim[5]); }; }; /* * Canvas straight line Connector. Draws a straight line onto a Canvas element. */ jsPlumb.Connectors.canvas.Straight = function() { var self = this; jsPlumb.Connectors.Straight.apply(this, arguments); CanvasConnector.apply(this, arguments); this._paint = function(dimensions) { self.ctx.beginPath(); self.ctx.moveTo(dimensions[4], dimensions[5]); self.ctx.lineTo(dimensions[6], dimensions[7]); self.ctx.stroke(); }; // TODO this does not handle the case that src and target are swapped. this.createGradient = function(dim, ctx) { return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); }; }; jsPlumb.Connectors.canvas.Flowchart = function() { var self = this; jsPlumb.Connectors.Flowchart.apply(this, arguments); CanvasConnector.apply(this, arguments); this._paint = function(dimensions) { self.ctx.beginPath(); self.ctx.moveTo(dimensions[4], dimensions[5]); // loop through extra points for (var i = 0; i < dimensions[8]; i++) { self.ctx.lineTo(dimensions[9 + (i*2)], dimensions[10 + (i*2)]); } // finally draw a line to the end self.ctx.lineTo(dimensions[6], dimensions[7]); self.ctx.stroke(); }; this.createGradient = function(dim, ctx) { return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); }; }; // ********************************* END OF CANVAS RENDERERS ******************************************************************* jsPlumb.Overlays.canvas.Label = jsPlumb.Overlays.Label; /** * a placeholder right now, really just exists to mirror the fact that there are SVG and VML versions of this. */ var CanvasOverlay = function() { jsPlumb.jsPlumbUIComponent.apply(this, arguments); }; var AbstractCanvasArrowOverlay = function(superclass, originalArgs) { superclass.apply(this, originalArgs); CanvasOverlay.apply(this, arguments); this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { var ctx = connector.ctx; ctx.lineWidth = lineWidth; ctx.beginPath(); ctx.moveTo(d.hxy.x, d.hxy.y); ctx.lineTo(d.tail[0].x, d.tail[0].y); ctx.lineTo(d.cxy.x, d.cxy.y); ctx.lineTo(d.tail[1].x, d.tail[1].y); ctx.lineTo(d.hxy.x, d.hxy.y); ctx.closePath(); if (strokeStyle) { ctx.strokeStyle = strokeStyle; ctx.stroke(); } if (fillStyle) { ctx.fillStyle = fillStyle; ctx.fill(); } }; }; jsPlumb.Overlays.canvas.Arrow = function() { AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); }; jsPlumb.Overlays.canvas.PlainArrow = function() { AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); }; jsPlumb.Overlays.canvas.Diamond = function() { AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); }; })();