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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444
  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. // to filter right click in FF, i could compare e.button. 0 means left mouse button; 1 middle, 2 right.
  82. //console.log(e.button);
  83. if (_mouseover && self._over(e) && !_mouseWasDown)
  84. self.fire("click", self, e);
  85. _mouseWasDown = false;
  86. };
  87. this.dblclick = function(e) {
  88. if (_mouseover && self._over(e) && !_mouseWasDown)
  89. self.fire("dblclick", self, e);
  90. _mouseWasDown = false;
  91. };
  92. this.mousedown = function(e) {
  93. if(self._over(e) && !_mouseDown) {
  94. _mouseDown = true;
  95. _posWhenMouseDown = _getOffset(_getElementObject(self.canvas));
  96. self.fire("mousedown", self, e);
  97. }
  98. };
  99. this.mouseup = function(e) {
  100. _mouseDown = false;
  101. self.fire("mouseup", self, e);
  102. };
  103. };
  104. var _newCanvas = function(params) {
  105. var canvas = document.createElement("canvas");
  106. jsPlumb.appendElement(canvas, params.parent);
  107. canvas.style.position = "absolute";
  108. if (params["class"]) canvas.className = params["class"];
  109. // set an id. if no id on the element and if uuid was supplied it
  110. // will be used, otherwise we'll create one.
  111. params["_jsPlumb"].getId(canvas, params.uuid);
  112. if (params.tooltip) canvas.setAttribute("label", params.tooltip);
  113. return canvas;
  114. };
  115. /**
  116. * Class:CanvasConnector
  117. * Superclass for Canvas Connector renderers.
  118. */
  119. var CanvasConnector = jsPlumb.CanvasConnector = function(params) {
  120. CanvasMouseAdapter.apply(this, arguments);
  121. var _paintOneStyle = function(dim, aStyle) {
  122. self.ctx.save();
  123. jsPlumb.extend(self.ctx, aStyle);
  124. if (aStyle.gradient) {
  125. var g = self.createGradient(dim, self.ctx);
  126. for ( var i = 0; i < aStyle.gradient.stops.length; i++)
  127. g.addColorStop(aStyle.gradient.stops[i][0], aStyle.gradient.stops[i][1]);
  128. self.ctx.strokeStyle = g;
  129. }
  130. self._paint(dim);
  131. self.ctx.restore();
  132. };
  133. var self = this,
  134. clazz = self._jsPlumb.connectorClass + " " + (params.cssClass || "");
  135. self.canvas = _newCanvas({
  136. "class":clazz,
  137. _jsPlumb:self._jsPlumb,
  138. parent:params.parent,
  139. tooltip:params.tooltip
  140. });
  141. self.ctx = self.canvas.getContext("2d");
  142. var displayElements = [ self.canvas ];
  143. this.getDisplayElements = function() { return displayElements; };
  144. this.appendDisplayElement = function(el) { displayElements.push(el); };
  145. self.paint = function(dim, style) {
  146. if (style != null) {
  147. jsPlumb.sizeCanvas(self.canvas, dim[0], dim[1], dim[2], dim[3]);
  148. if (style.outlineColor != null) {
  149. var outlineWidth = style.outlineWidth || 1,
  150. outlineStrokeWidth = style.lineWidth + (2 * outlineWidth),
  151. outlineStyle = {
  152. strokeStyle:style.outlineColor,
  153. lineWidth:outlineStrokeWidth
  154. };
  155. _paintOneStyle(dim, outlineStyle);
  156. }
  157. _paintOneStyle(dim, style);
  158. }
  159. };
  160. };
  161. /**
  162. * Class:CanvasEndpoint
  163. * Superclass for Canvas Endpoint renderers.
  164. */
  165. var CanvasEndpoint = function(params) {
  166. var self = this;
  167. CanvasMouseAdapter.apply(this, arguments);
  168. var clazz = self._jsPlumb.endpointClass + " " + (params.cssClass || ""),
  169. canvasParams = {
  170. "class":clazz,
  171. _jsPlumb:self._jsPlumb,
  172. parent:params.parent,
  173. tooltip:self.tooltip
  174. };
  175. self.canvas = _newCanvas(canvasParams);
  176. self.ctx = self.canvas.getContext("2d");
  177. this.paint = function(d, style, anchor) {
  178. jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]);
  179. if (style.outlineColor != null) {
  180. var outlineWidth = style.outlineWidth || 1,
  181. outlineStrokeWidth = style.lineWidth + (2 * outlineWidth);
  182. var outlineStyle = {
  183. strokeStyle:style.outlineColor,
  184. lineWidth:outlineStrokeWidth
  185. };
  186. }
  187. self._paint.apply(this, arguments);
  188. };
  189. };
  190. jsPlumb.Endpoints.canvas.Dot = function(params) {
  191. jsPlumb.Endpoints.Dot.apply(this, arguments);
  192. CanvasEndpoint.apply(this, arguments);
  193. var self = this,
  194. parseValue = function(value) {
  195. try { return parseInt(value); }
  196. catch(e) {
  197. if (value.substring(value.length - 1) == '%')
  198. return parseInt(value.substring(0, value - 1));
  199. }
  200. },
  201. calculateAdjustments = function(gradient) {
  202. var offsetAdjustment = self.defaultOffset, innerRadius = self.defaultInnerRadius;
  203. gradient.offset && (offsetAdjustment = parseValue(gradient.offset));
  204. gradient.innerRadius && (innerRadius = parseValue(gradient.innerRadius));
  205. return [offsetAdjustment, innerRadius];
  206. };
  207. this._paint = function(d, style, anchor) {
  208. if (style != null) {
  209. var ctx = self.canvas.getContext('2d'), orientation = anchor.getOrientation(self);
  210. jsPlumb.extend(ctx, style);
  211. if (style.gradient) {
  212. var adjustments = calculateAdjustments(style.gradient),
  213. yAdjust = orientation[1] == 1 ? adjustments[0] * -1 : adjustments[0],
  214. xAdjust = orientation[0] == 1 ? adjustments[0] * -1: adjustments[0],
  215. g = ctx.createRadialGradient(d[4], d[4], d[4], d[4] + xAdjust, d[4] + yAdjust, adjustments[1]);
  216. for (var i = 0; i < style.gradient.stops.length; i++)
  217. g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]);
  218. ctx.fillStyle = g;
  219. }
  220. ctx.beginPath();
  221. ctx.arc(d[4], d[4], d[4], 0, Math.PI*2, true);
  222. ctx.closePath();
  223. if (style.fillStyle || style.gradient) ctx.fill();
  224. if (style.strokeStyle) ctx.stroke();
  225. }
  226. };
  227. };
  228. jsPlumb.Endpoints.canvas.Rectangle = function(params) {
  229. var self = this;
  230. jsPlumb.Endpoints.Rectangle.apply(this, arguments);
  231. CanvasEndpoint.apply(this, arguments);
  232. this._paint = function(d, style, anchor) {
  233. var ctx = self.canvas.getContext("2d"), orientation = anchor.getOrientation(self);
  234. jsPlumb.extend(ctx, style);
  235. /* canvas gradient */
  236. if (style.gradient) {
  237. // first figure out which direction to run the gradient in (it depends on the orientation of the anchors)
  238. var y1 = orientation[1] == 1 ? d[3] : orientation[1] == 0 ? d[3] / 2 : 0;
  239. var y2 = orientation[1] == -1 ? d[3] : orientation[1] == 0 ? d[3] / 2 : 0;
  240. var x1 = orientation[0] == 1 ? d[2] : orientation[0] == 0 ? d[2] / 2 : 0;
  241. var x2 = orientation[0] == -1 ? d[2] : orientation[0] == 0 ? d[2] / 2 : 0;
  242. var g = ctx.createLinearGradient(x1,y1,x2,y2);
  243. for (var i = 0; i < style.gradient.stops.length; i++)
  244. g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]);
  245. ctx.fillStyle = g;
  246. }
  247. ctx.beginPath();
  248. ctx.rect(0, 0, d[2], d[3]);
  249. ctx.closePath();
  250. if (style.fillStyle || style.gradient) ctx.fill();
  251. if (style.strokeStyle) ctx.stroke();
  252. };
  253. };
  254. jsPlumb.Endpoints.canvas.Triangle = function(params) {
  255. var self = this;
  256. jsPlumb.Endpoints.Triangle.apply(this, arguments);
  257. CanvasEndpoint.apply(this, arguments);
  258. this._paint = function(d, style, anchor)
  259. {
  260. var width = d[2], height = d[3], x = d[0], y = d[1],
  261. ctx = self.canvas.getContext('2d'),
  262. offsetX = 0, offsetY = 0, angle = 0,
  263. orientation = anchor.getOrientation(self);
  264. if( orientation[0] == 1 ) {
  265. offsetX = width;
  266. offsetY = height;
  267. angle = 180;
  268. }
  269. if( orientation[1] == -1 ) {
  270. offsetX = width;
  271. angle = 90;
  272. }
  273. if( orientation[1] == 1 ) {
  274. offsetY = height;
  275. angle = -90;
  276. }
  277. ctx.fillStyle = style.fillStyle;
  278. ctx.translate(offsetX, offsetY);
  279. ctx.rotate(angle * Math.PI/180);
  280. ctx.beginPath();
  281. ctx.moveTo(0, 0);
  282. ctx.lineTo(width/2, height/2);
  283. ctx.lineTo(0, height);
  284. ctx.closePath();
  285. if (style.fillStyle || style.gradient) ctx.fill();
  286. if (style.strokeStyle) ctx.stroke();
  287. };
  288. };
  289. /*
  290. * Canvas Image Endpoint: uses the default version, which creates an <img> tag.
  291. */
  292. jsPlumb.Endpoints.canvas.Image = jsPlumb.Endpoints.Image;
  293. /*
  294. * Blank endpoint in all renderers is just the default Blank endpoint.
  295. */
  296. jsPlumb.Endpoints.canvas.Blank = jsPlumb.Endpoints.Blank;
  297. /*
  298. * Canvas Bezier Connector. Draws a Bezier curve onto a Canvas element.
  299. */
  300. jsPlumb.Connectors.canvas.Bezier = function() {
  301. var self = this;
  302. jsPlumb.Connectors.Bezier.apply(this, arguments);
  303. CanvasConnector.apply(this, arguments);
  304. this._paint = function(dimensions) {
  305. self.ctx.beginPath();
  306. self.ctx.moveTo(dimensions[4], dimensions[5]);
  307. self.ctx.bezierCurveTo(dimensions[8], dimensions[9], dimensions[10], dimensions[11], dimensions[6], dimensions[7]);
  308. self.ctx.stroke();
  309. };
  310. // TODO i doubt this handles the case that source and target are swapped.
  311. this.createGradient = function(dim, ctx, swap) {
  312. return /*(swap) ? self.ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]) : */self.ctx.createLinearGradient(dim[6], dim[7], dim[4], dim[5]);
  313. };
  314. };
  315. /*
  316. * Canvas straight line Connector. Draws a straight line onto a Canvas element.
  317. */
  318. jsPlumb.Connectors.canvas.Straight = function() {
  319. var self = this;
  320. jsPlumb.Connectors.Straight.apply(this, arguments);
  321. CanvasConnector.apply(this, arguments);
  322. this._paint = function(dimensions) {
  323. self.ctx.beginPath();
  324. self.ctx.moveTo(dimensions[4], dimensions[5]);
  325. self.ctx.lineTo(dimensions[6], dimensions[7]);
  326. self.ctx.stroke();
  327. };
  328. // TODO this does not handle the case that src and target are swapped.
  329. this.createGradient = function(dim, ctx) {
  330. return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]);
  331. };
  332. };
  333. jsPlumb.Connectors.canvas.Flowchart = function() {
  334. var self = this;
  335. jsPlumb.Connectors.Flowchart.apply(this, arguments);
  336. CanvasConnector.apply(this, arguments);
  337. this._paint = function(dimensions) {
  338. self.ctx.beginPath();
  339. self.ctx.moveTo(dimensions[4], dimensions[5]);
  340. // loop through extra points
  341. for (var i = 0; i < dimensions[8]; i++) {
  342. self.ctx.lineTo(dimensions[9 + (i*2)], dimensions[10 + (i*2)]);
  343. }
  344. // finally draw a line to the end
  345. self.ctx.lineTo(dimensions[6], dimensions[7]);
  346. self.ctx.stroke();
  347. };
  348. this.createGradient = function(dim, ctx) {
  349. return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]);
  350. };
  351. };
  352. // ********************************* END OF CANVAS RENDERERS *******************************************************************
  353. jsPlumb.Overlays.canvas.Label = jsPlumb.Overlays.Label;
  354. /**
  355. * a placeholder right now, really just exists to mirror the fact that there are SVG and VML versions of this.
  356. */
  357. var CanvasOverlay = function() {
  358. jsPlumb.jsPlumbUIComponent.apply(this, arguments);
  359. };
  360. var AbstractCanvasArrowOverlay = function(superclass, originalArgs) {
  361. superclass.apply(this, originalArgs);
  362. CanvasOverlay.apply(this, arguments);
  363. this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) {
  364. var ctx = connector.ctx;
  365. ctx.lineWidth = lineWidth;
  366. ctx.beginPath();
  367. ctx.moveTo(d.hxy.x, d.hxy.y);
  368. ctx.lineTo(d.tail[0].x, d.tail[0].y);
  369. ctx.lineTo(d.cxy.x, d.cxy.y);
  370. ctx.lineTo(d.tail[1].x, d.tail[1].y);
  371. ctx.lineTo(d.hxy.x, d.hxy.y);
  372. ctx.closePath();
  373. if (strokeStyle) {
  374. ctx.strokeStyle = strokeStyle;
  375. ctx.stroke();
  376. }
  377. if (fillStyle) {
  378. ctx.fillStyle = fillStyle;
  379. ctx.fill();
  380. }
  381. };
  382. };
  383. jsPlumb.Overlays.canvas.Arrow = function() {
  384. AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]);
  385. };
  386. jsPlumb.Overlays.canvas.PlainArrow = function() {
  387. AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]);
  388. };
  389. jsPlumb.Overlays.canvas.Diamond = function() {
  390. AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]);
  391. };
  392. })();