jsPlumb-defaults-1.3.2-RC1.js 40 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932
  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 default Connectors, Endpoint and Overlay definitions.
  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. (function() {
  19. /**
  20. *
  21. * Helper class to consume unused mouse events by components that are DOM elements and
  22. * are used by all of the different rendering modes.
  23. *
  24. */
  25. jsPlumb.DOMElementComponent = function(params) {
  26. jsPlumb.jsPlumbUIComponent.apply(this, arguments);
  27. // when render mode is canvas, these functions may be called by the canvas mouse handler.
  28. // this component is safe to pipe this stuff to /dev/null.
  29. this.mousemove =
  30. this.dblclick =
  31. this.click =
  32. this.mousedown =
  33. this.mouseup = function(e) { };
  34. };
  35. /**
  36. * Class: Connectors.Straight
  37. * The Straight connector draws a simple straight line between the two anchor points. It does not have any constructor parameters.
  38. */
  39. jsPlumb.Connectors.Straight = function() {
  40. this.type = "Straight";
  41. var self = this;
  42. var currentPoints = null;
  43. var _m, _m2, _b, _dx, _dy, _theta, _theta2, _sx, _sy, _tx, _ty;
  44. /**
  45. * Computes the new size and position of the canvas.
  46. * @param sourceAnchor Absolute position on screen of the source object's anchor.
  47. * @param targetAnchor Absolute position on screen of the target object's anchor.
  48. * @param positionMatrix Indicates the relative positions of the left,top of the
  49. * two plumbed objects. so [0,0] indicates that the source is to the left of, and
  50. * above, the target. [1,0] means the source is to the right and above. [0,1] means
  51. * the source is to the left and below. [1,1] means the source is to the right
  52. * and below. this is used to figure out which direction to draw the connector in.
  53. * @returns an array of positioning information. the first two values are
  54. * the [left, top] absolute position the canvas should be placed on screen. the
  55. * next two values are the [width,height] the canvas should be. after that each
  56. * Connector can put whatever it likes into the array:it will be passed back in
  57. * to the paint call. This particular function stores the origin and destination of
  58. * the line it is going to draw. a more involved implementation, like a Bezier curve,
  59. * would store the control point info in this array too.
  60. */
  61. this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth) {
  62. var w = Math.abs(sourcePos[0] - targetPos[0]);
  63. var h = Math.abs(sourcePos[1] - targetPos[1]);
  64. var widthAdjusted = false, heightAdjusted = false;
  65. // these are padding to ensure the whole connector line appears
  66. var xo = 0.45 * w, yo = 0.45 * h;
  67. // these are padding to ensure the whole connector line appears
  68. w *= 1.9; h *=1.9;
  69. var x = Math.min(sourcePos[0], targetPos[0]) - xo;
  70. var y = Math.min(sourcePos[1], targetPos[1]) - yo;
  71. // minimum size is 2 * line Width if minWidth was not given.
  72. var calculatedMinWidth = Math.max(2 * lineWidth, minWidth);
  73. if (w < calculatedMinWidth) {
  74. w = calculatedMinWidth;
  75. x = sourcePos[0] + ((targetPos[0] - sourcePos[0]) / 2) - (calculatedMinWidth / 2);
  76. xo = (w - Math.abs(sourcePos[0]-targetPos[0])) / 2;
  77. }
  78. if (h < calculatedMinWidth) {
  79. h = calculatedMinWidth;
  80. y = sourcePos[1] + ((targetPos[1] - sourcePos[1]) / 2) - (calculatedMinWidth / 2);
  81. yo = (h - Math.abs(sourcePos[1]-targetPos[1])) / 2;
  82. }
  83. _sx = sourcePos[0] < targetPos[0] ? xo : w-xo;
  84. _sy = sourcePos[1] < targetPos[1] ? yo:h-yo;
  85. _tx = sourcePos[0] < targetPos[0] ? w-xo : xo;
  86. _ty = sourcePos[1] < targetPos[1] ? h-yo : yo;
  87. currentPoints = [ x, y, w, h, _sx, _sy, _tx, _ty ];
  88. _dx = _tx - _sx, _dy = (_ty - _sy);
  89. _m = _dy / _dx, _m2 = -1 / _m;
  90. _b = -1 * ((_m * _sx) - _sy);
  91. _theta = Math.atan(_m); _theta2 = Math.atan(_m2);
  92. return currentPoints;
  93. };
  94. /**
  95. * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from
  96. * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much.
  97. */
  98. this.pointOnPath = function(location) {
  99. var xp = _sx + (location * _dx);
  100. var yp = (_m == Infinity || _m == -Infinity) ? _sy + (location * (_ty - _sy)) : (_m * xp) + _b;
  101. return {x:xp, y:yp};
  102. };
  103. /**
  104. * returns the gradient of the connector at the given point - which for us is constant.
  105. */
  106. this.gradientAtPoint = function(location) { return _m; };
  107. /**
  108. * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where
  109. * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels.
  110. */
  111. this.pointAlongPathFrom = function(location, distance) {
  112. var p = self.pointOnPath(location);
  113. var orientation = distance > 0 ? 1 : -1;
  114. var y = Math.abs(distance * Math.sin(_theta));
  115. if (_sy > _ty) y = y * -1;
  116. var x = Math.abs(distance * Math.cos(_theta));
  117. if (_sx > _tx) x = x * -1;
  118. return {x:p.x + (orientation * x), y:p.y + (orientation * y)};
  119. };
  120. /**
  121. * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location.
  122. * the line is 'length' pixels long.
  123. */
  124. this.perpendicularToPathAt = function(location, length, distance) {
  125. var p = self.pointAlongPathFrom(location, distance);
  126. var m = self.gradientAtPoint(p.location);
  127. var _theta2 = Math.atan(-1 / m);
  128. var y = length / 2 * Math.sin(_theta2);
  129. var x = length / 2 * Math.cos(_theta2);
  130. return [{x:p.x + x, y:p.y + y}, {x:p.x - x, y:p.y - y}];
  131. };
  132. };
  133. /**
  134. * Class:Connectors.Bezier
  135. * This Connector draws a Bezier curve with two control points. You can provide a 'curviness' value which gets applied to jsPlumb's
  136. * internal voodoo machine and ends up generating locations for the two control points. See the constructor documentation below.
  137. */
  138. /**
  139. * Function:Constructor
  140. *
  141. * Parameters:
  142. * curviness - How 'curvy' you want the curve to be! This is a directive for the placement of control points, not endpoints of the curve, so your curve does not
  143. * actually touch the given point, but it has the tendency to lean towards it. The larger this value, the greater the curve is pulled from a straight line.
  144. * Optional; defaults to 150.
  145. *
  146. */
  147. jsPlumb.Connectors.Bezier = function(params) {
  148. var self = this;
  149. params = params || {};
  150. this.majorAnchor = params.curviness || 150;
  151. this.minorAnchor = 10;
  152. var currentPoints = null;
  153. this.type = "Bezier";
  154. this._findControlPoint = function(point, sourceAnchorPosition, targetAnchorPosition, sourceAnchor, targetAnchor) {
  155. // determine if the two anchors are perpendicular to each other in their orientation. we swap the control
  156. // points around if so (code could be tightened up)
  157. var soo = sourceAnchor.getOrientation(), too = targetAnchor.getOrientation();
  158. var perpendicular = soo[0] != too[0] || soo[1] == too[1];
  159. var p = [];
  160. var ma = self.majorAnchor, mi = self.minorAnchor;
  161. if (!perpendicular) {
  162. if (soo[0] == 0) // X
  163. p.push(sourceAnchorPosition[0] < targetAnchorPosition[0] ? point[0] + mi : point[0] - mi);
  164. else p.push(point[0] - (ma * soo[0]));
  165. if (soo[1] == 0) // Y
  166. p.push(sourceAnchorPosition[1] < targetAnchorPosition[1] ? point[1] + mi : point[1] - mi);
  167. else p.push(point[1] + (ma * too[1]));
  168. }
  169. else {
  170. if (too[0] == 0) // X
  171. p.push(targetAnchorPosition[0] < sourceAnchorPosition[0] ? point[0] + mi : point[0] - mi);
  172. else p.push(point[0] + (ma * too[0]));
  173. if (too[1] == 0) // Y
  174. p.push(targetAnchorPosition[1] < sourceAnchorPosition[1] ? point[1] + mi : point[1] - mi);
  175. else p.push(point[1] + (ma * soo[1]));
  176. }
  177. return p;
  178. };
  179. var _CP, _CP2, _sx, _tx, _ty, _sx, _sy, _canvasX, _canvasY, _w, _h;
  180. this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth)
  181. {
  182. lineWidth = lineWidth || 0;
  183. _w = Math.abs(sourcePos[0] - targetPos[0]) + lineWidth;
  184. _h = Math.abs(sourcePos[1] - targetPos[1]) + lineWidth;
  185. _canvasX = Math.min(sourcePos[0], targetPos[0])-(lineWidth/2);
  186. _canvasY = Math.min(sourcePos[1], targetPos[1])-(lineWidth/2);
  187. _sx = sourcePos[0] < targetPos[0] ? _w - (lineWidth/2): (lineWidth/2);
  188. _sy = sourcePos[1] < targetPos[1] ? _h - (lineWidth/2) : (lineWidth/2);
  189. _tx = sourcePos[0] < targetPos[0] ? (lineWidth/2) : _w - (lineWidth/2);
  190. _ty = sourcePos[1] < targetPos[1] ? (lineWidth/2) : _h - (lineWidth/2);
  191. _CP = self._findControlPoint([_sx,_sy], sourcePos, targetPos, sourceAnchor, targetAnchor);
  192. _CP2 = self._findControlPoint([_tx,_ty], targetPos, sourcePos, targetAnchor, sourceAnchor);
  193. var minx1 = Math.min(_sx,_tx); var minx2 = Math.min(_CP[0], _CP2[0]); var minx = Math.min(minx1,minx2);
  194. var maxx1 = Math.max(_sx,_tx); var maxx2 = Math.max(_CP[0], _CP2[0]); var maxx = Math.max(maxx1,maxx2);
  195. if (maxx > _w) _w = maxx;
  196. if (minx < 0) {
  197. _canvasX += minx; var ox = Math.abs(minx);
  198. _w += ox; _CP[0] += ox; _sx += ox; _tx +=ox; _CP2[0] += ox;
  199. }
  200. var miny1 = Math.min(_sy,_ty); var miny2 = Math.min(_CP[1], _CP2[1]); var miny = Math.min(miny1,miny2);
  201. var maxy1 = Math.max(_sy,_ty); var maxy2 = Math.max(_CP[1], _CP2[1]); var maxy = Math.max(maxy1,maxy2);
  202. if (maxy > _h) _h = maxy;
  203. if (miny < 0) {
  204. _canvasY += miny; var oy = Math.abs(miny);
  205. _h += oy; _CP[1] += oy; _sy += oy; _ty +=oy; _CP2[1] += oy;
  206. }
  207. if (minWidth && _w < minWidth) {
  208. var posAdjust = (minWidth - _w) / 2;
  209. _w = minWidth;
  210. _canvasX -= posAdjust; _sx = _sx + posAdjust ; _tx = _tx + posAdjust; _CP[0] = _CP[0] + posAdjust; _CP2[0] = _CP2[0] + posAdjust;
  211. }
  212. if (minWidth && _h < minWidth) {
  213. var posAdjust = (minWidth - _h) / 2;
  214. _h = minWidth;
  215. _canvasY -= posAdjust; _sy = _sy + posAdjust ; _ty = _ty + posAdjust; _CP[1] = _CP[1] + posAdjust; _CP2[1] = _CP2[1] + posAdjust;
  216. }
  217. currentPoints = [_canvasX, _canvasY, _w, _h, _sx, _sy, _tx, _ty, _CP[0], _CP[1], _CP2[0], _CP2[1] ];
  218. return currentPoints;
  219. };
  220. var _makeCurve = function() {
  221. return [
  222. { x:_sx, y:_sy },
  223. { x:_CP[0], y:_CP[1] },
  224. { x:_CP2[0], y:_CP2[1] },
  225. { x:_tx, y:_ty }
  226. ];
  227. };
  228. /**
  229. * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from
  230. * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much.
  231. */
  232. this.pointOnPath = function(location) {
  233. return jsBezier.pointOnCurve(_makeCurve(), location);
  234. };
  235. /**
  236. * returns the gradient of the connector at the given point.
  237. */
  238. this.gradientAtPoint = function(location) {
  239. return jsBezier.gradientAtPoint(_makeCurve(), location);
  240. };
  241. /**
  242. * for Bezier curves this method is a little tricky, cos calculating path distance algebraically is notoriously difficult.
  243. * this method is iterative, jumping forward .05% of the path at a time and summing the distance between this point and the previous
  244. * one, until the sum reaches 'distance'. the method may turn out to be computationally expensive; we'll see.
  245. * another drawback of this method is that if the connector gets quite long, .05% of the length of it is not necessarily smaller
  246. * than the desired distance, in which case the loop returns immediately and the arrow is mis-shapen. so a better strategy might be to
  247. * calculate the step as a function of distance/distance between endpoints.
  248. */
  249. this.pointAlongPathFrom = function(location, distance) {
  250. return jsBezier.pointAlongCurveFrom(_makeCurve(), location, distance);
  251. };
  252. /**
  253. * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location.
  254. * the line is 'length' pixels long.
  255. */
  256. this.perpendicularToPathAt = function(location, length, distance) {
  257. return jsBezier.perpendicularToCurveAt(_makeCurve(), location, length, distance);
  258. };
  259. };
  260. /**
  261. * Class: Connectors.Flowchart
  262. * Provides 'flowchart' connectors, consisting of vertical and horizontal line segments.
  263. */
  264. /**
  265. * Function: Constructor
  266. *
  267. * Parameters:
  268. * stub - minimum length for the stub at each end of the connector. defaults to 30 pixels.
  269. */
  270. jsPlumb.Connectors.Flowchart = function(params) {
  271. this.type = "Flowchart";
  272. params = params || {};
  273. var self = this,
  274. minStubLength = params.stub || params.minStubLength /* bwds compat. */ || 30,
  275. segments = [],
  276. segmentGradients = [],
  277. segmentProportions = [],
  278. segmentLengths = [],
  279. segmentProportionalLengths = [],
  280. points = [],
  281. swapX,
  282. swapY,
  283. /**
  284. * recalculates the gradients of each segment, and the points at which the segments begin, proportional to the total length travelled
  285. * by all the segments that constitute the connector.
  286. */
  287. updateSegmentGradientsAndProportions = function(startX, startY, endX, endY) {
  288. var total = 0;
  289. for (var i = 0; i < segments.length; i++) {
  290. var sx = i == 0 ? startX : segments[i][2],
  291. sy = i == 0 ? startY : segments[i][3],
  292. ex = segments[i][0],
  293. ey = segments[i][1];
  294. segmentGradients[i] = sx == ex ? Infinity : 0;
  295. segmentLengths[i] = Math.abs(sx == ex ? ey - sy : ex - sx);
  296. total += segmentLengths[i];
  297. }
  298. var curLoc = 0;
  299. for (var i = 0; i < segments.length; i++) {
  300. segmentProportionalLengths[i] = segmentLengths[i] / total;
  301. segmentProportions[i] = [curLoc, (curLoc += (segmentLengths[i] / total)) ];
  302. }
  303. },
  304. appendSegmentsToPoints = function() {
  305. points.push(segments.length);
  306. for (var i = 0; i < segments.length; i++) {
  307. points.push(segments[i][0]);
  308. points.push(segments[i][1]);
  309. }
  310. },
  311. /**
  312. * helper method to add a segment.
  313. */
  314. addSegment = function(x, y, sx, sy, tx, ty) {
  315. var lx = segments.length == 0 ? sx : segments[segments.length - 1][0];
  316. var ly = segments.length == 0 ? sy : segments[segments.length - 1][1];
  317. segments.push([x, y, lx, ly]);
  318. },
  319. /**
  320. * returns [segment, proportion of travel in segment, segment index] for the segment
  321. * that contains the point which is 'location' distance along the entire path, where
  322. * 'location' is a decimal between 0 and 1 inclusive. in this connector type, paths
  323. * are made up of a list of segments, each of which contributes some fraction to
  324. * the total length.
  325. */
  326. findSegmentForLocation = function(location) {
  327. var idx = segmentProportions.length - 1, inSegmentProportion = 0;
  328. for (var i = 0; i < segmentProportions.length; i++) {
  329. if (segmentProportions[i][1] >= location) {
  330. idx = i;
  331. inSegmentProportion = (location - segmentProportions[i][0]) / segmentProportionalLengths[i];
  332. break;
  333. }
  334. }
  335. return { segment:segments[idx], proportion:inSegmentProportion, index:idx };
  336. };
  337. this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth) {
  338. segments = [];
  339. segmentGradients = [];
  340. segmentProportionalLengths = [];
  341. segmentLengths = [];
  342. segmentProportionals = [];
  343. swapX = targetPos[0] < sourcePos[0];
  344. swapY = targetPos[1] < sourcePos[1];
  345. var lw = lineWidth || 1,
  346. offx = (lw / 2) + (minStubLength * 2),
  347. offy = (lw / 2) + (minStubLength * 2),
  348. so = sourceAnchor.orientation || sourceAnchor.getOrientation(),
  349. to = targetAnchor.orientation || targetAnchor.getOrientation(),
  350. x = swapX ? targetPos[0] : sourcePos[0],
  351. y = swapY ? targetPos[1] : sourcePos[1],
  352. w = Math.abs(targetPos[0] - sourcePos[0]) + 2*offx,
  353. h = Math.abs(targetPos[1] - sourcePos[1]) + 2*offy;
  354. if (w < minWidth) {
  355. offx += (minWidth - w) / 2;
  356. w = minWidth;
  357. }
  358. if (h < minWidth) {
  359. offy += (minWidth - h) / 2;
  360. h = minWidth;
  361. }
  362. var sx = swapX ? w-offx : offx,
  363. sy = swapY ? h-offy : offy,
  364. tx = swapX ? offx : w-offx ,
  365. ty = swapY ? offy : h-offy,
  366. startStubX = sx + (so[0] * minStubLength),
  367. startStubY = sy + (so[1] * minStubLength),
  368. endStubX = tx + (to[0] * minStubLength),
  369. endStubY = ty + (to[1] * minStubLength),
  370. midx = startStubX + ((endStubX - startStubX) / 2),
  371. midy = startStubY + ((endStubY - startStubY) / 2);
  372. x -= offx; y -= offy;
  373. points = [x, y, w, h, sx, sy, tx, ty], extraPoints = [];
  374. addSegment(startStubX, startStubY, sx, sy, tx, ty);
  375. if (so[0] == 0) {
  376. var startStubIsBeforeEndStub = startStubY < endStubY;
  377. // when start point's stub is less than endpoint's stub
  378. if (startStubIsBeforeEndStub) {
  379. addSegment(startStubX, midy, sx, sy, tx, ty);
  380. addSegment(midx, midy, sx, sy, tx, ty);
  381. addSegment(endStubX, midy, sx, sy, tx, ty);
  382. } else {
  383. // when start point's stub is greater than endpoint's stub
  384. addSegment(midx, startStubY, sx, sy, tx, ty);
  385. addSegment(midx, endStubY, sx, sy, tx, ty);
  386. }
  387. }
  388. else {
  389. var startStubIsBeforeEndStub = startStubX < endStubX;
  390. // when start point's stub is less than endpoint's stub
  391. if (startStubIsBeforeEndStub) {
  392. addSegment(midx, startStubY, sx, sy, tx, ty);
  393. addSegment(midx, midy, sx, sy, tx, ty);
  394. addSegment(midx, endStubY, sx, sy, tx, ty);
  395. } else {
  396. // when start point's stub is greater than endpoint's stub
  397. addSegment(startStubX, midy, sx, sy, tx, ty);
  398. addSegment(endStubX, midy, sx, sy, tx, ty);
  399. }
  400. }
  401. addSegment(endStubX, endStubY, sx, sy, tx, ty);
  402. addSegment(tx, ty, sx, sy, tx, ty);
  403. appendSegmentsToPoints();
  404. updateSegmentGradientsAndProportions(sx, sy, tx, ty);
  405. return points;
  406. };
  407. /**
  408. * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from
  409. * 0 to 1 inclusive. for this connector we must first figure out which segment the given point lies in, and then compute the x,y position
  410. * from our knowledge of the segment's start and end points.
  411. */
  412. this.pointOnPath = function(location) {
  413. return self.pointAlongPathFrom(location, 0);
  414. };
  415. /**
  416. * returns the gradient of the connector at the given point; the gradient will be either 0 or Infinity, depending on the direction of the
  417. * segment the point falls in. segment gradients are calculated in the compute method.
  418. */
  419. this.gradientAtPoint = function(location) {
  420. return segmentGradients[findSegmentForLocation(location)["index"]];
  421. };
  422. /**
  423. * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where
  424. * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. when you consider this concept from the point of view
  425. * of this connector, it starts to become clear that there's a problem with the overlay paint code: given that this connector makes several
  426. * 90 degree turns, it's entirely possible that an arrow overlay could be forced to paint itself around a corner, which would look stupid. this is
  427. * because jsPlumb uses this method (and pointOnPath) so determine the locations of the various points that go to make up an overlay. a better
  428. * solution would probably be to just use pointOnPath along with gradientAtPoint, and draw the overlay so that its axis ran along
  429. * a tangent to the connector. for straight line connectors this would obviously mean the overlay was painted directly on the connector, since a
  430. * tangent to a straight line is the line itself, which is what we want; for this connector, and for beziers, the results would probably be better. an additional
  431. * advantage is, of course, that there's less computation involved doing it that way.
  432. */
  433. this.pointAlongPathFrom = function(location, distance) {
  434. var s = findSegmentForLocation(location), seg = s.segment, p = s.proportion, sl = segmentLengths[s.index], m = segmentGradients[s.index];
  435. var e = {
  436. x : m == Infinity ? seg[2] : seg[2] > seg[0] ? seg[0] + ((1 - p) * sl) - distance : seg[2] + (p * sl) + distance,
  437. y : m == 0 ? seg[3] : seg[3] > seg[1] ? seg[1] + ((1 - p) * sl) - distance : seg[3] + (p * sl) + distance,
  438. segmentInfo : s
  439. };
  440. return e;
  441. };
  442. /**
  443. * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location.
  444. * the line is 'length' pixels long.
  445. */
  446. this.perpendicularToPathAt = function(location, length, distance) {
  447. var p = self.pointAlongPathFrom(location, distance);
  448. var m = segmentGradients[p.segmentInfo.index];
  449. var _theta2 = Math.atan(-1 / m);
  450. var y = length / 2 * Math.sin(_theta2);
  451. var x = length / 2 * Math.cos(_theta2);
  452. return [{x:p.x + x, y:p.y + y}, {x:p.x - x, y:p.y - y}];
  453. };
  454. };
  455. // ********************************* END OF CONNECTOR TYPES *******************************************************************
  456. // ********************************* ENDPOINT TYPES *******************************************************************
  457. /**
  458. * Class: Endpoints.Dot
  459. * A round endpoint, with default radius 10 pixels.
  460. */
  461. /**
  462. * Function: Constructor
  463. *
  464. * Parameters:
  465. *
  466. * radius - radius of the endpoint. defaults to 10 pixels.
  467. */
  468. jsPlumb.Endpoints.Dot = function(params) {
  469. this.type = "Dot";
  470. var self = this;
  471. params = params || {};
  472. this.radius = params.radius || 10;
  473. this.defaultOffset = 0.5 * this.radius;
  474. this.defaultInnerRadius = this.radius / 3;
  475. this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) {
  476. var r = endpointStyle.radius || self.radius;
  477. var x = anchorPoint[0] - r;
  478. var y = anchorPoint[1] - r;
  479. return [ x, y, r * 2, r * 2, r ];
  480. };
  481. };
  482. /**
  483. * Class: Endpoints.Rectangle
  484. * A Rectangular Endpoint, with default size 20x20.
  485. */
  486. /**
  487. * Function: Constructor
  488. *
  489. * Parameters:
  490. *
  491. * width - width of the endpoint. defaults to 20 pixels.
  492. * height - height of the endpoint. defaults to 20 pixels.
  493. */
  494. jsPlumb.Endpoints.Rectangle = function(params) {
  495. this.type = "Rectangle";
  496. var self = this;
  497. params = params || {};
  498. this.width = params.width || 20;
  499. this.height = params.height || 20;
  500. this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) {
  501. var width = endpointStyle.width || self.width;
  502. var height = endpointStyle.height || self.height;
  503. var x = anchorPoint[0] - (width/2);
  504. var y = anchorPoint[1] - (height/2);
  505. return [ x, y, width, height];
  506. };
  507. };
  508. /**
  509. * Class: Endpoints.Image
  510. * Draws an image as the Endpoint.
  511. */
  512. /**
  513. * Function: Constructor
  514. *
  515. * Parameters:
  516. *
  517. * src - location of the image to use.
  518. */
  519. jsPlumb.Endpoints.Image = function(params) {
  520. this.type = "Image";
  521. jsPlumb.DOMElementComponent.apply(this, arguments);
  522. var self = this, initialized = false;
  523. this.img = new Image();
  524. self.ready = false;
  525. this.img.onload = function() {
  526. self.ready = true;
  527. };
  528. this.img.src = params.src || params.url;
  529. this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) {
  530. self.anchorPoint = anchorPoint;
  531. if (self.ready) return [anchorPoint[0] - self.img.width / 2, anchorPoint[1] - self.img.height/ 2, self.img.width, self.img.height];
  532. else return [0,0,0,0];
  533. };
  534. self.canvas = document.createElement("img"), initialized = false;
  535. self.canvas.style["margin"] = 0;
  536. self.canvas.style["padding"] = 0;
  537. self.canvas.style["outline"] = 0;
  538. self.canvas.style["position"] = "absolute";
  539. self.canvas.className = jsPlumb.endpointClass;
  540. jsPlumb.appendElement(self.canvas, params.parent);
  541. self.attachListeners(self.canvas, self);
  542. var actuallyPaint = function(d, style, anchor) {
  543. if (!initialized) {
  544. self.canvas.setAttribute("src", self.img.src);
  545. initialized = true;
  546. }
  547. var width = self.img.width,
  548. height = self.img.height,
  549. x = self.anchorPoint[0] - (width/2),
  550. y = self.anchorPoint[1] - (height/2);
  551. jsPlumb.sizeCanvas(self.canvas, x, y, width, height);
  552. };
  553. this.paint = function(d, style, anchor) {
  554. if (self.ready) {
  555. actuallyPaint(d, style, anchor);
  556. }
  557. else {
  558. window.setTimeout(function() {
  559. self.paint(d, style, anchor);
  560. }, 200);
  561. }
  562. };
  563. };
  564. /**
  565. * Class: Endpoints.Blank
  566. * An Endpoint that paints nothing on the screen, and cannot be interacted with using the mouse. There are no constructor parameters for this Endpoint.
  567. */
  568. jsPlumb.Endpoints.Blank = function(params) {
  569. var self = this;
  570. this.type = "Blank";
  571. jsPlumb.DOMElementComponent.apply(this, arguments);
  572. this.compute = function() {
  573. return [0,0,10,0];
  574. };
  575. self.canvas = document.createElement("div");
  576. self.canvas.style.display = "block";
  577. self.canvas.style.width = "1px";
  578. self.canvas.style.height = "1px";
  579. self.canvas.style.background = "transparent";
  580. self.canvas.style.position = "absolute";
  581. jsPlumb.appendElement(self.canvas, params.parent);
  582. this.paint = function() { };
  583. };
  584. /**
  585. * Class: Endpoints.Triangle
  586. * A triangular Endpoint.
  587. */
  588. /**
  589. * Function: Constructor
  590. *
  591. * Parameters:
  592. *
  593. * width - width of the triangle's base. defaults to 55 pixels.
  594. * height - height of the triangle from base to apex. defaults to 55 pixels.
  595. */
  596. jsPlumb.Endpoints.Triangle = function(params) {
  597. this.type = "Triangle";
  598. params = params || { };
  599. params.width = params.width || 55;
  600. param.height = params.height || 55;
  601. this.width = params.width;
  602. this.height = params.height;
  603. this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) {
  604. var width = endpointStyle.width || self.width;
  605. var height = endpointStyle.height || self.height;
  606. var x = anchorPoint[0] - (width/2);
  607. var y = anchorPoint[1] - (height/2);
  608. return [ x, y, width, height ];
  609. };
  610. };
  611. // ********************************* END OF ENDPOINT TYPES *******************************************************************
  612. // ********************************* OVERLAY DEFINITIONS ***********************************************************************
  613. /**
  614. * Class: Overlays.Arrow
  615. *
  616. * An arrow overlay, defined by four points: the head, the two sides of the tail, and a 'foldback' point at some distance along the length
  617. * of the arrow that lines from each tail point converge into. The foldback point is defined using a decimal that indicates some fraction
  618. * of the length of the arrow and has a default value of 0.623. A foldback point value of 1 would mean that the arrow had a straight line
  619. * across the tail.
  620. */
  621. /**
  622. * Function: Constructor
  623. *
  624. * Parameters:
  625. *
  626. * length - distance in pixels from head to tail baseline. default 20.
  627. * width - width in pixels of the tail baseline. default 20.
  628. * fillStyle - style to use when filling the arrow. defaults to "black".
  629. * strokeStyle - style to use when stroking the arrow. defaults to null, which means the arrow is not stroked.
  630. * lineWidth - line width to use when stroking the arrow. defaults to 1, but only used if strokeStyle is not null.
  631. * 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.
  632. * location - distance (as a decimal from 0 to 1 inclusive) marking where the arrow should sit on the connector. defaults to 0.5.
  633. * direction - indicates the direction the arrow points in. valid values are -1 and 1; 1 is default.
  634. */
  635. jsPlumb.Overlays.Arrow = function(params) {
  636. this.type = "Arrow";
  637. params = params || {};
  638. var self = this;
  639. this.length = params.length || 20;
  640. this.width = params.width || 20;
  641. this.id = params.id;
  642. this.connection = params.connection;
  643. var direction = (params.direction || 1) < 0 ? -1 : 1;
  644. var paintStyle = params.paintStyle || { lineWidth:1 };
  645. this.loc = params.location == null ? 0.5 : params.location;
  646. // how far along the arrow the lines folding back in come to. default is 62.3%.
  647. var foldback = params.foldback || 0.623;
  648. var _getFoldBackPoint = function(connector, loc) {
  649. if (foldback == 0.5) return connector.pointOnPath(loc);
  650. else {
  651. var adj = 0.5 - foldback; // we calculate relative to the center
  652. return connector.pointAlongPathFrom(loc, direction * self.length * adj);
  653. }
  654. };
  655. this.computeMaxSize = function() { return self.width * 1.5; };
  656. this.draw = function(connector, currentConnectionPaintStyle, connectorDimensions) {
  657. // this is the arrow head position
  658. var hxy = connector.pointAlongPathFrom(self.loc, direction * (self.length / 2));
  659. // this is the center of the tail
  660. var txy = connector.pointAlongPathFrom(self.loc, -1 * direction * (self.length / 2)), tx = txy.x, ty = txy.y;
  661. // this is the tail vector
  662. var tail = connector.perpendicularToPathAt(self.loc, self.width, -1 * direction * (self.length / 2));
  663. // this is the point the tail goes in to
  664. var cxy = _getFoldBackPoint(connector, self.loc);
  665. // if loc = 1, then hxy should be flush with the element, or if direction == -1, the tail midpoint.
  666. if (self.loc == 1) {
  667. var lxy = connector.pointOnPath(self.loc);
  668. // TODO determine why the 1.2.6 released version does not
  669. // use 'direction' in the two equations below, yet both
  670. // that and 1.3.0 still paint the arrows correctly.
  671. var dx = (lxy.x - hxy.x) * direction, dy = (lxy.y - hxy.y) * direction;
  672. cxy.x += dx; cxy.y += dy;
  673. txy.x += dx; txy.y += dy;
  674. tail[0].x += dx; tail[0].y += dy;
  675. tail[1].x += dx; tail[1].y += dy;
  676. hxy.x += dx; hxy.y += dy;
  677. }
  678. // if loc = 0, then tail midpoint should be flush with the element, or, if direction == -1, hxy should be.
  679. if (self.loc == 0) {
  680. var lxy = connector.pointOnPath(self.loc);
  681. var tailMid = foldback > 1 ? cxy : {
  682. x:tail[0].x + ((tail[1].x - tail[0].x) / 2),
  683. y:tail[0].y + ((tail[1].y - tail[0].y) / 2)
  684. };
  685. var dx = (lxy.x - tailMid.x) * direction, dy = (lxy.y - tailMid.y) * direction;
  686. cxy.x += dx; cxy.y += dy;
  687. txy.x += dx; txy.y += dy;
  688. tail[0].x += dx; tail[0].y += dy;
  689. tail[1].x += dx; tail[1].y += dy;
  690. hxy.x += dx; hxy.y += dy;
  691. }
  692. var minx = Math.min(hxy.x, tail[0].x, tail[1].x);
  693. var maxx = Math.max(hxy.x, tail[0].x, tail[1].x);
  694. var miny = Math.min(hxy.y, tail[0].y, tail[1].y);
  695. var maxy = Math.max(hxy.y, tail[0].y, tail[1].y);
  696. var d = { hxy:hxy, tail:tail, cxy:cxy },
  697. strokeStyle = paintStyle.strokeStyle || currentConnectionPaintStyle.strokeStyle,
  698. fillStyle = paintStyle.fillStyle || currentConnectionPaintStyle.strokeStyle,
  699. lineWidth = paintStyle.lineWidth || currentConnectionPaintStyle.lineWidth;
  700. self.paint(connector, d, lineWidth, strokeStyle, fillStyle, connectorDimensions);
  701. return [ minx, maxx, miny, maxy];
  702. };
  703. };
  704. /**
  705. * Class: Overlays.PlainArrow
  706. *
  707. * 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
  708. * point along the length of the arrow: in this case, that foldback point is the full length of the arrow. so it just does
  709. * a 'call' to Arrow with foldback set appropriately.
  710. */
  711. /**
  712. * Function: Constructor
  713. * See <Overlays.Arrow> for allowed parameters for this overlay.
  714. */
  715. jsPlumb.Overlays.PlainArrow = function(params) {
  716. params = params || {};
  717. var p = jsPlumb.extend(params, {foldback:1});
  718. jsPlumb.Overlays.Arrow.call(this, p);
  719. this.type = "PlainArrow";
  720. };
  721. /**
  722. * Class: Overlays.Diamond
  723. *
  724. * A diamond. Like PlainArrow, this is a concrete case of the more generic case of the tail points converging on some point...it just
  725. * happens that in this case, that point is greater than the length of the the arrow.
  726. *
  727. * this could probably do with some help with positioning...due to the way it reuses the Arrow paint code, what Arrow thinks is the
  728. * 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
  729. * stuck when it comes to helping out the Arrow class. possibly we could pass in a 'transpose' parameter or something. the value
  730. * would be -l/4 in this case - move along one quarter of the total length.
  731. */
  732. /**
  733. * Function: Constructor
  734. * See <Overlays.Arrow> for allowed parameters for this overlay.
  735. */
  736. jsPlumb.Overlays.Diamond = function(params) {
  737. params = params || {};
  738. var l = params.length || 40;
  739. var p = jsPlumb.extend(params, {length:l/2, foldback:2});
  740. jsPlumb.Overlays.Arrow.call(this, p);
  741. this.type = "Diamond";
  742. };
  743. /**
  744. * Class: Overlays.Label
  745. * A Label overlay. For all different renderer types (SVG/Canvas/VML), jsPlumb draws a Label overlay as a styled DIV. Version 1.3.0 of jsPlumb
  746. * introduced the ability to set css classes on the label; this is now the preferred way for you to style a label. The 'labelStyle' parameter
  747. * is still supported in 1.3.0 but its usage is deprecated. Under the hood, jsPlumb just turns that object into a bunch of CSS directive that it
  748. * puts on the Label's 'style' attribute, so the end result is the same.
  749. */
  750. /**
  751. * Function: Constructor
  752. *
  753. * Parameters:
  754. * cssClass - optional css class string to append to css class. This string is appended "as-is", so you can of course have multiple classes
  755. * defined. This parameter is preferred to using labelStyle, borderWidth and borderStyle.
  756. * 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
  757. * label function returns null. empty strings _will_ be painted.
  758. * location - distance (as a decimal from 0 to 1 inclusive) marking where the label should sit on the connector. defaults to 0.5.
  759. * labelStyle - (deprecated) js object containing style instructions for the label. defaults to jsPlumb.Defaults.LabelStyle.
  760. * borderWidth - (deprecated) width of a border to paint. defaults to zero.
  761. * borderStyle - (deprecated) strokeStyle to use when painting the border, if necessary.
  762. *
  763. */
  764. jsPlumb.Overlays.Label = function(params) {
  765. this.type = "Label";
  766. jsPlumb.DOMElementComponent.apply(this, arguments);
  767. this.labelStyle = params.labelStyle || jsPlumb.Defaults.LabelStyle;
  768. this.labelStyle.font = this.labelStyle.font || "12px sans-serif";
  769. this.label = params.label || "banana";
  770. this.connection = params.connection;
  771. this.id = params.id;
  772. var self = this;
  773. var labelWidth = null, labelHeight = null, labelText = null, labelPadding = null;
  774. this.location = params.location || 0.5;
  775. this.cachedDimensions = null; // setting on 'this' rather than using closures uses a lot less memory. just don't monkey with it!
  776. var initialised = false,
  777. labelText = null,
  778. div = document.createElement("div");
  779. div.style["position"] = "absolute";
  780. div.style["font"] = self.labelStyle.font;
  781. div.style["color"] = self.labelStyle.color || "black";
  782. if (self.labelStyle.fillStyle) div.style["background"] = self.labelStyle.fillStyle;//_convertStyle(self.labelStyle.fillStyle, true);
  783. if (self.labelStyle.borderWidth > 0) {
  784. var dStyle = self.labelStyle.borderStyle ? self.labelStyle.borderStyle/*_convertStyle(self.labelStyle.borderStyle, true)*/ : "black";
  785. div.style["border"] = self.labelStyle.borderWidth + "px solid " + dStyle;
  786. }
  787. if (self.labelStyle.padding) div.style["padding"] = self.labelStyle.padding;
  788. var clazz = params["_jsPlumb"].overlayClass + " " +
  789. (self.labelStyle.cssClass ? self.labelStyle.cssClass :
  790. params.cssClass ? params.cssClass : "");
  791. div.className = clazz;
  792. jsPlumb.appendElement(div, params.connection.parent);
  793. jsPlumb.getId(div);
  794. self.attachListeners(div, self);
  795. this.paint = function(connector, d, connectorDimensions) {
  796. if (!initialised) {
  797. connector.appendDisplayElement(div);
  798. self.attachListeners(div, connector);
  799. initialised = true;
  800. }
  801. div.style.left = (connectorDimensions[0] + d.minx) + "px";
  802. div.style.top = (connectorDimensions[1] + d.miny) + "px";
  803. };
  804. this.getTextDimensions = function(connector) {
  805. labelText = typeof self.label == 'function' ? self.label(self) : self.label;
  806. div.innerHTML = labelText.replace(/\r\n/g, "<br/>");
  807. var de = jsPlumb.CurrentLibrary.getElementObject(div),
  808. s = jsPlumb.CurrentLibrary.getSize(de);
  809. return {width:s[0], height:s[1]};
  810. };
  811. this.computeMaxSize = function(connector) {
  812. var td = self.getTextDimensions(connector);
  813. return td.width ? Math.max(td.width, td.height) * 1.5 : 0;
  814. };
  815. this.draw = function(connector, currentConnectionPaintStyle, connectorDimensions) {
  816. var td = self.getTextDimensions(connector);
  817. if (td.width != null) {
  818. var cxy = connector.pointOnPath(self.location);
  819. var minx = cxy.x - (td.width / 2);
  820. var miny = cxy.y - (td.height / 2);
  821. self.paint(connector, {
  822. minx:minx,
  823. miny:miny,
  824. td:td,
  825. cxy:cxy
  826. }, connectorDimensions);
  827. return [minx, minx+td.width, miny, miny+td.height];
  828. }
  829. else return [0,0,0,0];
  830. };
  831. };
  832. // ********************************* END OF OVERLAY DEFINITIONS ***********************************************************************
  833. // ********************************* OVERLAY CANVAS RENDERERS***********************************************************************
  834. // ********************************* END OF OVERLAY CANVAS RENDERERS ***********************************************************************
  835. })();