jsPlumb-flowchart-0.1-RC1.js 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. ;(function() {
  2. jsPlumb.Connectors.Flowchart = function(params) {
  3. params = params || {};
  4. var self = this,
  5. minStubLength = params.minStubLength || 30,
  6. segments = [],
  7. segmentGradients = [],
  8. segmentProportions = [],
  9. segmentLengths = [],
  10. segmentProportionalLengths = [],
  11. points = [],
  12. swapX,
  13. swapY,
  14. /**
  15. * recalculates the gradients of each segment, and the points at which the segments begin, proportional to the total length travelled
  16. * by all the segments that constitute the connector.
  17. */
  18. updateSegmentGradientsAndProportions = function(startX, startY, endX, endY) {
  19. var total = 0;
  20. for (var i = 0; i < segments.length; i++) {
  21. var sx = i == 0 ? startX : segments[i][2],
  22. sy = i == 0 ? startY : segments[i][3],
  23. ex = segments[i][0],
  24. ey = segments[i][1];
  25. segmentGradients[i] = sx == ex ? Infinity : 0;
  26. segmentLengths[i] = Math.abs(sx == ex ? ey - sy : ex - sx);
  27. total += segmentLengths[i];
  28. }
  29. var curLoc = 0;
  30. for (var i = 0; i < segments.length; i++) {
  31. segmentProportionalLengths[i] = segmentLengths[i] / total;
  32. segmentProportions[i] = [curLoc, (curLoc += (segmentLengths[i] / total)) ];
  33. }
  34. },
  35. appendSegmentsToPoints = function() {
  36. points.push(segments.length);
  37. for (var i = 0; i < segments.length; i++) {
  38. points.push(segments[i][0]);
  39. points.push(segments[i][1]);
  40. }
  41. },
  42. /**
  43. * helper method to add a segment.
  44. */
  45. addSegment = function(x, y, sx, sy, tx, ty) {
  46. var lx = segments.length == 0 ? sx : segments[segments.length - 1][0];
  47. var ly = segments.length == 0 ? sy : segments[segments.length - 1][1];
  48. segments.push([x, y, lx, ly]);
  49. },
  50. /**
  51. * returns [segment, proportion of travel in segment, segment index] for the segment that contains the point which is 'location' distance along the entire path, where 'location' is
  52. * a decimal between 0 and 1 inclusive. in this connector type paths are made up of a list of segments, each of which contributes some fraction to
  53. * the total length.
  54. */
  55. findSegmentForLocation = function(location) {
  56. var idx = segmentProportions.length - 1, inSegmentProportion = 0;
  57. for (var i = 0; i < segmentProportions.length; i++) {
  58. if (segmentProportions[i][1] >= location) {
  59. idx = i;
  60. inSegmentProportion = (location - segmentProportions[i][0]) / segmentProportionalLengths[i];
  61. break;
  62. }
  63. }
  64. return { segment:segments[idx], proportion:inSegmentProportion, index:idx };
  65. };
  66. this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth) {
  67. segments = [];
  68. segmentGradients = [];
  69. segmentProportionalLengths = [];
  70. segmentLengths = [];
  71. segmentProportionals = [];
  72. swapX = targetPos[0] < sourcePos[0];
  73. swapY = targetPos[1] < sourcePos[1];
  74. var lw = lineWidth || 1,
  75. offx = (lw / 2) + (minStubLength * 2),
  76. offy = (lw / 2) + (minStubLength * 2),
  77. so = sourceAnchor.orientation || sourceAnchor.getOrientation(),
  78. to = targetAnchor.orientation || targetAnchor.getOrientation(),
  79. x = swapX ? targetPos[0] : sourcePos[0],
  80. y = swapY ? targetPos[1] : sourcePos[1],
  81. w = Math.abs(targetPos[0] - sourcePos[0]) + 2*offx,
  82. h = Math.abs(targetPos[1] - sourcePos[1]) + 2*offy;
  83. if (w < minWidth) {
  84. offx += (minWidth - w) / 2;
  85. w = minWidth;
  86. }
  87. if (h < minWidth) {
  88. offy += (minWidth - h) / 2;
  89. h = minWidth;
  90. }
  91. sx = swapX ? w-offx : offx,
  92. sy = swapY ? h-offy : offy,
  93. tx = swapX ? offx : w-offx ,
  94. ty = swapY ? offy : h-offy,
  95. startStubX = sx + (so[0] * minStubLength),
  96. startStubY = sy + (so[1] * minStubLength),
  97. endStubX = tx + (to[0] * minStubLength),
  98. endStubY = ty + (to[1] * minStubLength),
  99. midx = startStubX + ((endStubX - startStubX) / 2),
  100. midy = startStubY + ((endStubY - startStubY) / 2);
  101. x -= offx; y -= offy;
  102. points = [x, y, w, h, sx, sy, tx, ty], extraPoints = [];
  103. addSegment(startStubX, startStubY, sx, sy, tx, ty);
  104. if (so[0] == 0) {
  105. var startStubIsBeforeEndStub = startStubY < endStubY;
  106. // when start point's stub is less than endpoint's stub
  107. if (startStubIsBeforeEndStub) {
  108. addSegment(startStubX, midy, sx, sy, tx, ty);
  109. addSegment(midx, midy, sx, sy, tx, ty);
  110. addSegment(endStubX, midy, sx, sy, tx, ty);
  111. } else {
  112. // when start point's stub is greater than endpoint's stub
  113. addSegment(midx, startStubY, sx, sy, tx, ty);
  114. addSegment(midx, endStubY, sx, sy, tx, ty);
  115. }
  116. }
  117. else {
  118. var startStubIsBeforeEndStub = startStubX < endStubX;
  119. // when start point's stub is less than endpoint's stub
  120. if (startStubIsBeforeEndStub) {
  121. addSegment(midx, startStubY, sx, sy, tx, ty);
  122. addSegment(midx, midy, sx, sy, tx, ty);
  123. addSegment(midx, endStubY, sx, sy, tx, ty);
  124. } else {
  125. // when start point's stub is greater than endpoint's stub
  126. addSegment(startStubX, midy, sx, sy, tx, ty);
  127. addSegment(endStubX, midy, sx, sy, tx, ty);
  128. }
  129. }
  130. addSegment(endStubX, endStubY, sx, sy, tx, ty);
  131. addSegment(tx, ty, sx, sy, tx, ty);
  132. appendSegmentsToPoints();
  133. updateSegmentGradientsAndProportions(sx, sy, tx, ty);
  134. return points;
  135. };
  136. this.paint = function(dimensions, ctx) {
  137. ctx.beginPath();
  138. ctx.moveTo(dimensions[4], dimensions[5]);
  139. // loop through extra points
  140. for (var i = 0; i < dimensions[8]; i++) {
  141. ctx.lineTo(dimensions[9 + (i*2)], dimensions[10 + (i*2)]);
  142. }
  143. // finally draw a line to the end
  144. ctx.lineTo(dimensions[6], dimensions[7]);
  145. ctx.stroke();
  146. };
  147. /**
  148. * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from
  149. * 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
  150. * from our knowledge of the segment's start and end points.
  151. */
  152. this.pointOnPath = function(location) {
  153. return self.pointAlongPathFrom(location, 0);
  154. };
  155. /**
  156. * returns the gradient of the connector at the given point; the gradient will be either 0 or Infinity, depending on the direction of the
  157. * segment the point falls in. segment gradients are calculated in the compute method.
  158. */
  159. this.gradientAtPoint = function(location) {
  160. return segmentGradients[findSegmentForLocation(location)["index"]];
  161. };
  162. /**
  163. * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where
  164. * '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
  165. * of this connector, it starts to become clear that there's a problem with the overlay paint code: given that this connector makes several
  166. * 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
  167. * because jsPlumb uses this method (and pointOnPath) so determine the locations of the various points that go to make up an overlay. a better
  168. * solution would probably be to just use pointOnPath along with gradientAtPoint, and draw the overlay so that its axis ran along
  169. * a tangent to the connector. for straight line connectors this would obviously mean the overlay was painted directly on the connector, since a
  170. * 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
  171. * advantage is, of course, that there's less computation involved doing it that way.
  172. */
  173. this.pointAlongPathFrom = function(location, distance) {
  174. var s = findSegmentForLocation(location), seg = s.segment, p = s.proportion, sl = segmentLengths[s.index], m = segmentGradients[s.index];
  175. var e = {
  176. //x : m == Infinity ? seg[2] : /*swapX ? seg[2] - (p * sl) - distance : */seg[2] + (p * sl) + distance,
  177. x : m == Infinity ? seg[2] : seg[2] > seg[0] ? seg[0] + ((1 - p) * sl) - distance : seg[2] + (p * sl) + distance,
  178. //y : m == 0 ? seg[3] : /*swapY ? seg[3] - (p * sl) - distance : */seg[3] + (p * sl) + distance,
  179. y : m == 0 ? seg[3] : seg[3] > seg[1] ? seg[1] + ((1 - p) * sl) - distance : seg[3] + (p * sl) + distance,
  180. segmentInfo : s
  181. };
  182. //console.log("pointalongpath, swapX =" + swapX + ",swapY=" + swapY, "loc", location, "travel", (p * sl), "dist", distance, e.x, e.y, "seg", seg, "len", sl, "prop.", p);
  183. return e;
  184. };
  185. /**
  186. * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location.
  187. * the line is 'length' pixels long.
  188. */
  189. this.perpendicularToPathAt = function(location, length, distance) {
  190. var p = self.pointAlongPathFrom(location, distance);
  191. var m = segmentGradients[p.segmentInfo.index];
  192. var _theta2 = Math.atan(-1 / m);
  193. var y = length / 2 * Math.sin(_theta2);
  194. var x = length / 2 * Math.cos(_theta2);
  195. return [{x:p.x + x, y:p.y + y}, {x:p.x - x, y:p.y - y}];
  196. };
  197. };
  198. })();