jsPlumb-defaults-1.2.6-RC1.js 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813
  1. /*
  2. * jsPlumb-defaults-1.2.6-RC1
  3. *
  4. * This script contains the default Anchors, Endpoints, Connectors and Overlays for jsPlumb. It should be used with jsPlumb 1.1.0 and above;
  5. * prior to version 1.1.0 of jsPlumb the defaults were included inside the main script.
  6. *
  7. * NOTE: for production usage you should use jsPlumb-all-x.x.x-min.js, which contains the main jsPlumb script and this script together,
  8. * in a minified file.
  9. *
  10. * Dual licensed under MIT and GPL2.
  11. */
  12. (function() {
  13. var ie = !!!document.createElement('canvas').getContext;
  14. /**
  15. * Places you can anchor a connection to. These are helpers for common locations; they all just return an instance
  16. * of Anchor that has been configured appropriately.
  17. *
  18. * You can write your own one of these; you
  19. * just need to provide a 'compute' method and an 'orientation'. so you'd say something like this:
  20. *
  21. * jsPlumb.Anchors.MY_ANCHOR = {
  22. * compute : function(xy, wh, txy, twh) { return some mathematics on those variables; },
  23. * getOrientation : function() { return [ox, oy]; }
  24. * };
  25. *
  26. * compute takes the [x,y] position of the top left corner of the anchored element,
  27. * and the element's [width,height] (all in pixels), as well as the location and dimension of the element it's plumbed to,
  28. * and returns where the anchor should be located.
  29. *
  30. * the 'orientation' array (returned here as [ox,oy]) indicates the general direction a connection from the anchor
  31. * should go in, if possible. it is an [x,y] matrix where a value of 0 means no preference,
  32. * -1 means go in a negative direction for the given axis, and 1 means go in a positive
  33. * direction. so consider a TopCenter anchor: the orientation matrix for it is [0,-1],
  34. * meaning connections naturally want to go upwards on screen. in a Bezier implementation, for example,
  35. * the curve would start out going in that direction, before bending towards the target anchor.
  36. */
  37. var _curryAnchor = function(x,y,ox,oy) {
  38. return function() {
  39. return jsPlumb.makeAnchor(x,y,ox,oy);
  40. };
  41. };
  42. jsPlumb.Anchors["TopCenter"] = _curryAnchor(0.5, 0, 0,-1);
  43. jsPlumb.Anchors["BottomCenter"] = _curryAnchor(0.5, 1, 0, 1);
  44. jsPlumb.Anchors["LeftMiddle"] = _curryAnchor(0, 0.5, -1, 0);
  45. jsPlumb.Anchors["RightMiddle"] = _curryAnchor(1, 0.5, 1, 0);
  46. jsPlumb.Anchors["Center"] = _curryAnchor(0.5, 0.5, 0, 0);
  47. jsPlumb.Anchors["TopRight"] = _curryAnchor(1, 0, 0,-1);
  48. jsPlumb.Anchors["BottomRight"] = _curryAnchor(1, 1, 0, 1);
  49. jsPlumb.Anchors["TopLeft"] = _curryAnchor(0, 0, 0, -1);
  50. jsPlumb.Anchors["BottomLeft"] = _curryAnchor(0, 1, 0, 1);
  51. jsPlumb.Defaults.DynamicAnchors = function() {
  52. return jsPlumb.makeAnchors(["TopCenter", "RightMiddle", "BottomCenter", "LeftMiddle"]);
  53. };
  54. jsPlumb.Anchors["AutoDefault"] = function() { return jsPlumb.makeDynamicAnchor(jsPlumb.Defaults.DynamicAnchors()); };
  55. /**
  56. * The Straight connector draws a simple straight line between the two anchor points.
  57. */
  58. jsPlumb.Connectors.Straight = function() {
  59. var self = this;
  60. var currentPoints = null;
  61. var _m, _m2, _b, _dx, _dy, _theta, _theta2, _sx, _sy, _tx, _ty;
  62. /**
  63. * Computes the new size and position of the canvas.
  64. * @param sourceAnchor Absolute position on screen of the source object's anchor.
  65. * @param targetAnchor Absolute position on screen of the target object's anchor.
  66. * @param positionMatrix Indicates the relative positions of the left,top of the
  67. * two plumbed objects. so [0,0] indicates that the source is to the left of, and
  68. * above, the target. [1,0] means the source is to the right and above. [0,1] means
  69. * the source is to the left and below. [1,1] means the source is to the right
  70. * and below. this is used to figure out which direction to draw the connector in.
  71. * @returns an array of positioning information. the first two values are
  72. * the [left, top] absolute position the canvas should be placed on screen. the
  73. * next two values are the [width,height] the canvas should be. after that each
  74. * Connector can put whatever it likes into the array:it will be passed back in
  75. * to the paint call. This particular function stores the origin and destination of
  76. * the line it is going to draw. a more involved implementation, like a Bezier curve,
  77. * would store the control point info in this array too.
  78. */
  79. this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth) {
  80. var w = Math.abs(sourcePos[0] - targetPos[0]);
  81. var h = Math.abs(sourcePos[1] - targetPos[1]);
  82. var widthAdjusted = false, heightAdjusted = false;
  83. // these are padding to ensure the whole connector line appears
  84. var xo = 0.45 * w, yo = 0.45 * h;
  85. // these are padding to ensure the whole connector line appears
  86. w *= 1.9; h *=1.9;
  87. var x = Math.min(sourcePos[0], targetPos[0]) - xo;
  88. var y = Math.min(sourcePos[1], targetPos[1]) - yo;
  89. // minimum size is 2 * line Width if minWidth was not given.
  90. var calculatedMinWidth = Math.max(2 * lineWidth, minWidth);
  91. if (w < calculatedMinWidth) {
  92. w = calculatedMinWidth;
  93. x = sourcePos[0] + ((targetPos[0] - sourcePos[0]) / 2) - (calculatedMinWidth / 2);
  94. xo = (w - Math.abs(sourcePos[0]-targetPos[0])) / 2;
  95. }
  96. if (h < calculatedMinWidth) {
  97. h = calculatedMinWidth;
  98. y = sourcePos[1] + ((targetPos[1] - sourcePos[1]) / 2) - (calculatedMinWidth / 2);
  99. yo = (h - Math.abs(sourcePos[1]-targetPos[1])) / 2;
  100. }
  101. _sx = sourcePos[0] < targetPos[0] ? xo : w-xo;
  102. _sy = sourcePos[1] < targetPos[1] ? yo:h-yo;
  103. _tx = sourcePos[0] < targetPos[0] ? w-xo : xo;
  104. _ty = sourcePos[1] < targetPos[1] ? h-yo : yo;
  105. currentPoints = [ x, y, w, h, _sx, _sy, _tx, _ty ];
  106. _dx = _tx - _sx, _dy = (_ty - _sy);
  107. _m = _dy / _dx, _m2 = -1 / _m;
  108. _b = -1 * ((_m * _sx) - _sy);
  109. _theta = Math.atan(_m); _theta2 = Math.atan(_m2);
  110. return currentPoints;
  111. };
  112. this.paint = function(dimensions, ctx)
  113. {
  114. ctx.beginPath();
  115. ctx.moveTo(dimensions[4], dimensions[5]);
  116. ctx.lineTo(dimensions[6], dimensions[7]);
  117. ctx.stroke();
  118. };
  119. /**
  120. * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from
  121. * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much.
  122. */
  123. this.pointOnPath = function(location) {
  124. var xp = _sx + (location * _dx);
  125. var yp = (_m == Infinity || _m == -Infinity) ? _sy + (location * (_ty - _sy)) : (_m * xp) + _b;
  126. return {x:xp, y:yp};
  127. };
  128. /**
  129. * returns the gradient of the connector at the given point - which for us is constant.
  130. */
  131. this.gradientAtPoint = function(location) { return _m; };
  132. /**
  133. * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where
  134. * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels.
  135. */
  136. this.pointAlongPathFrom = function(location, distance) {
  137. var p = self.pointOnPath(location);
  138. var orientation = distance > 0 ? 1 : -1;
  139. var y = Math.abs(distance * Math.sin(_theta));
  140. if (_sy > _ty) y = y * -1;
  141. var x = Math.abs(distance * Math.cos(_theta));
  142. if (_sx > _tx) x = x * -1;
  143. return {x:p.x + (orientation * x), y:p.y + (orientation * y)};
  144. };
  145. /**
  146. * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location.
  147. * the line is 'length' pixels long.
  148. */
  149. this.perpendicularToPathAt = function(location, length, distance) {
  150. var p = self.pointAlongPathFrom(location, distance);
  151. var m = self.gradientAtPoint(p.location);
  152. var _theta2 = Math.atan(-1 / m);
  153. var y = length / 2 * Math.sin(_theta2);
  154. var x = length / 2 * Math.cos(_theta2);
  155. return [{x:p.x + x, y:p.y + y}, {x:p.x - x, y:p.y - y}];
  156. };
  157. this.createGradient = function(dim, ctx) {
  158. return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]);
  159. };
  160. };
  161. /**
  162. * This Connector draws a Bezier curve with two control points.
  163. *
  164. * @param curviness How 'curvy' you want the curve to be! This is a directive for the
  165. * placement of control points, not endpoints of the curve, so your curve does not
  166. * actually touch the given point, but it has the tendency to lean towards it. the larger
  167. * this value, the greater the curve is pulled from a straight line.
  168. *
  169. * note that the method signature changed in 1.2.6 to take a params object, so the method
  170. * argument was renamed. you can still provide just an integer to this constructor, though the
  171. * preferred method is to use {curviness:XXX}.
  172. *
  173. * a future implementation of this could take the control points as arguments, rather
  174. * than fixing the curve to one basic shape.
  175. */
  176. jsPlumb.Connectors.Bezier = function(params) {
  177. var self = this;
  178. this.majorAnchor = 150;
  179. // backwards compatibility (ideally we'd just use params.curviness || 150).
  180. if (params) {
  181. if (params.constructor == Number) this.majorAnchor = params;
  182. else if (params.curviness) this.majorAnchor = params.curviness;
  183. }
  184. this.minorAnchor = 10;
  185. var currentPoints = null;
  186. this._findControlPoint = function(point, sourceAnchorPosition, targetAnchorPosition, sourceAnchor, targetAnchor) {
  187. // determine if the two anchors are perpendicular to each other in their orientation. we swap the control
  188. // points around if so (code could be tightened up)
  189. var soo = sourceAnchor.getOrientation(), too = targetAnchor.getOrientation();
  190. var perpendicular = soo[0] != too[0] || soo[1] == too[1];
  191. var p = [];
  192. var ma = self.majorAnchor, mi = self.minorAnchor;
  193. if (!perpendicular) {
  194. if (soo[0] == 0) // X
  195. p.push(sourceAnchorPosition[0] < targetAnchorPosition[0] ? point[0] + mi : point[0] - mi);
  196. else p.push(point[0] - (ma * soo[0]));
  197. if (soo[1] == 0) // Y
  198. p.push(sourceAnchorPosition[1] < targetAnchorPosition[1] ? point[1] + mi : point[1] - mi);
  199. else p.push(point[1] + (ma * too[1]));
  200. }
  201. else {
  202. if (too[0] == 0) // X
  203. p.push(targetAnchorPosition[0] < sourceAnchorPosition[0] ? point[0] + mi : point[0] - mi);
  204. else p.push(point[0] + (ma * too[0]));
  205. if (too[1] == 0) // Y
  206. p.push(targetAnchorPosition[1] < sourceAnchorPosition[1] ? point[1] + mi : point[1] - mi);
  207. else p.push(point[1] + (ma * soo[1]));
  208. }
  209. return p;
  210. };
  211. var _CP, _CP2, _sx, _tx, _ty, _sx, _sy, _canvasX, _canvasY, _w, _h;
  212. this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth)
  213. {
  214. lineWidth = lineWidth || 0;
  215. _w = Math.abs(sourcePos[0] - targetPos[0]) + lineWidth;
  216. _h = Math.abs(sourcePos[1] - targetPos[1]) + lineWidth;
  217. _canvasX = Math.min(sourcePos[0], targetPos[0])-(lineWidth/2);
  218. _canvasY = Math.min(sourcePos[1], targetPos[1])-(lineWidth/2);
  219. _sx = sourcePos[0] < targetPos[0] ? _w - (lineWidth/2): (lineWidth/2);
  220. _sy = sourcePos[1] < targetPos[1] ? _h - (lineWidth/2) : (lineWidth/2);
  221. _tx = sourcePos[0] < targetPos[0] ? (lineWidth/2) : _w - (lineWidth/2);
  222. _ty = sourcePos[1] < targetPos[1] ? (lineWidth/2) : _h - (lineWidth/2);
  223. _CP = self._findControlPoint([_sx,_sy], sourcePos, targetPos, sourceAnchor, targetAnchor);
  224. _CP2 = self._findControlPoint([_tx,_ty], targetPos, sourcePos, targetAnchor, sourceAnchor);
  225. var minx1 = Math.min(_sx,_tx); var minx2 = Math.min(_CP[0], _CP2[0]); var minx = Math.min(minx1,minx2);
  226. var maxx1 = Math.max(_sx,_tx); var maxx2 = Math.max(_CP[0], _CP2[0]); var maxx = Math.max(maxx1,maxx2);
  227. if (maxx > _w) _w = maxx;
  228. if (minx < 0) {
  229. _canvasX += minx; var ox = Math.abs(minx);
  230. _w += ox; _CP[0] += ox; _sx += ox; _tx +=ox; _CP2[0] += ox;
  231. }
  232. var miny1 = Math.min(_sy,_ty); var miny2 = Math.min(_CP[1], _CP2[1]); var miny = Math.min(miny1,miny2);
  233. var maxy1 = Math.max(_sy,_ty); var maxy2 = Math.max(_CP[1], _CP2[1]); var maxy = Math.max(maxy1,maxy2);
  234. if (maxy > _h) _h = maxy;
  235. if (miny < 0) {
  236. _canvasY += miny; var oy = Math.abs(miny);
  237. _h += oy; _CP[1] += oy; _sy += oy; _ty +=oy; _CP2[1] += oy;
  238. }
  239. if (minWidth && _w < minWidth) {
  240. var posAdjust = (minWidth - _w) / 2;
  241. _w = minWidth;
  242. _canvasX -= posAdjust; _sx = _sx + posAdjust ; _tx = _tx + posAdjust; _CP[0] = _CP[0] + posAdjust; _CP2[0] = _CP2[0] + posAdjust;
  243. }
  244. if (minWidth && _h < minWidth) {
  245. var posAdjust = (minWidth - _h) / 2;
  246. _h = minWidth;
  247. _canvasY -= posAdjust; _sy = _sy + posAdjust ; _ty = _ty + posAdjust; _CP[1] = _CP[1] + posAdjust; _CP2[1] = _CP2[1] + posAdjust;
  248. }
  249. currentPoints = [_canvasX, _canvasY, _w, _h, _sx, _sy, _tx, _ty, _CP[0], _CP[1], _CP2[0], _CP2[1] ];
  250. return currentPoints;
  251. };
  252. this.paint = function(d, ctx) {
  253. ctx.beginPath();
  254. ctx.moveTo(d[4],d[5]);
  255. ctx.bezierCurveTo(d[8],d[9],d[10],d[11],d[6],d[7]);
  256. ctx.stroke();
  257. };
  258. var _makeCurve = function() {
  259. return [
  260. { x:_sx, y:_sy },
  261. { x:_CP[0], y:_CP[1] },
  262. { x:_CP2[0], y:_CP2[1] },
  263. { x:_tx, y:_ty }
  264. ];
  265. };
  266. /**
  267. * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from
  268. * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much.
  269. */
  270. this.pointOnPath = function(location) {
  271. return jsBezier.pointOnCurve(_makeCurve(), location);
  272. };
  273. /**
  274. * returns the gradient of the connector at the given point.
  275. */
  276. this.gradientAtPoint = function(location) {
  277. return jsBezier.gradientAtPoint(_makeCurve(), location);
  278. };
  279. /**
  280. * for Bezier curves this method is a little tricky, cos calculating path distance algebraically is notoriously difficult.
  281. * this method is iterative, jumping forward .05% of the path at a time and summing the distance between this point and the previous
  282. * one, until the sum reaches 'distance'. the method may turn out to be computationally expensive; we'll see.
  283. * another drawback of this method is that if the connector gets quite long, .05% of the length of it is not necessarily smaller
  284. * than the desired distance, in which case the loop returns immediately and the arrow is mis-shapen. so a better strategy might be to
  285. * calculate the step as a function of distance/distance between endpoints.
  286. */
  287. this.pointAlongPathFrom = function(location, distance) {
  288. return jsBezier.pointAlongCurveFrom(_makeCurve(), location, distance);
  289. };
  290. /**
  291. * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location.
  292. * the line is 'length' pixels long.
  293. */
  294. this.perpendicularToPathAt = function(location, length, distance) {
  295. return jsBezier.perpendicularToCurveAt(_makeCurve(), location, length, distance);
  296. };
  297. this.createGradient = function(dim, ctx, swap) {
  298. return (swap) ? ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]) : ctx.createLinearGradient(dim[6], dim[7], dim[4], dim[5]);
  299. };
  300. };
  301. /**
  302. * Types of endpoint UIs. we supply four - a circle of default radius 10px, a rectangle of
  303. * default size 20x20, an image (with no default), and a Triangle, of default size 15.
  304. * you can supply others of these if you want to - see the documentation for a howto.
  305. */
  306. /**
  307. * a round endpoint, with default radius 10 pixels.
  308. */
  309. jsPlumb.Endpoints.Dot = function(params) {
  310. params = params || { radius:10 };
  311. var self = this;
  312. this.radius = params.radius;
  313. var defaultOffset = 0.5 * this.radius;
  314. var defaultInnerRadius = this.radius / 3;
  315. var parseValue = function(value) {
  316. try {
  317. return parseInt(value);
  318. }
  319. catch(e) {
  320. if (value.substring(value.length - 1) == '%')
  321. return parseInt(value.substring(0, value - 1));
  322. }
  323. };
  324. var calculateAdjustments = function(gradient) {
  325. var offsetAdjustment = defaultOffset;
  326. var innerRadius = defaultInnerRadius;
  327. if (gradient.offset) offsetAdjustment = parseValue(gradient.offset);
  328. if(gradient.innerRadius) innerRadius = parseValue(gradient.innerRadius);
  329. return [offsetAdjustment, innerRadius];
  330. };
  331. this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) {
  332. var radius = endpointStyle.radius || self.radius;
  333. var x = anchorPoint[0] - radius;
  334. var y = anchorPoint[1] - radius;
  335. jsPlumb.sizeCanvas(canvas, x, y, radius * 2, radius * 2);
  336. var ctx = canvas.getContext('2d');
  337. var style = jsPlumb.extend({}, endpointStyle);
  338. if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle;
  339. jsPlumb.extend(ctx, style);
  340. if (endpointStyle.gradient && !ie) {
  341. var adjustments = calculateAdjustments(endpointStyle.gradient);
  342. var yAdjust = orientation[1] == 1 ? adjustments[0] * -1 : adjustments[0];
  343. var xAdjust = orientation[0] == 1 ? adjustments[0] * -1: adjustments[0];
  344. var g = ctx.createRadialGradient(radius, radius, radius, radius + xAdjust, radius + yAdjust, adjustments[1]);
  345. for (var i = 0; i < endpointStyle.gradient.stops.length; i++)
  346. g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]);
  347. ctx.fillStyle = g;
  348. }
  349. ctx.beginPath();
  350. ctx.arc(radius, radius, radius, 0, Math.PI*2, true);
  351. ctx.closePath();
  352. ctx.fill();
  353. };
  354. };
  355. /**
  356. * A Rectangular endpoint, with default size 20x20.
  357. */
  358. jsPlumb.Endpoints.Rectangle = function(params) {
  359. params = params || { width:20, height:20 };
  360. var self = this;
  361. this.width = params.width;
  362. this.height = params.height;
  363. this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) {
  364. var width = endpointStyle.width || self.width;
  365. var height = endpointStyle.height || self.height;
  366. var x = anchorPoint[0] - (width/2);
  367. var y = anchorPoint[1] - (height/2);
  368. jsPlumb.sizeCanvas(canvas, x, y, width, height);
  369. var ctx = canvas.getContext('2d');
  370. var style = jsPlumb.extend({}, endpointStyle);
  371. if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle;
  372. jsPlumb.extend(ctx, style);
  373. var ie = (/MSIE/.test(navigator.userAgent) && !window.opera);
  374. if (endpointStyle.gradient && !ie) {
  375. // first figure out which direction to run the gradient in (it depends on the orientation of the anchors)
  376. var y1 = orientation[1] == 1 ? height : orientation[1] == 0 ? height / 2 : 0;
  377. var y2 = orientation[1] == -1 ? height : orientation[1] == 0 ? height / 2 : 0;
  378. var x1 = orientation[0] == 1 ? width : orientation[0] == 0 ? width / 2 : 0;
  379. var x2 = orientation[0] == -1 ? width : orientation[0] == 0 ? height / 2 : 0;
  380. var g = ctx.createLinearGradient(x1,y1,x2,y2);
  381. for (var i = 0; i < endpointStyle.gradient.stops.length; i++)
  382. g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]);
  383. ctx.fillStyle = g;
  384. }
  385. ctx.beginPath();
  386. ctx.rect(0, 0, width, height);
  387. ctx.closePath();
  388. ctx.fill();
  389. };
  390. };
  391. jsPlumb.Endpoints.Triangle = function(params) {
  392. params = params || { width:55, height:55 };
  393. var self = this;
  394. this.width = params.width;
  395. this.height = params.height;
  396. this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle)
  397. {
  398. var width = endpointStyle.width || self.width;
  399. var height = endpointStyle.height || self.height;
  400. var x = anchorPoint[0] - width/2;
  401. var y = anchorPoint[1] - height/2;
  402. jsPlumb.sizeCanvas(canvas, x, y, width, height);
  403. var ctx = canvas.getContext('2d');
  404. var offsetX = 0, offsetY = 0, angle = 0;
  405. if( orientation[0] == 1 )
  406. {
  407. offsetX = width;
  408. offsetY = height;
  409. angle = 180;
  410. }
  411. if( orientation[1] == -1 )
  412. {
  413. offsetX = width;
  414. angle = 90;
  415. }
  416. if( orientation[1] == 1 )
  417. {
  418. offsetY = height;
  419. angle = -90;
  420. }
  421. ctx.fillStyle = endpointStyle.fillStyle;
  422. ctx.translate(offsetX, offsetY);
  423. ctx.rotate(angle * Math.PI/180);
  424. ctx.beginPath();
  425. ctx.moveTo(0, 0);
  426. ctx.lineTo(width/2, height/2);
  427. ctx.lineTo(0, height);
  428. ctx.closePath();
  429. ctx.fill();
  430. };
  431. };
  432. /**
  433. * Image endpoint - draws an image as the endpoint. You must provide a 'url' or, since 1.2.4, a 'src' property in the params object..
  434. */
  435. jsPlumb.Endpoints.Image = function(params) {
  436. var self = this;
  437. this.img = new Image();
  438. var ready = false;
  439. this.img.onload = function() {
  440. self.ready = true;
  441. };
  442. this.img.src = params.src || params.url;
  443. var actuallyPaint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) {
  444. var width = self.img.width || endpointStyle.width;
  445. var height = self.img.height || endpointStyle.height;
  446. var x = anchorPoint[0] - (width/2);
  447. var y = anchorPoint[1] - (height/2);
  448. jsPlumb.sizeCanvas(canvas, x, y, width, height);
  449. var ctx = canvas.getContext('2d');
  450. ctx.drawImage(self.img,0,0);
  451. };
  452. this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) {
  453. if (self.ready) {
  454. actuallyPaint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle);
  455. }
  456. else
  457. window.setTimeout(function() {
  458. self.paint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle);
  459. }, 200);
  460. };
  461. };
  462. /**
  463. * An arrow overlay. you can provide:
  464. *
  465. * length - distance in pixels from head to tail baseline. default 20.
  466. * width - width in pixels of the tail baseline. default 20.
  467. * fillStyle - style to use when filling the arrow. defaults to "black".
  468. * strokeStyle - style to use when stroking the arrow. defaults to null, which means the arrow is not stroked.
  469. * lineWidth - line width to use when stroking the arrow. defaults to 1, but only used if strokeStyle is not null.
  470. * foldback - distance (as a decimal from 0 to 1 inclusive) along the length of the arrow marking the point the tail points should fold back to. defaults to 0.623.
  471. * location - distance (as a decimal from 0 to 1 inclusive) marking where the arrow should sit on the connector. defaults to 0.5.
  472. * direction - indicates the direction the arrow points in. valid values are -1 and 1; 1 is default.
  473. */
  474. jsPlumb.Overlays.Arrow = function(params) {
  475. params = params || {};
  476. var self = this;
  477. this.length = params.length || 20;
  478. this.width = params.width || 20;
  479. this.connection = params.connection;
  480. var direction = (params.direction || 1) < 0 ? -1 : 1;
  481. /*var fillStyle = params.fillStyle;
  482. var strokeStyle = params.strokeStyle;
  483. var lineWidth = params.lineWidth || 1;*/
  484. var paintStyle = params.paintStyle || { lineWidth:1 };
  485. this.loc = params.location == null ? 0.5 : params.location;
  486. // how far along the arrow the lines folding back in come to. default is 62.3%.
  487. var foldback = params.foldback || 0.623;
  488. var _getFoldBackPoint = function(connector, loc) {
  489. if (foldback == 0.5) return connector.pointOnPath(loc);
  490. else {
  491. var adj = 0.5 - foldback; // we calculate relative to the center
  492. return connector.pointAlongPathFrom(loc, direction * self.length * adj);
  493. }
  494. };
  495. this.computeMaxSize = function() { return self.width * 1.5; };
  496. this.draw = function(connector, ctx, currentConnectionPaintStyle) {
  497. // this is the arrow head position
  498. var hxy = connector.pointAlongPathFrom(self.loc, direction * (self.length / 2));
  499. // this is the center of the tail
  500. var txy = connector.pointAlongPathFrom(self.loc, -1 * direction * (self.length / 2)), tx = txy.x, ty = txy.y;
  501. // this is the tail vector
  502. var tail = connector.perpendicularToPathAt(self.loc, self.width, -1 * direction * (self.length / 2));
  503. // this is the point the tail goes in to
  504. var cxy = _getFoldBackPoint(connector, self.loc);
  505. // if loc = 1, then hxy should be flush with the element.
  506. if (self.loc == 1) {
  507. var lxy = connector.pointOnPath(self.loc);
  508. var dx = lxy.x - hxy.x, dy = lxy.y - hxy.y;
  509. cxy.x += dx; cxy.y += dy;
  510. txy.x += dx; txy.y += dy;
  511. tail[0].x += dx; tail[0].y += dy;
  512. tail[1].x += dx; tail[1].y += dy;
  513. hxy.x += dx; hxy.y += dy;
  514. }
  515. // if loc = 0, then tail midpoint should be flush with the element.
  516. if (self.loc == 0) {
  517. var lxy = connector.pointOnPath(self.loc);
  518. var tailMid = foldback > 1 ? cxy : {
  519. x:tail[0].x + ((tail[1].x - tail[0].x) / 2),
  520. y:tail[0].y + ((tail[1].y - tail[0].y) / 2)
  521. };
  522. var dx = lxy.x - tailMid.x, dy = lxy.y - tailMid.y;
  523. cxy.x += dx; cxy.y += dy;
  524. txy.x += dx; txy.y += dy;
  525. tail[0].x += dx; tail[0].y += dy;
  526. tail[1].x += dx; tail[1].y += dy;
  527. hxy.x += dx; hxy.y += dy;
  528. }
  529. var minx = Math.min(hxy.x, tail[0].x, tail[1].x);
  530. var maxx = Math.max(hxy.x, tail[0].x, tail[1].x);
  531. var miny = Math.min(hxy.y, tail[0].y, tail[1].y);
  532. var maxy = Math.max(hxy.y, tail[0].y, tail[1].y);
  533. ctx.lineWidth = paintStyle.lineWidth;
  534. ctx.beginPath();
  535. ctx.moveTo(hxy.x, hxy.y);
  536. ctx.lineTo(tail[0].x, tail[0].y);
  537. ctx.lineTo(cxy.x, cxy.y);
  538. ctx.lineTo(tail[1].x, tail[1].y);
  539. ctx.lineTo(hxy.x, hxy.y);
  540. ctx.closePath();
  541. if (paintStyle.strokeStyle) {
  542. ctx.strokeStyle = paintStyle.strokeStyle;
  543. ctx.stroke();
  544. }
  545. ctx.fillStyle = paintStyle.fillStyle || currentConnectionPaintStyle.strokeStyle;
  546. ctx.fill();
  547. return [ minx, maxx, miny, maxy];
  548. };
  549. };
  550. /**
  551. * a basic arrow. this is in fact just one instance of the more generic case in which the tail folds back on itself to some
  552. * point along the length of the arrow: in this case, that foldback point is the full length of the arrow. so it just does
  553. * a 'call' to Arrow with foldback set appropriately. See Arrow for params.
  554. */
  555. jsPlumb.Overlays.PlainArrow = function(params) {
  556. params = params || {};
  557. var p = jsPlumb.extend(params, {foldback:1});
  558. jsPlumb.Overlays.Arrow.call(this, p);
  559. };
  560. /**
  561. * a diamond. like PlainArrow, this is a concrete case of the more generic case of the tail points converging on some point...it just
  562. * happens that in this case, that point is greater than the length of the the arrow. See Arrow for params.
  563. *
  564. * this could probably do with some help with positioning...due to the way it reuses the Arrow paint code, what Arrow thinks is the
  565. * center is actually 1/4 of the way along for this guy. but we don't have any knowledge of pixels at this point, so we're kind of
  566. * stuck when it comes to helping out the Arrow class. possibly we could pass in a 'transpose' parameter or something. the value
  567. * would be -l/4 in this case - move along one quarter of the total length.
  568. */
  569. jsPlumb.Overlays.Diamond = function(params) {
  570. params = params || {};
  571. var l = params.length || 40;
  572. var p = jsPlumb.extend(params, {length:l/2, foldback:2});
  573. jsPlumb.Overlays.Arrow.call(this, p);
  574. };
  575. /**
  576. * A Label overlay. Params you can provide:
  577. *
  578. * labelStyle - js object containing style instructions for the label. defaults to jsPlumb.Defaults.LabelStyle.
  579. * label - the label to paint. may be a string or a function that returns a string. nothing will be painted if your label is null or your
  580. * label function returns null. empty strings _will_ be painted.
  581. * location - distance (as a decimal from 0 to 1 inclusive) marking where the label should sit on the connector. defaults to 0.5.
  582. * borderWidth - width of a border to paint. defaults to zero.
  583. * borderStyle - strokeStyle to use when painting the border, if necessary.
  584. */
  585. jsPlumb.Overlays.Label = function(params) {
  586. this.labelStyle = params.labelStyle || jsPlumb.Defaults.LabelStyle;
  587. this.label = params.label;
  588. this.connection = params.connection;
  589. var self = this;
  590. var labelWidth = null, labelHeight = null, labelText = null, labelPadding = null;
  591. this.location = params.location || 0.5;
  592. this.cachedDimensions = null; // setting on 'this' rather than using closures uses a lot less memory. just don't monkey with it!
  593. var _textDimensions = function(ctx) {
  594. if (self.cachedDimensions) return self.cachedDimensions; // return cached copy if we can. if we add a setLabel function remember to clear the cache.
  595. labelText = typeof self.label == 'function' ? self.label(self) : self.label;
  596. var d = {};
  597. if (labelText) {
  598. var lines = labelText.split(/\n|\r\n/);
  599. ctx.save();
  600. if (self.labelStyle.font) ctx.font = self.labelStyle.font;
  601. var t = _widestLine(lines, ctx);
  602. // a fake text height measurement: use the width of upper case M
  603. var h = ctx.measureText("M").width;
  604. labelPadding = self.labelStyle.padding || 0.25;
  605. labelWidth = t + (2 * t * labelPadding);
  606. labelHeight = (lines.length * h) + (2 * h * labelPadding);
  607. var textHeight = lines.length * h;
  608. ctx.restore();
  609. d = {width:labelWidth, height:labelHeight, lines:lines, oneLine:h, padding:labelPadding, textHeight:textHeight};
  610. }
  611. if (typeof self.label != 'function') self.cachedDimensions = d; // cache it if we can.
  612. return d;
  613. };
  614. this.computeMaxSize = function(connector, ctx) {
  615. var td = _textDimensions(ctx);
  616. return td.width ? Math.max(td.width, td.height) * 1.5 : 0;
  617. };
  618. var _widestLine = function(lines, ctx) {
  619. var max = 0;
  620. for (var i = 0; i < lines.length; i++) {
  621. var t = ctx.measureText(lines[i]).width;
  622. if (t > max) max = t;
  623. }
  624. return max;
  625. };
  626. this.draw = function(connector, ctx, currentConnectionPaintStyle) {
  627. var td = _textDimensions(ctx);
  628. if (td.width) {
  629. var cxy = connector.pointOnPath(self.location);
  630. if (self.labelStyle.font) ctx.font = self.labelStyle.font;
  631. if (self.labelStyle.fillStyle)
  632. ctx.fillStyle = self.labelStyle.fillStyle;
  633. else
  634. ctx.fillStyle = "rgba(0,0,0,0)";
  635. var minx = cxy.x - (td.width / 2);
  636. var miny = cxy.y - (td.height / 2);
  637. ctx.fillRect(minx, miny , td.width , td.height );
  638. if (self.labelStyle.color) ctx.fillStyle = self.labelStyle.color;
  639. ctx.textBaseline = "middle";
  640. ctx.textAlign = "center";
  641. for (i = 0; i < td.lines.length; i++) {
  642. ctx.fillText(td.lines[i],cxy.x, cxy.y - (td.textHeight / 2) + (td.oneLine/2) + (i*td.oneLine));
  643. }
  644. // border
  645. if (self.labelStyle.borderWidth > 0) {
  646. ctx.strokeStyle = self.labelStyle.borderStyle || "black";
  647. ctx.strokeRect(minx, miny, td.width , td.height );
  648. }
  649. return [minx, minx+td.width, miny, miny+td.height];
  650. }
  651. else return [0,0,0,0];
  652. };
  653. };
  654. /**
  655. * an image overlay. params may contain:
  656. *
  657. * location : proportion along the connector to draw the image. optional.
  658. * src : image src. required.
  659. * events : map of event names to functions; each event listener will be bound to the img.
  660. */
  661. jsPlumb.Overlays.Image = function(params) {
  662. var self = this;
  663. this.location = params.location || 0.5;
  664. this.img = new Image();
  665. this.connection = params.connection;
  666. var imgDiv = null;
  667. var notReadyInterval = null;
  668. var notReadyConnector, notReadyContext;
  669. var events = params.events || {};
  670. var _init = function() {
  671. if (self.ready) {
  672. window.clearInterval(notReadyInterval);
  673. imgDiv = document.createElement("img");
  674. imgDiv.src = self.img.src;
  675. imgDiv.style.position = "absolute";
  676. imgDiv.style.display="none";
  677. imgDiv.className = "_jsPlumb_overlay";
  678. document.body.appendChild(imgDiv);// HMM
  679. // attach events
  680. for (var e in events) {
  681. jsPlumb.CurrentLibrary.bind(imgDiv, e, events[e]);
  682. }
  683. if (notReadyConnector && notReadyContext) {
  684. _draw(notReadyConnector, notReadyContext);
  685. notReadyContext = null;
  686. notReadyConnector = null;
  687. }
  688. }
  689. };
  690. this.img.onload = function() {
  691. self.ready = true;
  692. // jsPlumb.repaintAll();
  693. };
  694. this.img.src = params.src || params.url;
  695. notReadyInterval = window.setInterval(_init, 250);
  696. this.computeMaxSize = function(connector, ctx) {
  697. return [ self.img.width, self.img.height ];
  698. };
  699. var _draw = function(connector, ctx, currentConnectionPaintStyle) {
  700. if (imgDiv != null) {
  701. var cxy = connector.pointOnPath(self.location);
  702. var canvas = jsPlumb.CurrentLibrary.getElementObject(ctx.canvas);
  703. var canvasOffset = jsPlumb.CurrentLibrary.getOffset(canvas);
  704. var minx = cxy.x - (self.img.width/2);
  705. var miny = cxy.y - (self.img.height/2);
  706. var o = {left:canvasOffset.left + minx, top:canvasOffset.top + miny};
  707. jsPlumb.CurrentLibrary.setOffset(imgDiv, o);
  708. imgDiv.style.display = "block";
  709. return [minx,minx + self.img.width, miny, miny+self.img.height];
  710. }
  711. };
  712. this.draw = function(connector, ctx) {
  713. if (self.ready)
  714. return _draw(connector, ctx);
  715. else {
  716. notReadyConnector = connector;
  717. notReadyContext = ctx;
  718. return [0,0,0,0];
  719. }
  720. };
  721. };
  722. })();