6 * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas
9 * This file contains the 'flowchart' connectors, consisting of vertical and horizontal line segments.
11 * Copyright (c) 2010 - 2013 Simon Porritt (simon.porritt@gmail.com)
14 * http://github.com/sporritt/jsplumb
15 * http://code.google.com/p/jsplumb
17 * Dual licensed under the MIT and GPL2 licenses.
22 * Function: Constructor
25 * stub - minimum length for the stub at each end of the connector. This can be an integer, giving a value for both ends of the connections,
26 * or an array of two integers, giving separate values for each end. The default is an integer with value 30 (pixels).
27 * gap - gap to leave between the end of the connector and the element on which the endpoint resides. if you make this larger than stub then you will see some odd looking behaviour.
28 Like stub, this can be an array or a single value. defaults to 0 pixels for each end.
29 * cornerRadius - optional, defines the radius of corners between segments. defaults to 0 (hard edged corners).
30 * alwaysRespectStubs - defaults to false. whether or not the connectors should always draw the stub, or, if the two elements
31 are in close proximity to each other (closer than the sum of the two stubs), to adjust the stubs.
33 var Flowchart = function(params) {
34 this.type = "Flowchart";
35 params = params || {};
36 params.stub = params.stub == null ? 30 : params.stub;
38 _super = jsPlumb.Connectors.AbstractConnector.apply(this, arguments),
39 midpoint = params.midpoint == null ? 0.5 : params.midpoint,
40 points = [], segments = [],
42 alwaysRespectStubs = params.alwaysRespectStubs,
43 userSuppliedSegments = null,
44 lastx = null, lasty = null, lastOrientation,
45 cornerRadius = params.cornerRadius != null ? params.cornerRadius : 0,
46 sgn = function(n) { return n < 0 ? -1 : n === 0 ? 0 : 1; },
48 * helper method to add a segment.
50 addSegment = function(segments, x, y, paintInfo) {
51 if (lastx == x && lasty == y) return;
52 var lx = lastx == null ? paintInfo.sx : lastx,
53 ly = lasty == null ? paintInfo.sy : lasty,
54 o = lx == x ? "v" : "h",
60 segments.push([lx, ly, x, y, o, sgnx, sgny]);
62 segLength = function(s) {
63 return Math.sqrt(Math.pow(s[0] - s[2], 2) + Math.pow(s[1] - s[3], 2));
65 _cloneArray = function(a) { var _a = []; _a.push.apply(_a, a); return _a;},
66 updateMinMax = function(a1) {
67 self.bounds.minX = Math.min(self.bounds.minX, a1[2]);
68 self.bounds.maxX = Math.max(self.bounds.maxX, a1[2]);
69 self.bounds.minY = Math.min(self.bounds.minY, a1[3]);
70 self.bounds.maxY = Math.max(self.bounds.maxY, a1[3]);
72 writeSegments = function(conn, segments, paintInfo) {
74 for (var i = 0; i < segments.length - 1; i++) {
76 current = current || _cloneArray(segments[i]);
77 next = _cloneArray(segments[i + 1]);
78 if (cornerRadius > 0 && current[4] != next[4]) {
79 var radiusToUse = Math.min(cornerRadius, segLength(current), segLength(next));
80 // right angle. adjust current segment's end point, and next segment's start point.
81 current[2] -= current[5] * radiusToUse;
82 current[3] -= current[6] * radiusToUse;
83 next[0] += next[5] * radiusToUse;
84 next[1] += next[6] * radiusToUse;
85 var ac = (current[6] == next[5] && next[5] == 1) ||
86 ((current[6] == next[5] && next[5] === 0) && current[5] != next[6]) ||
87 (current[6] == next[5] && next[5] == -1),
88 sgny = next[1] > current[3] ? 1 : -1,
89 sgnx = next[0] > current[2] ? 1 : -1,
90 sgnEqual = sgny == sgnx,
91 cx = (sgnEqual && ac || (!sgnEqual && !ac)) ? next[0] : current[2],
92 cy = (sgnEqual && ac || (!sgnEqual && !ac)) ? current[3] : next[1];
94 _super.addSegment(conn, "Straight", {
95 x1:current[0], y1:current[1], x2:current[2], y2:current[3]
98 _super.addSegment(conn, "Arc", {
110 // dx + dy are used to adjust for line width.
111 var dx = (current[2] == current[0]) ? 0 : (current[2] > current[0]) ? (paintInfo.lw / 2) : -(paintInfo.lw / 2),
112 dy = (current[3] == current[1]) ? 0 : (current[3] > current[1]) ? (paintInfo.lw / 2) : -(paintInfo.lw / 2);
113 _super.addSegment(conn, "Straight", {
114 x1:current[0]- dx, y1:current[1]-dy, x2:current[2] + dx, y2:current[3] + dy
120 _super.addSegment(conn, "Straight", {
121 x1:next[0], y1:next[1], x2:next[2], y2:next[3]
125 this.setSegments = function(s) {
126 userSuppliedSegments = s;
129 this.isEditable = function() { return true; };
132 Function: getOriginalSegments
133 Gets the segments before the addition of rounded corners. This is used by the flowchart
134 connector editor, since it only wants to concern itself with the original segments.
136 this.getOriginalSegments = function() {
137 return userSuppliedSegments || segments;
140 this._compute = function(paintInfo, params) {
142 if (params.clearEdits)
143 userSuppliedSegments = null;
145 if (userSuppliedSegments != null) {
146 writeSegments(this, userSuppliedSegments, paintInfo);
151 lastx = null; lasty = null;
152 lastOrientation = null;
154 var midx = paintInfo.startStubX + ((paintInfo.endStubX - paintInfo.startStubX) * midpoint),
155 midy = paintInfo.startStubY + ((paintInfo.endStubY - paintInfo.startStubY) * midpoint);
157 var findClearedLine = function(start, mult, anchorPos, dimension) {
158 return start + (mult * (( 1 - anchorPos) * dimension) + _super.maxStub);
160 orientations = { x:[ 0, 1 ], y:[ 1, 0 ] },
161 commonStubCalculator = function(axis) {
162 return [ paintInfo.startStubX, paintInfo.startStubY, paintInfo.endStubX, paintInfo.endStubY ];
165 perpendicular:commonStubCalculator,
166 orthogonal:commonStubCalculator,
167 opposite:function(axis) {
169 idx = axis == "x" ? 0 : 1,
172 return ( (pi.so[idx] == 1 && (
173 ( (pi.startStubX > pi.endStubX) && (pi.tx > pi.startStubX) ) ||
174 ( (pi.sx > pi.endStubX) && (pi.tx > pi.sx))))) ||
176 ( (pi.so[idx] == -1 && (
177 ( (pi.startStubX < pi.endStubX) && (pi.tx < pi.startStubX) ) ||
178 ( (pi.sx < pi.endStubX) && (pi.tx < pi.sx)))));
181 return ( (pi.so[idx] == 1 && (
182 ( (pi.startStubY > pi.endStubY) && (pi.ty > pi.startStubY) ) ||
183 ( (pi.sy > pi.endStubY) && (pi.ty > pi.sy))))) ||
185 ( (pi.so[idx] == -1 && (
186 ( (pi.startStubY < pi.endStubY) && (pi.ty < pi.startStubY) ) ||
187 ( (pi.sy < pi.endStubY) && (pi.ty < pi.sy)))));
191 if (!alwaysRespectStubs && areInProximity[axis]()) {
193 "x":[(paintInfo.sx + paintInfo.tx) / 2, paintInfo.startStubY, (paintInfo.sx + paintInfo.tx) / 2, paintInfo.endStubY],
194 "y":[paintInfo.startStubX, (paintInfo.sy + paintInfo.ty) / 2, paintInfo.endStubX, (paintInfo.sy + paintInfo.ty) / 2]
198 return [ paintInfo.startStubX, paintInfo.startStubY, paintInfo.endStubX, paintInfo.endStubY ];
203 perpendicular : function(axis, ss, oss, es, oes) {
206 x:[ [ [ 1,2,3,4 ], null, [ 2,1,4,3 ] ], null, [ [ 4,3,2,1 ], null, [ 3,4,1,2 ] ] ],
207 y:[ [ [ 3,2,1,4 ], null, [ 2,3,4,1 ] ], null, [ [ 4,1,2,3 ], null, [ 1,4,3,2 ] ] ]
210 x:[ [ pi.startStubX, pi.endStubX ] , null, [ pi.endStubX, pi.startStubX ] ],
211 y:[ [ pi.startStubY, pi.endStubY ] , null, [ pi.endStubY, pi.startStubY ] ]
214 x:[ [ midx, pi.startStubY ], [ midx, pi.endStubY ] ],
215 y:[ [ pi.startStubX, midy ], [ pi.endStubX, midy ] ]
218 x:[ [ pi.endStubX, pi.startStubY ] ],
219 y:[ [ pi.startStubX, pi.endStubY ] ]
222 x:[ [ pi.startStubX, pi.endStubY ], [ pi.endStubX, pi.endStubY ] ],
223 y:[ [ pi.endStubX, pi.startStubY ], [ pi.endStubX, pi.endStubY ] ]
226 x:[ [ pi.startStubX, midy ], [ pi.endStubX, midy ], [ pi.endStubX, pi.endStubY ] ],
227 y:[ [ midx, pi.startStubY ], [ midx, pi.endStubY ], [ pi.endStubX, pi.endStubY ] ]
230 x:[ pi.startStubY, pi.endStubY ],
231 y:[ pi.startStubX, pi.endStubX ]
233 soIdx = orientations[axis][0], toIdx = orientations[axis][1],
234 _so = pi.so[soIdx] + 1,
235 _to = pi.to[toIdx] + 1,
236 otherFlipped = (pi.to[toIdx] == -1 && (otherStubs[axis][1] < otherStubs[axis][0])) || (pi.to[toIdx] == 1 && (otherStubs[axis][1] > otherStubs[axis][0])),
237 stub1 = stubs[axis][_so][0],
238 stub2 = stubs[axis][_so][1],
239 segmentIndexes = sis[axis][_so][_to];
241 if (pi.segment == segmentIndexes[3] || (pi.segment == segmentIndexes[2] && otherFlipped)) {
242 return midLines[axis];
244 else if (pi.segment == segmentIndexes[2] && stub2 < stub1) {
245 return linesToEnd[axis];
247 else if ((pi.segment == segmentIndexes[2] && stub2 >= stub1) || (pi.segment == segmentIndexes[1] && !otherFlipped)) {
248 return startToMidToEnd[axis];
250 else if (pi.segment == segmentIndexes[0] || (pi.segment == segmentIndexes[1] && otherFlipped)) {
251 return startToEnd[axis];
254 orthogonal : function(axis, startStub, otherStartStub, endStub, otherEndStub) {
257 "x":pi.so[0] == -1 ? Math.min(startStub, endStub) : Math.max(startStub, endStub),
258 "y":pi.so[1] == -1 ? Math.min(startStub, endStub) : Math.max(startStub, endStub)
262 "x":[ [ extent, otherStartStub ],[ extent, otherEndStub ], [ endStub, otherEndStub ] ],
263 "y":[ [ otherStartStub, extent ], [ otherEndStub, extent ], [ otherEndStub, endStub ] ]
266 opposite : function(axis, ss, oss, es, oes) {
268 otherAxis = {"x":"y","y":"x"}[axis],
269 dim = {"x":"height","y":"width"}[axis],
270 comparator = pi["is" + axis.toUpperCase() + "GreaterThanStubTimes2"];
272 if (params.sourceEndpoint.elementId == params.targetEndpoint.elementId) {
273 var _val = oss + ((1 - params.sourceEndpoint.anchor[otherAxis]) * params.sourceInfo[dim]) + _super.maxStub;
275 "x":[ [ ss, _val ], [ es, _val ] ],
276 "y":[ [ _val, ss ], [ _val, es ] ]
280 else if (!comparator || (pi.so[idx] == 1 && ss > es) || (pi.so[idx] == -1 && ss < es)) {
282 "x":[[ ss, midy ], [ es, midy ]],
283 "y":[[ midx, ss ], [ midx, es ]]
286 else if ((pi.so[idx] == 1 && ss < es) || (pi.so[idx] == -1 && ss > es)) {
288 "x":[[ midx, pi.sy ], [ midx, pi.ty ]],
289 "y":[[ pi.sx, midy ], [ pi.tx, midy ]]
295 var stubs = stubCalculators[paintInfo.anchorOrientation](paintInfo.sourceAxis),
296 idx = paintInfo.sourceAxis == "x" ? 0 : 1,
297 oidx = paintInfo.sourceAxis == "x" ? 1 : 0,
301 oes = stubs[oidx + 2];
303 // add the start stub segment.
304 addSegment(segments, stubs[0], stubs[1], paintInfo);
306 // compute the rest of the line
307 var p = lineCalculators[paintInfo.anchorOrientation](paintInfo.sourceAxis, ss, oss, es, oes);
309 for (var i = 0; i < p.length; i++) {
310 addSegment(segments, p[i][0], p[i][1], paintInfo);
315 addSegment(segments, stubs[2], stubs[3], paintInfo);
318 addSegment(segments, paintInfo.tx, paintInfo.ty, paintInfo);
320 writeSegments(this, segments, paintInfo);
323 this.getPath = function() {
324 var _last = null, _lastAxis = null, s = [], segs = userSuppliedSegments || segments;
325 for (var i = 0; i < segs.length; i++) {
326 var seg = segs[i], axis = seg[4], axisIndex = (axis == "v" ? 3 : 2);
327 if (_last != null && _lastAxis === axis) {
328 _last[axisIndex] = seg[axisIndex];
331 if (seg[0] != seg[2] || seg[1] != seg[3]) {
333 start:[ seg[0], seg[1] ],
334 end:[ seg[2], seg[3] ]
344 this.setPath = function(path) {
345 userSuppliedSegments = [];
346 for (var i = 0; i < path.length; i++) {
347 var lx = path[i].start[0],
348 ly = path[i].start[1],
351 o = lx == x ? "v" : "h",
355 userSuppliedSegments.push([lx, ly, x, y, o, sgnx, sgny]);
360 jsPlumbUtil.extend(Flowchart, jsPlumb.Connectors.AbstractConnector);
361 jsPlumb.registerConnectorType(Flowchart, "Flowchart");