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

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