jsPlumb-defaults-1.2.3-RC1.js 31 KB

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