jsPlumb-defaults-1.3.4-RC1.js 41 KB

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