6 * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas
9 * This file contains the default Connectors, Endpoint and Overlay definitions.
11 * Copyright (c) 2010 - 2013 Simon Porritt (http://jsplumb.org)
14 * http://github.com/sporritt/jsplumb
15 * http://code.google.com/p/jsplumb
17 * Dual licensed under the MIT and GPL2 licenses.
24 * Helper class to consume unused mouse events by components that are DOM elements and
25 * are used by all of the different rendering modes.
28 jsPlumb.DOMElementComponent = jsPlumbUtil.extend(jsPlumb.jsPlumbUIComponent, function(params) {
29 // when render mode is canvas, these functions may be called by the canvas mouse handler.
30 // this component is safe to pipe this stuff to /dev/null.
35 this.mouseup = function(e) { };
41 * Class: AbstractSegment
42 * A Connector is made up of 1..N Segments, each of which has a Type, such as 'Straight', 'Arc',
43 * 'Bezier'. This is new from 1.4.2, and gives us a lot more flexibility when drawing connections: things such
44 * as rounded corners for flowchart connectors, for example, or a straight line stub for Bezier connections, are
45 * much easier to do now.
47 * A Segment is responsible for providing coordinates for painting it, and also must be able to report its length.
50 AbstractSegment : function(params) {
54 * Function: findClosestPointOnPath
55 * Finds the closest point on this segment to the given [x, y],
56 * returning both the x and y of the point plus its distance from
57 * the supplied point, and its location along the length of the
58 * path inscribed by the segment. This implementation returns
59 * Infinity for distance and null values for everything else;
60 * subclasses are expected to override.
62 this.findClosestPointOnPath = function(x, y) {
71 this.getBounds = function() {
73 minX:Math.min(params.x1, params.x2),
74 minY:Math.min(params.y1, params.y2),
75 maxX:Math.max(params.x1, params.x2),
76 maxY:Math.max(params.y1, params.y2)
80 Straight : function(params) {
81 var _super = jsPlumb.Segments.AbstractSegment.apply(this, arguments),
82 length, m, m2, x1, x2, y1, y2,
83 _recalc = function() {
84 length = Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
85 m = jsPlumbGeom.gradient({x:x1, y:y1}, {x:x2, y:y2});
89 this.type = "Straight";
91 this.getLength = function() { return length; };
92 this.getGradient = function() { return m; };
94 this.getCoordinates = function() {
95 return { x1:x1,y1:y1,x2:x2,y2:y2 };
97 this.setCoordinates = function(coords) {
98 x1 = coords.x1; y1 = coords.y1; x2 = coords.x2; y2 = coords.y2;
101 this.setCoordinates({x1:params.x1, y1:params.y1, x2:params.x2, y2:params.y2});
103 this.getBounds = function() {
105 minX:Math.min(x1, x2),
106 minY:Math.min(y1, y2),
107 maxX:Math.max(x1, x2),
108 maxY:Math.max(y1, y2)
113 * returns the point on the segment's path that is 'location' along the length of the path, where 'location' is a decimal from
114 * 0 to 1 inclusive. for the straight line segment this is simple maths.
116 this.pointOnPath = function(location, absolute) {
117 if (location === 0 && !absolute)
118 return { x:x1, y:y1 };
119 else if (location == 1 && !absolute)
120 return { x:x2, y:y2 };
122 var l = absolute ? location > 0 ? location : length + location : location * length;
123 return jsPlumbGeom.pointOnLine({x:x1, y:y1}, {x:x2, y:y2}, l);
128 * returns the gradient of the segment at the given point - which for us is constant.
130 this.gradientAtPoint = function(_) {
135 * returns the point on the segment's path that is 'distance' along the length of the path from 'location', where
136 * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels.
137 * this hands off to jsPlumbUtil to do the maths, supplying two points and the distance.
139 this.pointAlongPathFrom = function(location, distance, absolute) {
140 var p = this.pointOnPath(location, absolute),
141 farAwayPoint = distance <= 0 ? {x:x1, y:y1} : {x:x2, y:y2 };
145 x:x1 + ((x2 - x1) * 10),
146 y:y1 + ((y1 - y2) * 10)
150 if (distance <= 0 && Math.abs(distance) > 1) distance *= -1;
152 return jsPlumbGeom.pointOnLine(p, farAwayPoint, distance);
155 // is c between a and b?
156 var within = function(a,b,c) {
157 return c >= Math.min(a,b) && c <= Math.max(a,b);
159 // find which of a and b is closest to c
160 var closest = function(a,b,c) {
161 return Math.abs(c - a) < Math.abs(c - b) ? a : b;
165 Function: findClosestPointOnPath
166 Finds the closest point on this segment to [x,y]. See
167 notes on this method in AbstractSegment.
169 this.findClosestPointOnPath = function(x, y) {
183 out.x = within(x1, x2, x) ? x : closest(x1, x2, x);
185 else if (m == Infinity || m == -Infinity) {
187 out.y = within(y1, y2, y) ? y : closest(y1, y2, y);
190 // closest point lies on normal from given point to this line.
191 var b = y1 - (m * x1),
193 // y1 = m.x1 + b and y1 = m2.x1 + b2
194 // so m.x1 + b = m2.x1 + b2
195 // x1(m - m2) = b2 - b
196 // x1 = (b2 - b) / (m - m2)
197 _x1 = (b2 -b) / (m - m2),
200 out.x = within(x1,x2,_x1) ? _x1 : closest(x1,x2,_x1);//_x1;
201 out.y = within(y1,y2,_y1) ? _y1 : closest(y1,y2,_y1);//_y1;
204 var fractionInSegment = jsPlumbGeom.lineLength([ out.x, out.y ], [ x1, y1 ]);
205 out.d = jsPlumbGeom.lineLength([x,y], [out.x, out.y]);
206 out.l = fractionInSegment / length;
212 Arc Segment. You need to supply:
215 cx - center x for the arc
216 cy - center y for the arc
217 ac - whether the arc is anticlockwise or not. default is clockwise.
221 startAngle - startAngle for the arc.
222 endAngle - endAngle for the arc.
226 x1 - x for start point
227 y1 - y for start point
232 Arc : function(params) {
233 var _super = jsPlumb.Segments.AbstractSegment.apply(this, arguments),
234 _calcAngle = function(_x, _y) {
235 return jsPlumbGeom.theta([params.cx, params.cy], [_x, _y]);
237 _calcAngleForLocation = function(segment, location) {
238 if (segment.anticlockwise) {
239 var sa = segment.startAngle < segment.endAngle ? segment.startAngle + TWO_PI : segment.startAngle,
240 s = Math.abs(sa - segment.endAngle);
241 return sa - (s * location);
244 var ea = segment.endAngle < segment.startAngle ? segment.endAngle + TWO_PI : segment.endAngle,
245 ss = Math.abs (ea - segment.startAngle);
247 return segment.startAngle + (ss * location);
250 TWO_PI = 2 * Math.PI;
252 this.radius = params.r;
253 this.anticlockwise = params.ac;
256 if (params.startAngle && params.endAngle) {
257 this.startAngle = params.startAngle;
258 this.endAngle = params.endAngle;
259 this.x1 = params.cx + (this.radius * Math.cos(params.startAngle));
260 this.y1 = params.cy + (this.radius * Math.sin(params.startAngle));
261 this.x2 = params.cx + (this.radius * Math.cos(params.endAngle));
262 this.y2 = params.cy + (this.radius * Math.sin(params.endAngle));
265 this.startAngle = _calcAngle(params.x1, params.y1);
266 this.endAngle = _calcAngle(params.x2, params.y2);
273 if (this.endAngle < 0) this.endAngle += TWO_PI;
274 if (this.startAngle < 0) this.startAngle += TWO_PI;
276 // segment is used by vml
277 this.segment = jsPlumbGeom.quadrant([this.x1, this.y1], [this.x2, this.y2]);
279 // we now have startAngle and endAngle as positive numbers, meaning the
280 // absolute difference (|d|) between them is the sweep (s) of this arc, unless the
281 // arc is 'anticlockwise' in which case 's' is given by 2PI - |d|.
283 var ea = this.endAngle < this.startAngle ? this.endAngle + TWO_PI : this.endAngle;
284 this.sweep = Math.abs (ea - this.startAngle);
285 if (this.anticlockwise) this.sweep = TWO_PI - this.sweep;
286 var circumference = 2 * Math.PI * this.radius,
287 frac = this.sweep / TWO_PI,
288 length = circumference * frac;
290 this.getLength = function() {
294 this.getBounds = function() {
296 minX:params.cx - params.r,
297 maxX:params.cx + params.r,
298 minY:params.cy - params.r,
299 maxY:params.cy + params.r
303 var VERY_SMALL_VALUE = 0.0000000001,
304 gentleRound = function(n) {
305 var f = Math.floor(n), r = Math.ceil(n);
306 if (n - f < VERY_SMALL_VALUE)
308 else if (r - n < VERY_SMALL_VALUE)
314 * returns the point on the segment's path that is 'location' along the length of the path, where 'location' is a decimal from
317 this.pointOnPath = function(location, absolute) {
319 if (location === 0) {
320 return { x:this.x1, y:this.y1, theta:this.startAngle };
322 else if (location == 1) {
323 return { x:this.x2, y:this.y2, theta:this.endAngle };
327 location = location / length;
330 var angle = _calcAngleForLocation(this, location),
331 _x = params.cx + (params.r * Math.cos(angle)),
332 _y = params.cy + (params.r * Math.sin(angle));
334 return { x:gentleRound(_x), y:gentleRound(_y), theta:angle };
338 * returns the gradient of the segment at the given point.
340 this.gradientAtPoint = function(location, absolute) {
341 var p = this.pointOnPath(location, absolute);
342 var m = jsPlumbGeom.normal( [ params.cx, params.cy ], [p.x, p.y ] );
343 if (!this.anticlockwise && (m == Infinity || m == -Infinity)) m *= -1;
347 this.pointAlongPathFrom = function(location, distance, absolute) {
348 var p = this.pointOnPath(location, absolute),
349 arcSpan = distance / circumference * 2 * Math.PI,
350 dir = this.anticlockwise ? -1 : 1,
351 startAngle = p.theta + (dir * arcSpan),
352 startX = params.cx + (this.radius * Math.cos(startAngle)),
353 startY = params.cy + (this.radius * Math.sin(startAngle));
355 return {x:startX, y:startY};
359 Bezier : function(params) {
360 var _super = jsPlumb.Segments.AbstractSegment.apply(this, arguments),
362 { x:params.x1, y:params.y1},
363 { x:params.cp1x, y:params.cp1y },
364 { x:params.cp2x, y:params.cp2y },
365 { x:params.x2, y:params.y2 }
367 // although this is not a strictly rigorous determination of bounds
368 // of a bezier curve, it works for the types of curves that this segment
371 minX:Math.min(params.x1, params.x2, params.cp1x, params.cp2x),
372 minY:Math.min(params.y1, params.y2, params.cp1y, params.cp2y),
373 maxX:Math.max(params.x1, params.x2, params.cp1x, params.cp2x),
374 maxY:Math.max(params.y1, params.y2, params.cp1y, params.cp2y)
377 this.type = "Bezier";
379 var _translateLocation = function(_curve, location, absolute) {
381 location = jsBezier.locationAlongCurveFrom(_curve, location > 0 ? 0 : 1, location);
387 * returns the point on the segment's path that is 'location' along the length of the path, where 'location' is a decimal from
390 this.pointOnPath = function(location, absolute) {
391 location = _translateLocation(curve, location, absolute);
392 return jsBezier.pointOnCurve(curve, location);
396 * returns the gradient of the segment at the given point.
398 this.gradientAtPoint = function(location, absolute) {
399 location = _translateLocation(curve, location, absolute);
400 return jsBezier.gradientAtPoint(curve, location);
403 this.pointAlongPathFrom = function(location, distance, absolute) {
404 location = _translateLocation(curve, location, absolute);
405 return jsBezier.pointAlongCurveFrom(curve, location, distance);
408 this.getLength = function() {
409 return jsBezier.getLength(curve);
412 this.getBounds = function() {
419 Class: AbstractComponent
420 Superclass for AbstractConnector and AbstractEndpoint.
422 var AbstractComponent = function() {
423 this.resetBounds = function() {
424 this.bounds = { minX:Infinity, minY:Infinity, maxX:-Infinity, maxY:-Infinity };
430 * Class: AbstractConnector
431 * Superclass for all Connectors; here is where Segments are managed. This is exposed on jsPlumb just so it
432 * can be accessed from other files. You should not try to instantiate one of these directly.
434 * When this class is asked for a pointOnPath, or gradient etc, it must first figure out which segment to dispatch
435 * that request to. This is done by keeping track of the total connector length as segments are added, and also
436 * their cumulative ratios to the total length. Then when the right segment is found it is a simple case of dispatching
437 * the request to it (and adjusting 'location' so that it is relative to the beginning of that segment.)
439 jsPlumb.Connectors.AbstractConnector = function(params) {
441 AbstractComponent.apply(this, arguments);
447 segmentProportions = [],
448 segmentProportionalLengths = [],
449 stub = params.stub || 0,
450 sourceStub = jsPlumbUtil.isArray(stub) ? stub[0] : stub,
451 targetStub = jsPlumbUtil.isArray(stub) ? stub[1] : stub,
452 gap = params.gap || 0,
453 sourceGap = jsPlumbUtil.isArray(gap) ? gap[0] : gap,
454 targetGap = jsPlumbUtil.isArray(gap) ? gap[1] : gap,
455 userProvidedSegments = null,
459 // subclasses should override.
460 this.isEditable = function() { return false; };
461 this.setEdited = function(ed) { edited = ed; };
463 // to be overridden by subclasses.
464 this.getPath = function() { };
465 this.setPath = function(path) { };
468 * Function: findSegmentForPoint
469 * Returns the segment that is closest to the given [x,y],
470 * null if nothing found. This function returns a JS
473 * d - distance from segment
474 * l - proportional location in segment
475 * x - x point on the segment
476 * y - y point on the segment
477 * s - the segment itself.
479 this.findSegmentForPoint = function(x, y) {
480 var out = { d:Infinity, s:null, x:null, y:null, l:null };
481 for (var i = 0; i < segments.length; i++) {
482 var _s = segments[i].findClosestPointOnPath(x, y);
500 var _updateSegmentProportions = function() {
502 for (var i = 0; i < segments.length; i++) {
503 var sl = segments[i].getLength();
504 segmentProportionalLengths[i] = sl / totalLength;
505 segmentProportions[i] = [curLoc, (curLoc += (sl / totalLength)) ];
510 * returns [segment, proportion of travel in segment, segment index] for the segment
511 * that contains the point which is 'location' distance along the entire path, where
512 * 'location' is a decimal between 0 and 1 inclusive. in this connector type, paths
513 * are made up of a list of segments, each of which contributes some fraction to
515 * From 1.3.10 this also supports the 'absolute' property, which lets us specify a location
516 * as the absolute distance in pixels, rather than a proportion of the total path.
518 _findSegmentForLocation = function(location, absolute) {
520 location = location > 0 ? location / totalLength : (totalLength + location) / totalLength;
523 var idx = segmentProportions.length - 1, inSegmentProportion = 1;
524 //if (location < 1) {
525 for (var i = 0; i < segmentProportions.length; i++) {
526 if (segmentProportions[i][1] >= location) {
528 // todo is this correct for all connector path types?
529 inSegmentProportion = location == 1 ? 1 : location === 0 ? 0 : (location - segmentProportions[i][0]) / segmentProportionalLengths[i];
534 return { segment:segments[idx], proportion:inSegmentProportion, index:idx };
536 _addSegment = function(conn, type, params) {
537 if (params.x1 == params.x2 && params.y1 == params.y2) return;
538 var s = new jsPlumb.Segments[type](params);
540 totalLength += s.getLength();
541 conn.updateBounds(s);
543 _clearSegments = function() {
545 segments.splice(0, segments.length);
546 segmentProportions.splice(0, segmentProportions.length);
547 segmentProportionalLengths.splice(0, segmentProportionalLengths.length);
550 this.setSegments = function(_segs) {
551 userProvidedSegments = [];
553 for (var i = 0; i < _segs.length; i++) {
554 userProvidedSegments.push(_segs[i]);
555 totalLength += _segs[i].getLength();
559 var _prepareCompute = function(params) {
560 this.lineWidth = params.lineWidth;
561 var segment = jsPlumbGeom.quadrant(params.sourcePos, params.targetPos),
562 swapX = params.targetPos[0] < params.sourcePos[0],
563 swapY = params.targetPos[1] < params.sourcePos[1],
564 lw = params.lineWidth || 1,
565 so = params.sourceEndpoint.anchor.getOrientation(params.sourceEndpoint),
566 to = params.targetEndpoint.anchor.getOrientation(params.targetEndpoint),
567 x = swapX ? params.targetPos[0] : params.sourcePos[0],
568 y = swapY ? params.targetPos[1] : params.sourcePos[1],
569 w = Math.abs(params.targetPos[0] - params.sourcePos[0]),
570 h = Math.abs(params.targetPos[1] - params.sourcePos[1]);
572 // if either anchor does not have an orientation set, we derive one from their relative
573 // positions. we fix the axis to be the one in which the two elements are further apart, and
574 // point each anchor at the other element. this is also used when dragging a new connection.
575 if (so[0] === 0 && so[1] === 0 || to[0] === 0 && to[1] === 0) {
576 var index = w > h ? 0 : 1, oIndex = [1,0][index];
578 so[index] = params.sourcePos[index] > params.targetPos[index] ? -1 : 1;
579 to[index] = params.sourcePos[index] > params.targetPos[index] ? 1 : -1;
580 so[oIndex] = 0; to[oIndex] = 0;
583 var sx = swapX ? w + (sourceGap * so[0]) : sourceGap * so[0],
584 sy = swapY ? h + (sourceGap * so[1]) : sourceGap * so[1],
585 tx = swapX ? targetGap * to[0] : w + (targetGap * to[0]),
586 ty = swapY ? targetGap * to[1] : h + (targetGap * to[1]),
587 oProduct = ((so[0] * to[0]) + (so[1] * to[1]));
590 sx:sx, sy:sy, tx:tx, ty:ty, lw:lw,
591 xSpan:Math.abs(tx - sx),
592 ySpan:Math.abs(ty - sy),
595 so:so, to:to, x:x, y:y, w:w, h:h,
597 startStubX : sx + (so[0] * sourceStub),
598 startStubY : sy + (so[1] * sourceStub),
599 endStubX : tx + (to[0] * targetStub),
600 endStubY : ty + (to[1] * targetStub),
601 isXGreaterThanStubTimes2 : Math.abs(sx - tx) > (sourceStub + targetStub),
602 isYGreaterThanStubTimes2 : Math.abs(sy - ty) > (sourceStub + targetStub),
603 opposite:oProduct == -1,
604 perpendicular:oProduct === 0,
605 orthogonal:oProduct == 1,
606 sourceAxis : so[0] === 0 ? "y" : "x",
607 points:[x, y, w, h, sx, sy, tx, ty ]
609 result.anchorOrientation = result.opposite ? "opposite" : result.orthogonal ? "orthogonal" : "perpendicular";
613 this.getSegments = function() { return segments; };
615 this.updateBounds = function(segment) {
616 var segBounds = segment.getBounds();
617 this.bounds.minX = Math.min(this.bounds.minX, segBounds.minX);
618 this.bounds.maxX = Math.max(this.bounds.maxX, segBounds.maxX);
619 this.bounds.minY = Math.min(this.bounds.minY, segBounds.minY);
620 this.bounds.maxY = Math.max(this.bounds.maxY, segBounds.maxY);
623 var dumpSegmentsToConsole = function() {
624 console.log("SEGMENTS:");
625 for (var i = 0; i < segments.length; i++) {
626 console.log(segments[i].type, segments[i].getLength(), segmentProportions[i]);
630 this.pointOnPath = function(location, absolute) {
631 var seg = _findSegmentForLocation(location, absolute);
632 return seg.segment && seg.segment.pointOnPath(seg.proportion, absolute) || [0,0];
635 this.gradientAtPoint = function(location) {
636 var seg = _findSegmentForLocation(location, absolute);
637 return seg.segment && seg.segment.gradientAtPoint(seg.proportion, absolute) || 0;
640 this.pointAlongPathFrom = function(location, distance, absolute) {
641 var seg = _findSegmentForLocation(location, absolute);
642 // TODO what happens if this crosses to the next segment?
643 return seg.segment && seg.segment.pointAlongPathFrom(seg.proportion, distance, false) || [0,0];
646 this.compute = function(params) {
648 paintInfo = _prepareCompute.call(this, params);
651 this._compute(paintInfo, params);
652 this.x = paintInfo.points[0];
653 this.y = paintInfo.points[1];
654 this.w = paintInfo.points[2];
655 this.h = paintInfo.points[3];
656 this.segment = paintInfo.segment;
657 _updateSegmentProportions();
661 addSegment:_addSegment,
662 prepareCompute:_prepareCompute,
663 sourceStub:sourceStub,
664 targetStub:targetStub,
665 maxStub:Math.max(sourceStub, targetStub),
668 maxGap:Math.max(sourceGap, targetGap)
671 jsPlumbUtil.extend(jsPlumb.Connectors.AbstractConnector, AbstractComponent);
674 * Class: Connectors.Straight
675 * The Straight connector draws a simple straight line between the two anchor points. It does not have any constructor parameters.
677 var Straight = jsPlumb.Connectors.Straight = function() {
678 this.type = "Straight";
679 var _super = jsPlumb.Connectors.AbstractConnector.apply(this, arguments);
681 this._compute = function(paintInfo, _) {
682 _super.addSegment(this, "Straight", {x1:paintInfo.sx, y1:paintInfo.sy, x2:paintInfo.startStubX, y2:paintInfo.startStubY});
683 _super.addSegment(this, "Straight", {x1:paintInfo.startStubX, y1:paintInfo.startStubY, x2:paintInfo.endStubX, y2:paintInfo.endStubY});
684 _super.addSegment(this, "Straight", {x1:paintInfo.endStubX, y1:paintInfo.endStubY, x2:paintInfo.tx, y2:paintInfo.ty});
687 jsPlumbUtil.extend(jsPlumb.Connectors.Straight, jsPlumb.Connectors.AbstractConnector);
688 jsPlumb.registerConnectorType(Straight, "Straight");
691 * Class:Connectors.Bezier
692 * This Connector draws a Bezier curve with two control points. You can provide a 'curviness' value which gets applied to jsPlumb's
693 * internal voodoo machine and ends up generating locations for the two control points. See the constructor documentation below.
696 * Function:Constructor
699 * 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
700 * 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.
701 * Optional; defaults to 150.
702 * stub - optional value for a distance to travel from the connector's endpoint before beginning the Bezier curve. defaults to 0.
705 var Bezier = function(params) {
706 params = params || {};
708 var _super = jsPlumb.Connectors.AbstractConnector.apply(this, arguments),
709 stub = params.stub || 50,
710 majorAnchor = params.curviness || 150,
713 this.type = "Bezier";
714 this.getCurviness = function() { return majorAnchor; };
716 this._findControlPoint = function(point, sourceAnchorPosition, targetAnchorPosition, sourceEndpoint, targetEndpoint) {
717 // determine if the two anchors are perpendicular to each other in their orientation. we swap the control
718 // points around if so (code could be tightened up)
719 var soo = sourceEndpoint.anchor.getOrientation(sourceEndpoint),
720 too = targetEndpoint.anchor.getOrientation(targetEndpoint),
721 perpendicular = soo[0] != too[0] || soo[1] == too[1],
724 if (!perpendicular) {
725 if (soo[0] === 0) // X
726 p.push(sourceAnchorPosition[0] < targetAnchorPosition[0] ? point[0] + minorAnchor : point[0] - minorAnchor);
727 else p.push(point[0] - (majorAnchor * soo[0]));
729 if (soo[1] === 0) // Y
730 p.push(sourceAnchorPosition[1] < targetAnchorPosition[1] ? point[1] + minorAnchor : point[1] - minorAnchor);
731 else p.push(point[1] + (majorAnchor * too[1]));
734 if (too[0] === 0) // X
735 p.push(targetAnchorPosition[0] < sourceAnchorPosition[0] ? point[0] + minorAnchor : point[0] - minorAnchor);
736 else p.push(point[0] + (majorAnchor * too[0]));
738 if (too[1] === 0) // Y
739 p.push(targetAnchorPosition[1] < sourceAnchorPosition[1] ? point[1] + minorAnchor : point[1] - minorAnchor);
740 else p.push(point[1] + (majorAnchor * soo[1]));
746 this._compute = function(paintInfo, p) {
747 var sp = p.sourcePos,
749 _w = Math.abs(sp[0] - tp[0]),
750 _h = Math.abs(sp[1] - tp[1]),
751 _sx = sp[0] < tp[0] ? _w : 0,
752 _sy = sp[1] < tp[1] ? _h : 0,
753 _tx = sp[0] < tp[0] ? 0 : _w,
754 _ty = sp[1] < tp[1] ? 0 : _h,
755 _CP = this._findControlPoint([_sx, _sy], sp, tp, p.sourceEndpoint, p.targetEndpoint),
756 _CP2 = this._findControlPoint([_tx, _ty], tp, sp, p.targetEndpoint, p.sourceEndpoint);
758 _super.addSegment(this, "Bezier", {
759 x1:_sx, y1:_sy, x2:_tx, y2:_ty,
760 cp1x:_CP[0], cp1y:_CP[1], cp2x:_CP2[0], cp2y:_CP2[1]
764 jsPlumbUtil.extend(Bezier, jsPlumb.Connectors.AbstractConnector);
765 jsPlumb.registerConnectorType(Bezier, "Bezier");
767 // ********************************* END OF CONNECTOR TYPES *******************************************************************
769 // ********************************* ENDPOINT TYPES *******************************************************************
771 jsPlumb.Endpoints.AbstractEndpoint = function(params) {
772 AbstractComponent.apply(this, arguments);
773 var compute = this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) {
774 var out = this._compute.apply(this, arguments);
779 this.bounds.minX = this.x;
780 this.bounds.minY = this.y;
781 this.bounds.maxX = this.x + this.w;
782 this.bounds.maxY = this.y + this.h;
787 cssClass:params.cssClass
790 jsPlumbUtil.extend(jsPlumb.Endpoints.AbstractEndpoint, AbstractComponent);
793 * Class: Endpoints.Dot
794 * A round endpoint, with default radius 10 pixels.
798 * Function: Constructor
802 * radius - radius of the endpoint. defaults to 10 pixels.
804 jsPlumb.Endpoints.Dot = function(params) {
806 var _super = jsPlumb.Endpoints.AbstractEndpoint.apply(this, arguments);
807 params = params || {};
808 this.radius = params.radius || 10;
809 this.defaultOffset = 0.5 * this.radius;
810 this.defaultInnerRadius = this.radius / 3;
812 this._compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) {
813 this.radius = endpointStyle.radius || this.radius;
814 var x = anchorPoint[0] - this.radius,
815 y = anchorPoint[1] - this.radius,
819 if (endpointStyle.strokeStyle) {
820 var lw = endpointStyle.lineWidth || 1;
826 return [ x, y, w, h, this.radius ];
829 jsPlumbUtil.extend(jsPlumb.Endpoints.Dot, jsPlumb.Endpoints.AbstractEndpoint);
832 * Class: Endpoints.Rectangle
833 * A Rectangular Endpoint, with default size 20x20.
836 * Function: Constructor
840 * width - width of the endpoint. defaults to 20 pixels.
841 * height - height of the endpoint. defaults to 20 pixels.
843 jsPlumb.Endpoints.Rectangle = function(params) {
844 this.type = "Rectangle";
845 var _super = jsPlumb.Endpoints.AbstractEndpoint.apply(this, arguments);
846 params = params || {};
847 this.width = params.width || 20;
848 this.height = params.height || 20;
850 this._compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) {
851 var width = endpointStyle.width || this.width,
852 height = endpointStyle.height || this.height,
853 x = anchorPoint[0] - (width/2),
854 y = anchorPoint[1] - (height/2);
856 return [ x, y, width, height];
859 jsPlumbUtil.extend(jsPlumb.Endpoints.Rectangle, jsPlumb.Endpoints.AbstractEndpoint);
862 var DOMElementEndpoint = function(params) {
863 jsPlumb.DOMElementComponent.apply(this, arguments);
864 this._jsPlumb.displayElements = [ ];
866 jsPlumbUtil.extend(DOMElementEndpoint, jsPlumb.DOMElementComponent, {
867 // jsPlumb.Endpoints.AbstractEndpoint
868 getDisplayElements : function() {
869 return this._jsPlumb.displayElements;
871 appendDisplayElement : function(el) {
872 this._jsPlumb.displayElements.push(el);
877 * Class: Endpoints.Image
878 * Draws an image as the Endpoint.
881 * Function: Constructor
885 * src - location of the image to use.
887 TODO: multiple references to self. not sure quite how to get rid of them entirely. perhaps self = null in the cleanup
888 function will suffice
890 TODO this class still leaks memory.
893 jsPlumb.Endpoints.Image = function(params) {
896 DOMElementEndpoint.apply(this, arguments);
897 jsPlumb.Endpoints.AbstractEndpoint.apply(this, arguments);
899 var _onload = params.onload,
900 src = params.src || params.url,
901 parent = params.parent,
902 clazz = params.cssClass ? " " + params.cssClass : "";
904 this._jsPlumb.img = new Image();
905 this._jsPlumb.ready = false;
906 this._jsPlumb.initialized = false;
907 this._jsPlumb.deleted = false;
908 this._jsPlumb.widthToUse = params.width;
909 this._jsPlumb.heightToUse = params.height;
910 this._jsPlumb.endpoint = params.endpoint;
912 this._jsPlumb.img.onload = function() {
913 // check we weren't actually discarded before use (in fact mostly happens in tests)
914 if (this._jsPlumb != null) {
915 this._jsPlumb.ready = true;
916 this._jsPlumb.widthToUse = this._jsPlumb.widthToUse || this._jsPlumb.img.width;
917 this._jsPlumb.heightToUse = this._jsPlumb.heightToUse || this._jsPlumb.img.height;
926 Sets the Image to use in this Endpoint.
929 img - may be a URL or an Image object
930 onload - optional; a callback to execute once the image has loaded.
932 this._jsPlumb.endpoint.setImage = function(_img, onload) {
933 var s = _img.constructor == String ? _img : _img.src;
935 this._jsPlumb.img.src = s;
937 if (this.canvas != null)
938 this.canvas.setAttribute("src", this._jsPlumb.img.src);
941 this._jsPlumb.endpoint.setImage(src, _onload);
943 var s = src.constructor == String ? src : src.src;
945 this._jsPlumb.img.src = src;
947 if (this.canvas != null)
948 this.canvas.setAttribute("src", this._jsPlumb.img.src);
951 //this._jsPlumb.endpoint.setImage(src, _onload);*/
953 this._compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) {
954 this.anchorPoint = anchorPoint;
955 if (this._jsPlumb.ready) return [anchorPoint[0] - this._jsPlumb.widthToUse / 2, anchorPoint[1] - this._jsPlumb.heightToUse / 2,
956 this._jsPlumb.widthToUse, this._jsPlumb.heightToUse];
957 else return [0,0,0,0];
960 this.canvas = document.createElement("img");
961 this.canvas.style.margin = 0;
962 this.canvas.style.padding = 0;
963 this.canvas.style.outline = 0;
964 this.canvas.style.position = "absolute";
965 this.canvas.className = this._jsPlumb.instance.endpointClass + clazz;
966 if (this._jsPlumb.widthToUse) this.canvas.setAttribute("width", this._jsPlumb.widthToUse);
967 if (this._jsPlumb.heightToUse) this.canvas.setAttribute("height", this._jsPlumb.heightToUse);
968 this._jsPlumb.instance.appendElement(this.canvas, parent);
969 this.attachListeners(this.canvas, this);
971 this.actuallyPaint = function(d, style, anchor) {
972 if (!this._jsPlumb.deleted) {
973 if (!this._jsPlumb.initialized) {
974 this.canvas.setAttribute("src", this._jsPlumb.img.src);
975 this.appendDisplayElement(this.canvas);
976 this._jsPlumb.initialized = true;
978 var x = this.anchorPoint[0] - (this._jsPlumb.widthToUse / 2),
979 y = this.anchorPoint[1] - (this._jsPlumb.heightToUse / 2);
980 jsPlumbUtil.sizeElement(this.canvas, x, y, this._jsPlumb.widthToUse, this._jsPlumb.heightToUse);
984 this.paint = function(style, anchor) {
985 if (this._jsPlumb != null) { // may have been deleted
986 if (this._jsPlumb.ready) {
987 this.actuallyPaint(style, anchor);
990 window.setTimeout(function() {
991 this.paint(style, anchor);
997 jsPlumbUtil.extend(jsPlumb.Endpoints.Image, [ DOMElementEndpoint, jsPlumb.Endpoints.AbstractEndpoint ], {
998 cleanup : function() {
999 this._jsPlumb.deleted = true;
1000 jsPlumbUtil.removeElement(this.canvas);
1006 * Class: Endpoints.Blank
1007 * An Endpoint that paints nothing (visible) on the screen. Supports cssClass and hoverClass parameters like all Endpoints.
1009 jsPlumb.Endpoints.Blank = function(params) {
1010 var _super = jsPlumb.Endpoints.AbstractEndpoint.apply(this, arguments);
1011 this.type = "Blank";
1012 DOMElementEndpoint.apply(this, arguments);
1013 this._compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) {
1014 return [anchorPoint[0], anchorPoint[1],10,0];
1017 this.canvas = document.createElement("div");
1018 this.canvas.style.display = "block";
1019 this.canvas.style.width = "1px";
1020 this.canvas.style.height = "1px";
1021 this.canvas.style.background = "transparent";
1022 this.canvas.style.position = "absolute";
1023 this.canvas.className = this._jsPlumb.endpointClass;
1024 jsPlumb.appendElement(this.canvas, params.parent);
1026 this.paint = function(style, anchor) {
1027 jsPlumbUtil.sizeElement(this.canvas, this.x, this.y, this.w, this.h);
1030 jsPlumbUtil.extend(jsPlumb.Endpoints.Blank, [jsPlumb.Endpoints.AbstractEndpoint, DOMElementEndpoint], {
1031 cleanup:function() {
1032 if (this.canvas && this.canvas.parentNode) {
1033 this.canvas.parentNode.removeChild(this.canvas);
1039 * Class: Endpoints.Triangle
1040 * A triangular Endpoint.
1043 * Function: Constructor
1047 * width - width of the triangle's base. defaults to 55 pixels.
1048 * height - height of the triangle from base to apex. defaults to 55 pixels.
1050 jsPlumb.Endpoints.Triangle = function(params) {
1051 this.type = "Triangle";
1052 var _super = jsPlumb.Endpoints.AbstractEndpoint.apply(this, arguments);
1053 params = params || { };
1054 params.width = params.width || 55;
1055 params.height = params.height || 55;
1056 this.width = params.width;
1057 this.height = params.height;
1058 this._compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) {
1059 var width = endpointStyle.width || self.width,
1060 height = endpointStyle.height || self.height,
1061 x = anchorPoint[0] - (width/2),
1062 y = anchorPoint[1] - (height/2);
1063 return [ x, y, width, height ];
1066 // ********************************* END OF ENDPOINT TYPES *******************************************************************
1069 // ********************************* OVERLAY DEFINITIONS ***********************************************************************
1071 var AbstractOverlay = jsPlumb.Overlays.AbstractOverlay = function(params) {
1072 this.visible = true;
1073 this.isAppendedAtTopLevel = true;
1074 this.component = params.component;
1075 this.loc = params.location == null ? 0.5 : params.location;
1076 this.endpointLoc = params.endpointLocation == null ? [ 0.5, 0.5] : params.endpointLocation;
1078 AbstractOverlay.prototype = {
1079 cleanup:function() {
1080 this.component = null;
1082 this.endpointLoc = null;
1084 setVisible : function(val) {
1086 // TODO this is only actually necessary for canvas. so, the Canvas overlay should
1087 // override setVisible and call this.
1088 //this.component.repaint();
1090 isVisible : function() { return this.visible; },
1091 hide : function() { this.setVisible(false); },
1092 show : function() { this.setVisible(true); },
1093 incrementLocation : function(amount) {
1095 this.component.repaint();
1097 setLocation : function(l) {
1099 this.component.repaint();
1101 getLocation : function() {
1108 * Class: Overlays.Arrow
1110 * 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
1111 * of the arrow that lines from each tail point converge into. The foldback point is defined using a decimal that indicates some fraction
1112 * 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
1116 * Function: Constructor
1120 * length - distance in pixels from head to tail baseline. default 20.
1121 * width - width in pixels of the tail baseline. default 20.
1122 * fillStyle - style to use when filling the arrow. defaults to "black".
1123 * strokeStyle - style to use when stroking the arrow. defaults to null, which means the arrow is not stroked.
1124 * lineWidth - line width to use when stroking the arrow. defaults to 1, but only used if strokeStyle is not null.
1125 * 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.
1126 * location - distance (as a decimal from 0 to 1 inclusive) marking where the arrow should sit on the connector. defaults to 0.5.
1127 * direction - indicates the direction the arrow points in. valid values are -1 and 1; 1 is default.
1129 jsPlumb.Overlays.Arrow = function(params) {
1130 this.type = "Arrow";
1131 AbstractOverlay.apply(this, arguments);
1132 this.isAppendedAtTopLevel = false;
1133 params = params || {};
1134 var _ju = jsPlumbUtil, _jg = jsPlumbGeom;
1136 this.length = params.length || 20;
1137 this.width = params.width || 20;
1138 this.id = params.id;
1139 var direction = (params.direction || 1) < 0 ? -1 : 1,
1140 paintStyle = params.paintStyle || { lineWidth:1 },
1141 // how far along the arrow the lines folding back in come to. default is 62.3%.
1142 foldback = params.foldback || 0.623;
1144 this.computeMaxSize = function() { return self.width * 1.5; };
1145 //this.cleanup = function() { }; // nothing to clean up for Arrows
1146 this.draw = function(component, currentConnectionPaintStyle) {
1148 var hxy, mid, txy, tail, cxy;
1149 if (component.pointAlongPathFrom) {
1151 if (_ju.isString(this.loc) || this.loc > 1 || this.loc < 0) {
1152 var l = parseInt(this.loc, 10),
1153 fromLoc = this.loc < 0 ? 1 : 0;
1154 hxy = component.pointAlongPathFrom(fromLoc, l, false);
1155 mid = component.pointAlongPathFrom(fromLoc, l - (direction * this.length / 2), false);
1156 txy = _jg.pointOnLine(hxy, mid, this.length);
1158 else if (this.loc == 1) {
1159 hxy = component.pointOnPath(this.loc);
1160 mid = component.pointAlongPathFrom(this.loc, -(this.length));
1161 txy = _jg.pointOnLine(hxy, mid, this.length);
1163 if (direction == -1) {
1169 else if (this.loc === 0) {
1170 txy = component.pointOnPath(this.loc);
1171 mid = component.pointAlongPathFrom(this.loc, this.length);
1172 hxy = _jg.pointOnLine(txy, mid, this.length);
1173 if (direction == -1) {
1180 hxy = component.pointAlongPathFrom(this.loc, direction * this.length / 2);
1181 mid = component.pointOnPath(this.loc);
1182 txy = _jg.pointOnLine(hxy, mid, this.length);
1185 tail = _jg.perpendicularLineTo(hxy, txy, this.width);
1186 cxy = _jg.pointOnLine(hxy, txy, foldback * this.length);
1188 var d = { hxy:hxy, tail:tail, cxy:cxy },
1189 strokeStyle = paintStyle.strokeStyle || currentConnectionPaintStyle.strokeStyle,
1190 fillStyle = paintStyle.fillStyle || currentConnectionPaintStyle.strokeStyle,
1191 lineWidth = paintStyle.lineWidth || currentConnectionPaintStyle.lineWidth,
1193 component:component,
1195 lineWidth:lineWidth,
1196 strokeStyle:strokeStyle,
1197 fillStyle:fillStyle,
1198 minX:Math.min(hxy.x, tail[0].x, tail[1].x),
1199 maxX:Math.max(hxy.x, tail[0].x, tail[1].x),
1200 minY:Math.min(hxy.y, tail[0].y, tail[1].y),
1201 maxY:Math.max(hxy.y, tail[0].y, tail[1].y)
1206 else return {component:component, minX:0,maxX:0,minY:0,maxY:0};
1209 jsPlumbUtil.extend(jsPlumb.Overlays.Arrow, AbstractOverlay);
1212 * Class: Overlays.PlainArrow
1214 * 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
1215 * point along the length of the arrow: in this case, that foldback point is the full length of the arrow. so it just does
1216 * a 'call' to Arrow with foldback set appropriately.
1219 * Function: Constructor
1220 * See <Overlays.Arrow> for allowed parameters for this overlay.
1222 jsPlumb.Overlays.PlainArrow = function(params) {
1223 params = params || {};
1224 var p = jsPlumb.extend(params, {foldback:1});
1225 jsPlumb.Overlays.Arrow.call(this, p);
1226 this.type = "PlainArrow";
1228 jsPlumbUtil.extend(jsPlumb.Overlays.PlainArrow, jsPlumb.Overlays.Arrow);
1231 * Class: Overlays.Diamond
1233 * A diamond. Like PlainArrow, this is a concrete case of the more generic case of the tail points converging on some point...it just
1234 * happens that in this case, that point is greater than the length of the the arrow.
1236 * this could probably do with some help with positioning...due to the way it reuses the Arrow paint code, what Arrow thinks is the
1237 * 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
1238 * stuck when it comes to helping out the Arrow class. possibly we could pass in a 'transpose' parameter or something. the value
1239 * would be -l/4 in this case - move along one quarter of the total length.
1242 * Function: Constructor
1243 * See <Overlays.Arrow> for allowed parameters for this overlay.
1245 jsPlumb.Overlays.Diamond = function(params) {
1246 params = params || {};
1247 var l = params.length || 40,
1248 p = jsPlumb.extend(params, {length:l/2, foldback:2});
1249 jsPlumb.Overlays.Arrow.call(this, p);
1250 this.type = "Diamond";
1252 jsPlumbUtil.extend(jsPlumb.Overlays.Diamond, jsPlumb.Overlays.Arrow);
1254 var _getDimensions = function(component) {
1255 if (component._jsPlumb.cachedDimensions == null)
1256 component._jsPlumb.cachedDimensions = component.getDimensions();
1257 return component._jsPlumb.cachedDimensions;
1260 // abstract superclass for overlays that add an element to the DOM.
1261 var AbstractDOMOverlay = function(params) {
1262 jsPlumb.DOMElementComponent.apply(this, arguments);
1263 AbstractOverlay.apply(this, arguments);
1265 var jpcl = jsPlumb.CurrentLibrary;
1266 this.id = params.id;
1267 this._jsPlumb.div = null;
1268 this._jsPlumb.initialised = false;
1269 this._jsPlumb.component = params.component;
1270 this._jsPlumb.cachedDimensions = null;
1271 this._jsPlumb.create = params.create;
1273 this.getElement = function() {
1274 if (this._jsPlumb.div == null) {
1275 var div = this._jsPlumb.div = jpcl.getDOMElement(this._jsPlumb.create(this._jsPlumb.component));
1276 div.style.position = "absolute";
1277 var clazz = params._jsPlumb.overlayClass + " " +
1278 (this.cssClass ? this.cssClass :
1279 params.cssClass ? params.cssClass : "");
1280 div.className = clazz;
1281 this._jsPlumb.instance.appendElement(div, this._jsPlumb.component.parent);
1282 this._jsPlumb.instance.getId(div);
1283 this.attachListeners(div, this);
1286 return this._jsPlumb.div;
1289 this.draw = function(component, currentConnectionPaintStyle, absolutePosition) {
1290 var td = _getDimensions(this);
1291 if (td != null && td.length == 2) {
1292 var cxy = { x:0,y:0 };
1294 // absolutePosition would have been set by a call to connection.setAbsoluteOverlayPosition.
1295 if (absolutePosition) {
1296 cxy = { x:absolutePosition[0], y:absolutePosition[1] };
1298 else if (component.pointOnPath) {
1299 var loc = this.loc, absolute = false;
1300 if (jsPlumbUtil.isString(this.loc) || this.loc < 0 || this.loc > 1) {
1301 loc = parseInt(this.loc, 10);
1304 cxy = component.pointOnPath(loc, absolute); // a connection
1307 var locToUse = this.loc.constructor == Array ? this.loc : this.endpointLoc;
1308 cxy = { x:locToUse[0] * component.w,
1309 y:locToUse[1] * component.h };
1312 var minx = cxy.x - (td[0] / 2),
1313 miny = cxy.y - (td[1] / 2);
1316 component:component,
1317 d:{ minx:minx, miny:miny, td:td, cxy:cxy },
1324 else return {minX:0,maxX:0,minY:0,maxY:0};
1327 jsPlumbUtil.extend(AbstractDOMOverlay, [jsPlumb.DOMElementComponent, AbstractOverlay], {
1328 getDimensions : function() {
1329 return jsPlumb.CurrentLibrary.getSize(jsPlumb.CurrentLibrary.getElementObject(this.getElement()));
1331 setVisible : function(state) {
1332 this._jsPlumb.div.style.display = state ? "block" : "none";
1335 * Function: clearCachedDimensions
1336 * Clears the cached dimensions for the label. As a performance enhancement, label dimensions are
1337 * cached from 1.3.12 onwards. The cache is cleared when you change the label text, of course, but
1338 * there are other reasons why the text dimensions might change - if you make a change through CSS, for
1339 * example, you might change the font size. in that case you should explicitly call this method.
1341 clearCachedDimensions : function() {
1342 this._jsPlumb.cachedDimensions = null;
1344 cleanup : function() {
1345 if (this._jsPlumb.div != null)
1346 jsPlumb.CurrentLibrary.removeElement(this._jsPlumb.div);
1348 computeMaxSize : function() {
1349 var td = _getDimensions(this);
1350 return Math.max(td[0], td[1]);
1352 reattachListeners : function(connector) {
1353 if (this._jsPlumb.div) {
1354 this.reattachListenersForElement(this._jsPlumb.div, this, connector);
1357 paint : function(p, containerExtents) {
1358 if (!this._jsPlumb.initialised) {
1360 p.component.appendDisplayElement(this._jsPlumb.div);
1361 this.attachListeners(this._jsPlumb.div, p.component);
1362 this._jsPlumb.initialised = true;
1364 this._jsPlumb.div.style.left = (p.component.x + p.d.minx) + "px";
1365 this._jsPlumb.div.style.top = (p.component.y + p.d.miny) + "px";
1370 * Class: Overlays.Custom
1371 * A Custom overlay. You supply a 'create' function which returns some DOM element, and jsPlumb positions it.
1372 * The 'create' function is passed a Connection or Endpoint.
1375 * Function: Constructor
1378 * create - function for jsPlumb to call that returns a DOM element.
1379 * location - distance (as a decimal from 0 to 1 inclusive) marking where the label should sit on the connector. defaults to 0.5.
1380 * id - optional id to use for later retrieval of this overlay.
1383 jsPlumb.Overlays.Custom = function(params) {
1384 this.type = "Custom";
1385 AbstractDOMOverlay.apply(this, arguments);
1387 jsPlumbUtil.extend(jsPlumb.Overlays.Custom, AbstractDOMOverlay);
1389 jsPlumb.Overlays.GuideLines = function() {
1393 this.type = "GuideLines";
1394 AbstractOverlay.apply(this, arguments);
1395 jsPlumb.jsPlumbUIComponent.apply(this, arguments);
1396 this.draw = function(connector, currentConnectionPaintStyle) {
1398 var head = connector.pointAlongPathFrom(self.loc, self.length / 2),
1399 mid = connector.pointOnPath(self.loc),
1400 tail = jsPlumbGeom.pointOnLine(head, mid, self.length),
1401 tailLine = jsPlumbGeom.perpendicularLineTo(head, tail, 40),
1402 headLine = jsPlumbGeom.perpendicularLineTo(tail, head, 20);
1405 connector:connector,
1410 minX:Math.min(head.x, tail.x, headLine[0].x, headLine[1].x),
1411 minY:Math.min(head.y, tail.y, headLine[0].y, headLine[1].y),
1412 maxX:Math.max(head.x, tail.x, headLine[0].x, headLine[1].x),
1413 maxY:Math.max(head.y, tail.y, headLine[0].y, headLine[1].y)
1417 // this.cleanup = function() { }; // nothing to clean up for GuideLines
1421 * Class: Overlays.Label
1425 * Function: Constructor
1428 * cssClass - optional css class string to append to css class. This string is appended "as-is", so you can of course have multiple classes
1429 * defined. This parameter is preferred to using labelStyle, borderWidth and borderStyle.
1430 * 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
1431 * label function returns null. empty strings _will_ be painted.
1432 * location - distance (as a decimal from 0 to 1 inclusive) marking where the label should sit on the connector. defaults to 0.5.
1433 * id - optional id to use for later retrieval of this overlay.
1437 jsPlumb.Overlays.Label = function(params) {
1438 this.labelStyle = params.labelStyle;
1440 var labelWidth = null, labelHeight = null, labelText = null, labelPadding = null;
1441 this.cssClass = this.labelStyle != null ? this.labelStyle.cssClass : null;
1442 var p = jsPlumb.extend({
1443 create : function() {
1444 return document.createElement("div");
1446 jsPlumb.Overlays.Custom.call(this, p);
1447 this.type = "Label";
1448 this.label = params.label || "";
1449 this.labelText = null;
1450 if (this.labelStyle) {
1451 var el = this.getElement();
1452 this.labelStyle.font = this.labelStyle.font || "12px sans-serif";
1453 el.style.font = this.labelStyle.font;
1454 el.style.color = this.labelStyle.color || "black";
1455 if (this.labelStyle.fillStyle) el.style.background = this.labelStyle.fillStyle;
1456 if (this.labelStyle.borderWidth > 0) {
1457 var dStyle = this.labelStyle.borderStyle ? this.labelStyle.borderStyle : "black";
1458 el.style.border = this.labelStyle.borderWidth + "px solid " + dStyle;
1460 if (this.labelStyle.padding) el.style.padding = this.labelStyle.padding;
1464 jsPlumbUtil.extend(jsPlumb.Overlays.Label, jsPlumb.Overlays.Custom, {
1465 cleanup:function() {
1468 this.labelText = null;
1469 this.cssClass = null;
1470 this.labelStyle = null;
1472 getLabel : function() {
1476 * Function: setLabel
1477 * sets the label's, um, label. you would think i'd call this function
1478 * 'setText', but you can pass either a Function or a String to this, so
1479 * it makes more sense as 'setLabel'. This uses innerHTML on the label div, so keep
1480 * that in mind if you need escaped HTML.
1482 setLabel : function(l) {
1484 this.labelText = null;
1485 this.clearCachedDimensions();
1487 this.component.repaint();
1489 getDimensions : function() {
1491 return AbstractDOMOverlay.prototype.getDimensions.apply(this, arguments);
1493 update : function() {
1494 if (typeof this.label == "function") {
1495 var lt = this.label(this);
1496 this.getElement().innerHTML = lt.replace(/\r\n/g, "<br/>");
1499 if (this.labelText == null) {
1500 this.labelText = this.label;
1501 this.getElement().innerHTML = this.labelText.replace(/\r\n/g, "<br/>");
1507 // ********************************* END OF OVERLAY DEFINITIONS ***********************************************************************