jsPlumb-renderers-svg-1.3.2-RC1.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401
  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 SVG 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. /**
  19. * SVG support for jsPlumb.
  20. *
  21. * things to investigate:
  22. *
  23. * gradients: https://developer.mozilla.org/en/svg_in_html_introduction
  24. * css:http://tutorials.jenkov.com/svg/svg-and-css.html
  25. * text on a path: http://www.w3.org/TR/SVG/text.html#TextOnAPath
  26. * pointer events: https://developer.mozilla.org/en/css/pointer-events
  27. *
  28. */
  29. ;(function() {
  30. var svgAttributeMap = {
  31. "stroke-linejoin":"stroke-linejoin",
  32. "joinstyle":"stroke-linejoin",
  33. "stroke-dashoffset":"stroke-dashoffset"
  34. };
  35. var ns = {
  36. svg:"http://www.w3.org/2000/svg",
  37. xhtml:"http://www.w3.org/1999/xhtml"
  38. },
  39. _attr = function(node, attributes) {
  40. for (var i in attributes)
  41. node.setAttribute(i, "" + attributes[i]);
  42. },
  43. _node = function(name, attributes) {
  44. var n = document.createElementNS(ns.svg, name);
  45. attributes = attributes || {};
  46. attributes["version"] = "1.1";
  47. attributes["xmnls"] = ns.xhtml;
  48. _attr(n, attributes);
  49. return n;
  50. },
  51. _pos = function(d) { return "position:absolute;left:" + d[0] + "px;top:" + d[1] + "px"; },
  52. _convertStyle = function(s, ignoreAlpha) {
  53. var o = s,
  54. pad = function(n) { return n.length == 1 ? "0" + n : n; },
  55. hex = function(k) { return pad(Number(k).toString(16)); },
  56. pattern = /(rgb[a]?\()(.*)(\))/;
  57. if (s.match(pattern)) {
  58. var parts = s.match(pattern)[2].split(",");
  59. o = "#" + hex(parts[0]) + hex(parts[1]) + hex(parts[2]);
  60. if (!ignoreAlpha && parts.length == 4)
  61. o = o + hex(parts[3]);
  62. }
  63. return o;
  64. },
  65. _clearGradient = function(parent) {
  66. for (var i = 0; i < parent.childNodes.length; i++) {
  67. if (parent.childNodes[i].tagName == "linearGradient" || parent.childNodes[i].tagName == "radialGradient")
  68. parent.removeChild(parent.childNodes[i]);
  69. }
  70. },
  71. _updateGradient = function(parent, node, style, dimensions) {
  72. var id = "jsplumb_gradient_" + (new Date()).getTime();
  73. // first clear out any existing gradient
  74. _clearGradient(parent);
  75. // this checks for an 'offset' property in the gradient, and in the absence of it, assumes
  76. // we want a linear gradient. if it's there, we create a radial gradient.
  77. // it is possible that a more explicit means of defining the gradient type would be
  78. // better. relying on 'offset' means that we can never have a radial gradient that uses
  79. // some default offset, for instance.
  80. if (!style.gradient.offset) {
  81. var g = _node("linearGradient", {id:id});
  82. parent.appendChild(g);
  83. }
  84. else {
  85. var g = _node("radialGradient", {
  86. id:id
  87. });
  88. parent.appendChild(g);
  89. }
  90. // the svg radial gradient seems to treat stops in the reverse
  91. // order to how canvas does it. so we want to keep all the maths the same, but
  92. // iterate the actual style declarations in reverse order, if the x indexes are not in order.
  93. for (var i = 0; i < style.gradient.stops.length; i++) {
  94. // Straight Connectors and Bezier connectors act slightly differently; this code is a bit of a kludge. but next version of
  95. // jsplumb will be replacing both Straight and Bezier to be generic instances of 'Connector', which has a list of segments.
  96. // so, not too concerned about leaving this in for now.
  97. var styleToUse = i;
  98. if (dimensions.length == 8)
  99. styleToUse = dimensions[4] < dimensions[6] ? i: style.gradient.stops.length - 1 - i;
  100. else
  101. styleToUse = dimensions[4] < dimensions[6] ? style.gradient.stops.length - 1 - i : i;
  102. var stopColor = _convertStyle(style.gradient.stops[styleToUse][1], true);
  103. var s = _node("stop", {"offset":Math.floor(style.gradient.stops[i][0] * 100) + "%", "stop-color":stopColor});
  104. g.appendChild(s);
  105. }
  106. var applyGradientTo = style.strokeStyle ? "stroke" : "fill";
  107. node.setAttribute("style", applyGradientTo + ":url(#" + id + ")");
  108. },
  109. _applyStyles = function(parent, node, style, dimensions) {
  110. if (style.gradient) {
  111. _updateGradient(parent, node, style, dimensions);
  112. }
  113. else {
  114. // make sure we clear any existing gradient
  115. _clearGradient(parent);
  116. node.setAttribute("style", "");
  117. }
  118. node.setAttribute("fill", style.fillStyle ? _convertStyle(style.fillStyle, true) : "none");
  119. node.setAttribute("stroke", style.strokeStyle ? _convertStyle(style.strokeStyle, true) : "none");
  120. if (style.lineWidth) {
  121. node.setAttribute("stroke-width", style.lineWidth);
  122. }
  123. // in SVG there is a stroke-dasharray attribute we can set, and its syntax looks like
  124. // the syntax in VML but is actually kind of nasty: values are given in the pixel
  125. // coordinate space, whereas in VML they are multiples of the width of the stroked
  126. // line, which makes a lot more sense. for that reason, jsPlumb is supporting both
  127. // the native svg 'stroke-dasharray' attribute, and also the 'dashstyle' concept from
  128. // VML, which will be the preferred method. the code below this converts a dashstyle
  129. // attribute given in terms of stroke width into a pixel representation, by using the
  130. // stroke's lineWidth.
  131. if(style["stroke-dasharray"]) {
  132. node.setAttribute("stroke-dasharray", style["stroke-dasharray"]);
  133. }
  134. if (style["dashstyle"] && style["lineWidth"]) {
  135. var sep = style["dashstyle"].indexOf(",") == -1 ? " " : ",",
  136. parts = style["dashstyle"].split(sep),
  137. styleToUse = "";
  138. parts.forEach(function(p) {
  139. styleToUse += (Math.floor(p * style.lineWidth) + sep);
  140. });
  141. node.setAttribute("stroke-dasharray", styleToUse);
  142. }
  143. // extra attributes such as join type, dash offset.
  144. for (var i in svgAttributeMap) {
  145. if (style[i]) {
  146. node.setAttribute(svgAttributeMap[i], style[i]);
  147. }
  148. }
  149. },
  150. _decodeFont = function(f) {
  151. var r = /([0-9].)(p[xt])\s(.*)/;
  152. var bits = f.match(r);
  153. return {size:bits[1] + bits[2], font:bits[3]};
  154. };
  155. /*
  156. * Base class for SVG components.
  157. */
  158. var SvgComponent = function(cssClass, originalArgs, pointerEventsSpec) {
  159. var self = this;
  160. pointerEventsSpec = pointerEventsSpec || "all";
  161. jsPlumb.jsPlumbUIComponent.apply(this, originalArgs);
  162. self.canvas = null, self.path = null, self.svg = null;
  163. this.setHover = function() { };
  164. self.canvas = document.createElement("div");
  165. self.canvas.style["position"] = "absolute";
  166. jsPlumb.sizeCanvas(self.canvas,0,0,1,1);
  167. var clazz = cssClass + " " + (originalArgs[0].cssClass || "");
  168. self.canvas.className = clazz;
  169. self.svg = _node("svg", {
  170. "style":"",
  171. "width":0,
  172. "height":0,
  173. "pointer-events":pointerEventsSpec/*,
  174. "class": clazz*/
  175. });
  176. jsPlumb.appendElement(self.canvas, originalArgs[0]["parent"]);
  177. self.canvas.appendChild(self.svg);
  178. // TODO this displayElement stuff is common between all components, across all
  179. // renderers. would be best moved to jsPlumbUIComponent.
  180. var displayElements = [ self.canvas ];
  181. this.getDisplayElements = function() {
  182. return displayElements;
  183. };
  184. this.appendDisplayElement = function(el) {
  185. displayElements.push(el);
  186. };
  187. this.paint = function(d, style, anchor) {
  188. if (style != null) {
  189. jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]);
  190. _attr(self.svg, {
  191. "style":_pos([0,0,d[2], d[3]]),
  192. "width": d[2],
  193. "height": d[3]
  194. });
  195. self._paint.apply(this, arguments);
  196. }
  197. };
  198. };
  199. /*
  200. * Base class for SVG connectors.
  201. */
  202. var SvgConnector = function(params) {
  203. var self = this;
  204. SvgComponent.apply(this, [ params["_jsPlumb"].connectorClass, arguments, "none" ]);
  205. this._paint = function(d, style) {
  206. var p = self.getPath(d), a = { "d":p }, outlineStyle = null;
  207. a["pointer-events"] = "all";
  208. // outline style. actually means drawing an svg object underneath the main one.
  209. if (style.outlineColor) {
  210. var outlineWidth = style.outlineWidth || 1,
  211. outlineStrokeWidth = style.lineWidth + (2 * outlineWidth);
  212. outlineStyle = {
  213. strokeStyle:_convertStyle(style.outlineColor),
  214. lineWidth:outlineStrokeWidth
  215. };
  216. if (self.bgPath == null) {
  217. self.bgPath = _node("path", a);
  218. self.svg.appendChild(self.bgPath);
  219. self.attachListeners(self.bgPath, self);
  220. }
  221. else {
  222. _attr(self.bgPath, a);
  223. }
  224. _applyStyles(self.svg, self.bgPath, outlineStyle, d);
  225. }
  226. if (self.path == null) {
  227. self.path = _node("path", a);
  228. self.svg.appendChild(self.path);
  229. self.attachListeners(self.path, self);
  230. }
  231. else {
  232. _attr(self.path, a);
  233. }
  234. _applyStyles(self.svg, self.path, style, d);
  235. };
  236. };
  237. /*
  238. * SVG Bezier Connector
  239. */
  240. jsPlumb.Connectors.svg.Bezier = function(params) {
  241. jsPlumb.Connectors.Bezier.apply(this, arguments);
  242. SvgConnector.apply(this, arguments);
  243. this.getPath = function(d) { return "M " + d[4] + " " + d[5] + " C " + d[8] + " " + d[9] + " " + d[10] + " " + d[11] + " " + d[6] + " " + d[7]; };
  244. };
  245. /*
  246. * SVG straight line Connector
  247. */
  248. jsPlumb.Connectors.svg.Straight = function(params) {
  249. jsPlumb.Connectors.Straight.apply(this, arguments);
  250. SvgConnector.apply(this, arguments);
  251. this.getPath = function(d) { return "M " + d[4] + " " + d[5] + " L " + d[6] + " " + d[7]; };
  252. };
  253. jsPlumb.Connectors.svg.Flowchart = function() {
  254. var self = this;
  255. jsPlumb.Connectors.Flowchart.apply(this, arguments);
  256. SvgConnector.apply(this, arguments);
  257. this.getPath = function(dimensions) {
  258. var p = "M " + dimensions[4] + "," + dimensions[5];
  259. // loop through extra points
  260. for (var i = 0; i < dimensions[8]; i++) {
  261. p = p + " L " + dimensions[9 + (i*2)] + " " + dimensions[10 + (i*2)];
  262. }
  263. // finally draw a line to the end
  264. p = p + " " + dimensions[6] + "," + dimensions[7];
  265. return p;
  266. };
  267. };
  268. /*
  269. * Base class for SVG endpoints.
  270. */
  271. var SvgEndpoint = function(params) {
  272. var self = this;
  273. SvgComponent.apply(this, [ params["_jsPlumb"].endpointClass, arguments, "all" ]);
  274. this._paint = function(d, style) {
  275. var s = jsPlumb.extend({}, style);
  276. if (s.outlineColor) {
  277. s.strokeWidth = s.outlineWidth;
  278. s.strokeStyle = _convertStyle(s.outlineColor, true);
  279. }
  280. if (self.node == null) {
  281. self.node = self.makeNode(d, s);
  282. self.svg.appendChild(self.node);
  283. self.attachListeners(self.node, self);
  284. }
  285. _applyStyles(self.svg, self.node, s, d);
  286. _pos(self.node, d);
  287. };
  288. };
  289. /*
  290. * SVG Dot Endpoint
  291. */
  292. jsPlumb.Endpoints.svg.Dot = function() {
  293. jsPlumb.Endpoints.Dot.apply(this, arguments);
  294. SvgEndpoint.apply(this, arguments);
  295. this.makeNode = function(d, style) {
  296. return _node("circle", {
  297. "cx" : d[2] / 2,
  298. "cy" : d[3] / 2,
  299. "r" : d[2] / 2
  300. });
  301. };
  302. };
  303. /*
  304. * SVG Rectangle Endpoint
  305. */
  306. jsPlumb.Endpoints.svg.Rectangle = function() {
  307. jsPlumb.Endpoints.Rectangle.apply(this, arguments);
  308. SvgEndpoint.apply(this, arguments);
  309. this.makeNode = function(d, style) {
  310. return _node("rect", {
  311. "width":d[2],
  312. "height":d[3]
  313. });
  314. };
  315. };
  316. /*
  317. * SVG Image Endpoint is the default image endpoint.
  318. */
  319. jsPlumb.Endpoints.svg.Image = jsPlumb.Endpoints.Image;
  320. /*
  321. * Blank endpoint in svg renderer is the default Blank endpoint.
  322. */
  323. jsPlumb.Endpoints.svg.Blank = jsPlumb.Endpoints.Blank;
  324. /*
  325. * Label endpoint in svg renderer is the default Label endpoint.
  326. */
  327. jsPlumb.Overlays.svg.Label = jsPlumb.Overlays.Label;
  328. var AbstractSvgArrowOverlay = function(superclass, originalArgs) {
  329. superclass.apply(this, originalArgs);
  330. jsPlumb.jsPlumbUIComponent.apply(this, originalArgs);
  331. var self = this, path =null;
  332. this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) {
  333. if (path == null) {
  334. path = _node("path");
  335. connector.svg.appendChild(path);
  336. self.attachListeners(path, connector);
  337. self.attachListeners(path, self);
  338. }
  339. _attr(path, {
  340. "d" : makePath(d),
  341. stroke : strokeStyle ? strokeStyle : null,
  342. fill : fillStyle ? fillStyle : null
  343. });
  344. };
  345. var makePath = function(d) {
  346. return "M" + d.hxy.x + "," + d.hxy.y +
  347. " L" + d.tail[0].x + "," + d.tail[0].y +
  348. " L" + d.cxy.x + "," + d.cxy.y +
  349. " L" + d.tail[1].x + "," + d.tail[1].y +
  350. " L" + d.hxy.x + "," + d.hxy.y;
  351. };
  352. };
  353. jsPlumb.Overlays.svg.Arrow = function() {
  354. AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]);
  355. };
  356. jsPlumb.Overlays.svg.PlainArrow = function() {
  357. AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]);
  358. };
  359. jsPlumb.Overlays.svg.Diamond = function() {
  360. AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]);
  361. };
  362. })();