jsPlumb-renderers-svg-1.3.0-RC1.js 12 KB

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