jsPlumb-renderers-canvas-1.3.5-RC1.js 16 KB

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