jsPlumb-renderers-canvas-1.3.0-RC1.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441
  1. ;(function() {
  2. // ********************************* CANVAS RENDERERS FOR CONNECTORS AND ENDPOINTS *******************************************************************
  3. // TODO refactor to renderer common script. put a ref to jsPlumb.sizeCanvas in there too.
  4. var _connectionBeingDragged = null,
  5. _getAttribute = function(el, attName) { return jsPlumb.CurrentLibrary.getAttribute(_getElementObject(el), attName); },
  6. _setAttribute = function(el, attName, attValue) { jsPlumb.CurrentLibrary.setAttribute(_getElementObject(el), attName, attValue); },
  7. _addClass = function(el, clazz) { jsPlumb.CurrentLibrary.addClass(_getElementObject(el), clazz); },
  8. _hasClass = function(el, clazz) { return jsPlumb.CurrentLibrary.hasClass(_getElementObject(el), clazz); },
  9. _removeClass = function(el, clazz) { jsPlumb.CurrentLibrary.removeClass(_getElementObject(el), clazz); },
  10. _getElementObject = function(el) { return jsPlumb.CurrentLibrary.getElementObject(el); },
  11. _getOffset = function(el) { return jsPlumb.CurrentLibrary.getOffset(_getElementObject(el)); },
  12. _getSize = function(el) { return jsPlumb.CurrentLibrary.getSize(_getElementObject(el)); },
  13. _pageXY = function(el) { return jsPlumb.CurrentLibrary.getPageXY(el); },
  14. _clientXY = function(el) { return jsPlumb.CurrentLibrary.getClientXY(el); },
  15. _setOffset = function(el, o) { jsPlumb.CurrentLibrary.setOffset(el, o); };
  16. /*
  17. * Class:CanvasMouseAdapter
  18. * Provides support for mouse events on canvases.
  19. */
  20. var CanvasMouseAdapter = function() {
  21. var self = this;
  22. self.overlayPlacements = [];
  23. jsPlumb.jsPlumbUIComponent.apply(this, arguments);
  24. jsPlumb.EventGenerator.apply(this, arguments);
  25. /**
  26. * returns whether or not the given event is ojver a painted area of the canvas.
  27. */
  28. this._over = function(e) {
  29. var o = _getOffset(_getElementObject(self.canvas)),
  30. pageXY = _pageXY(e),
  31. x = pageXY[0] - o.left, y = pageXY[1] - o.top;
  32. if (x > 0 && y > 0 && x < self.canvas.width && y < self.canvas.height) {
  33. // first check overlays
  34. for ( var i = 0; i < self.overlayPlacements.length; i++) {
  35. var p = self.overlayPlacements[i];
  36. if (p && (p[0] <= x && p[1] >= x && p[2] <= y && p[3] >= y))
  37. return true;
  38. }
  39. // then the canvas
  40. var d = self.canvas.getContext("2d").getImageData(parseInt(x), parseInt(y), 1, 1);
  41. return d.data[0] != 0 || d.data[1] != 0 || d.data[2] != 0 || d.data[3] != 0;
  42. }
  43. return false;
  44. };
  45. var _mouseover = false;
  46. var _mouseDown = false, _posWhenMouseDown = null, _mouseWasDown = false;
  47. var _nullSafeHasClass = function(el, clazz) {
  48. return el != null && _hasClass(el, clazz);
  49. };
  50. this.mousemove = function(e) {
  51. var pageXY = _pageXY(e), clientXY = _clientXY(e),
  52. ee = document.elementFromPoint(clientXY[0], clientXY[1]),
  53. eventSourceWasOverlay = _nullSafeHasClass(ee, "_jsPlumb_overlay");
  54. var _continue = _connectionBeingDragged == null && (_nullSafeHasClass(ee, "_jsPlumb_endpoint") || _nullSafeHasClass(ee, "_jsPlumb_connector"));
  55. if (!_mouseover && _continue && self._over(e)) {
  56. _mouseover = true;
  57. self.fire("mouseenter", self, e);
  58. return true;
  59. }
  60. // TODO here there is a remote chance that the overlay the mouse moved onto
  61. // is actually not an overlay for the current component. a more thorough check would
  62. // be to ensure the overlay belonged to the current component.
  63. else if (_mouseover && (!self._over(e) || !_continue) && !eventSourceWasOverlay) {
  64. _mouseover = false;
  65. self.fire("mouseexit", self, e);
  66. }
  67. self.fire("mousemove", self, e);
  68. };
  69. this.click = function(e) {
  70. if (_mouseover && self._over(e) && !_mouseWasDown)
  71. self.fire("click", self, e);
  72. _mouseWasDown = false;
  73. };
  74. this.dblclick = function(e) {
  75. if (_mouseover && self._over(e) && !_mouseWasDown)
  76. self.fire("dblclick", self, e);
  77. _mouseWasDown = false;
  78. };
  79. this.mousedown = function(e) {
  80. if(self._over(e) && !_mouseDown) {
  81. _mouseDown = true;
  82. _posWhenMouseDown = _getOffset(_getElementObject(self.canvas));
  83. self.fire("mousedown", self, e);
  84. }
  85. };
  86. this.mouseup = function(e) {
  87. //if (self == _connectionBeingDragged) _connectionBeingDragged = null;
  88. _mouseDown = false;
  89. self.fire("mouseup", self, e);
  90. };
  91. };
  92. var _newCanvas = function(params) {
  93. var canvas = document.createElement("canvas");
  94. jsPlumb.appendElement(canvas, params.parent);
  95. canvas.style.position = "absolute";
  96. if (params["class"]) canvas.className = params["class"];
  97. // set an id. if no id on the element and if uuid was supplied it
  98. // will be used, otherwise we'll create one.
  99. params["_jsPlumb"].getId(canvas, params.uuid);
  100. return canvas;
  101. };
  102. /**
  103. * Class:CanvasConnector
  104. * Superclass for Canvas Connector renderers.
  105. */
  106. var CanvasConnector = jsPlumb.CanvasConnector = function(params) {
  107. CanvasMouseAdapter.apply(this, arguments);
  108. var _paintOneStyle = function(dim, aStyle) {
  109. self.ctx.save();
  110. jsPlumb.extend(self.ctx, aStyle);
  111. if (aStyle.gradient) {
  112. var g = self.createGradient(dim, self.ctx);
  113. for ( var i = 0; i < aStyle.gradient.stops.length; i++)
  114. g.addColorStop(aStyle.gradient.stops[i][0], aStyle.gradient.stops[i][1]);
  115. self.ctx.strokeStyle = g;
  116. }
  117. self._paint(dim);
  118. self.ctx.restore();
  119. };
  120. var self = this,
  121. clazz = self._jsPlumb.connectorClass + " " + (params.cssClass || "");
  122. self.canvas = _newCanvas({
  123. "class":clazz,
  124. _jsPlumb:self._jsPlumb,
  125. parent:params.parent
  126. });
  127. self.ctx = self.canvas.getContext("2d");
  128. var displayElements = [ self.canvas ];
  129. this.getDisplayElements = function() {
  130. return displayElements;
  131. };
  132. this.appendDisplayElement = function(el) {
  133. displayElements.push(el);
  134. };
  135. self.paint = function(dim, style) {
  136. if (style != null) {
  137. jsPlumb.sizeCanvas(self.canvas, dim[0], dim[1], dim[2], dim[3]);
  138. if (style.outlineColor != null) {
  139. var outlineWidth = style.outlineWidth || 1,
  140. outlineStrokeWidth = style.lineWidth + (2 * outlineWidth);
  141. var outlineStyle = {
  142. strokeStyle:style.outlineColor,
  143. lineWidth:outlineStrokeWidth
  144. };
  145. _paintOneStyle(dim, outlineStyle);
  146. }
  147. _paintOneStyle(dim, style);
  148. }
  149. };
  150. };
  151. /**
  152. * Class:CanvasEndpoint
  153. * Superclass for Canvas Endpoint renderers.
  154. */
  155. var CanvasEndpoint = function(params) {
  156. var self = this;
  157. CanvasMouseAdapter.apply(this, arguments);
  158. var clazz = self._jsPlumb.endpointClass + " " + (params.cssClass || "");
  159. self.canvas = _newCanvas({
  160. "class":clazz,
  161. _jsPlumb:self._jsPlumb,
  162. parent:params.parent
  163. });
  164. self.ctx = self.canvas.getContext("2d");
  165. this.paint = function(d, style, anchor) {
  166. jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]);
  167. if (style.outlineColor != null) {
  168. var outlineWidth = style.outlineWidth || 1,
  169. outlineStrokeWidth = style.lineWidth + (2 * outlineWidth);
  170. var outlineStyle = {
  171. strokeStyle:style.outlineColor,
  172. lineWidth:outlineStrokeWidth
  173. };
  174. // _paintOneStyle(d, outlineStyle);
  175. }
  176. self._paint.apply(this, arguments);
  177. };
  178. };
  179. jsPlumb.Endpoints.canvas.Dot = function(params) {
  180. var self = this;
  181. jsPlumb.Endpoints.Dot.apply(this, arguments);
  182. CanvasEndpoint.apply(this, arguments);
  183. var parseValue = function(value) {
  184. try {
  185. return parseInt(value);
  186. }
  187. catch(e) {
  188. if (value.substring(value.length - 1) == '%')
  189. return parseInt(value.substring(0, value - 1));
  190. }
  191. };
  192. var calculateAdjustments = function(gradient) {
  193. var offsetAdjustment = self.defaultOffset, innerRadius = self.defaultInnerRadius;
  194. gradient.offset && (offsetAdjustment = parseValue(gradient.offset));
  195. gradient.innerRadius && (innerRadius = parseValue(gradient.innerRadius));
  196. return [offsetAdjustment, innerRadius];
  197. };
  198. this._paint = function(d, style, anchor) {
  199. if (style != null) {
  200. var ctx = self.canvas.getContext('2d'), orientation = anchor.getOrientation();
  201. jsPlumb.extend(ctx, style);
  202. if (style.gradient) {
  203. var adjustments = calculateAdjustments(style.gradient),
  204. yAdjust = orientation[1] == 1 ? adjustments[0] * -1 : adjustments[0],
  205. xAdjust = orientation[0] == 1 ? adjustments[0] * -1: adjustments[0],
  206. g = ctx.createRadialGradient(d[4], d[4], d[4], d[4] + xAdjust, d[4] + yAdjust, adjustments[1]);
  207. for (var i = 0; i < style.gradient.stops.length; i++)
  208. g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]);
  209. ctx.fillStyle = g;
  210. }
  211. ctx.beginPath();
  212. ctx.arc(d[4], d[4], d[4], 0, Math.PI*2, true);
  213. ctx.closePath();
  214. if (style.fillStyle || style.gradient) ctx.fill();
  215. if (style.strokeStyle) ctx.stroke();
  216. }
  217. };
  218. };
  219. jsPlumb.Endpoints.canvas.Rectangle = function(params) {
  220. var self = this;
  221. jsPlumb.Endpoints.Rectangle.apply(this, arguments);
  222. CanvasEndpoint.apply(this, arguments);
  223. this._paint = function(d, style, anchor) {
  224. var ctx = self.canvas.getContext("2d"), orientation = anchor.getOrientation();
  225. jsPlumb.extend(ctx, style);
  226. /* canvas gradient */
  227. if (style.gradient) {
  228. // first figure out which direction to run the gradient in (it depends on the orientation of the anchors)
  229. var y1 = orientation[1] == 1 ? d[3] : orientation[1] == 0 ? d[3] / 2 : 0;
  230. var y2 = orientation[1] == -1 ? d[3] : orientation[1] == 0 ? d[3] / 2 : 0;
  231. var x1 = orientation[0] == 1 ? d[2] : orientation[0] == 0 ? d[2] / 2 : 0;
  232. var x2 = orientation[0] == -1 ? d[2] : orientation[0] == 0 ? d[2] / 2 : 0;
  233. var g = ctx.createLinearGradient(x1,y1,x2,y2);
  234. for (var i = 0; i < style.gradient.stops.length; i++)
  235. g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]);
  236. ctx.fillStyle = g;
  237. }
  238. ctx.beginPath();
  239. ctx.rect(0, 0, d[2], d[3]);
  240. ctx.closePath();
  241. if (style.fillStyle || style.gradient) ctx.fill();
  242. if (style.strokeStyle) ctx.stroke();
  243. };
  244. };
  245. jsPlumb.Endpoints.canvas.Triangle = function(params) {
  246. var self = this;
  247. jsPlumb.Endpoints.Triangle.apply(this, arguments);
  248. CanvasEndpoint.apply(this, arguments);
  249. this._paint = function(d, style, anchor)
  250. {
  251. var width = d[2], height = d[3], x = d[0], y = d[1];
  252. var ctx = self.canvas.getContext('2d');
  253. var offsetX = 0, offsetY = 0, angle = 0;
  254. if( orientation[0] == 1 )
  255. {
  256. offsetX = width;
  257. offsetY = height;
  258. angle = 180;
  259. }
  260. if( orientation[1] == -1 )
  261. {
  262. offsetX = width;
  263. angle = 90;
  264. }
  265. if( orientation[1] == 1 )
  266. {
  267. offsetY = height;
  268. angle = -90;
  269. }
  270. ctx.fillStyle = style.fillStyle;
  271. ctx.translate(offsetX, offsetY);
  272. ctx.rotate(angle * Math.PI/180);
  273. ctx.beginPath();
  274. ctx.moveTo(0, 0);
  275. ctx.lineTo(width/2, height/2);
  276. ctx.lineTo(0, height);
  277. ctx.closePath();
  278. if (style.fillStyle || style.gradient) ctx.fill();
  279. if (style.strokeStyle) ctx.stroke();
  280. };
  281. };
  282. /*
  283. * Canvas Image Endpoint: uses the default version, which creates an <img> tag.
  284. */
  285. jsPlumb.Endpoints.canvas.Image = jsPlumb.Endpoints.Image;
  286. /*
  287. * Blank endpoint in all renderers is just the default Blank endpoint.
  288. */
  289. jsPlumb.Endpoints.canvas.Blank = jsPlumb.Endpoints.Blank;
  290. /*
  291. * Canvas Bezier Connector. Draws a Bezier curve onto a Canvas element.
  292. */
  293. jsPlumb.Connectors.canvas.Bezier = function() {
  294. var self = this;
  295. jsPlumb.Connectors.Bezier.apply(this, arguments);
  296. CanvasConnector.apply(this, arguments);
  297. this._paint = function(dimensions) {
  298. self.ctx.beginPath();
  299. self.ctx.moveTo(dimensions[4], dimensions[5]);
  300. self.ctx.bezierCurveTo(dimensions[8], dimensions[9], dimensions[10], dimensions[11], dimensions[6], dimensions[7]);
  301. self.ctx.stroke();
  302. };
  303. // TODO i doubt this handles the case that source and target are swapped.
  304. this.createGradient = function(dim, ctx, swap) {
  305. return /*(swap) ? self.ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]) : */self.ctx.createLinearGradient(dim[6], dim[7], dim[4], dim[5]);
  306. };
  307. };
  308. /*
  309. * Canvas straight line Connector. Draws a straight line onto a Canvas element.
  310. */
  311. jsPlumb.Connectors.canvas.Straight = function() {
  312. var self = this;
  313. jsPlumb.Connectors.Straight.apply(this, arguments);
  314. CanvasConnector.apply(this, arguments);
  315. this._paint = function(dimensions) {
  316. self.ctx.beginPath();
  317. self.ctx.moveTo(dimensions[4], dimensions[5]);
  318. self.ctx.lineTo(dimensions[6], dimensions[7]);
  319. self.ctx.stroke();
  320. };
  321. // TODO this does not handle the case that src and target are swapped.
  322. this.createGradient = function(dim, ctx) {
  323. return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]);
  324. };
  325. };
  326. jsPlumb.Connectors.canvas.Flowchart = function() {
  327. var self = this;
  328. jsPlumb.Connectors.Flowchart.apply(this, arguments);
  329. CanvasConnector.apply(this, arguments);
  330. this._paint = function(dimensions) {
  331. self.ctx.beginPath();
  332. self.ctx.moveTo(dimensions[4], dimensions[5]);
  333. // loop through extra points
  334. for (var i = 0; i < dimensions[8]; i++) {
  335. self.ctx.lineTo(dimensions[9 + (i*2)], dimensions[10 + (i*2)]);
  336. }
  337. // finally draw a line to the end
  338. self.ctx.lineTo(dimensions[6], dimensions[7]);
  339. self.ctx.stroke();
  340. };
  341. this.createGradient = function(dim, ctx) {
  342. return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]);
  343. };
  344. };
  345. // ********************************* END OF CANVAS RENDERERS *******************************************************************
  346. jsPlumb.Overlays.canvas.Label = jsPlumb.Overlays.Label;
  347. /**
  348. * a placeholder right now, really just exists to mirror the fact that there are SVG and VML versions of this.
  349. */
  350. var CanvasOverlay = function() {
  351. jsPlumb.jsPlumbUIComponent.apply(this, arguments);
  352. };
  353. var AbstractCanvasArrowOverlay = function(superclass, originalArgs) {
  354. superclass.apply(this, originalArgs);
  355. CanvasOverlay.apply(this, arguments);
  356. this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) {
  357. var ctx = connector.ctx;
  358. ctx.lineWidth = lineWidth;
  359. ctx.beginPath();
  360. ctx.moveTo(d.hxy.x, d.hxy.y);
  361. ctx.lineTo(d.tail[0].x, d.tail[0].y);
  362. ctx.lineTo(d.cxy.x, d.cxy.y);
  363. ctx.lineTo(d.tail[1].x, d.tail[1].y);
  364. ctx.lineTo(d.hxy.x, d.hxy.y);
  365. ctx.closePath();
  366. if (strokeStyle) {
  367. ctx.strokeStyle = strokeStyle;
  368. ctx.stroke();
  369. }
  370. if (fillStyle) {
  371. ctx.fillStyle = fillStyle;
  372. ctx.fill();
  373. }
  374. };
  375. };
  376. jsPlumb.Overlays.canvas.Arrow = function() {
  377. AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]);
  378. };
  379. jsPlumb.Overlays.canvas.PlainArrow = function() {
  380. AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]);
  381. };
  382. jsPlumb.Overlays.canvas.Diamond = function() {
  383. AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]);
  384. };
  385. })();