Created
September 20, 2012 20:22
-
-
Save eugenkiss/3758128 to your computer and use it in GitHub Desktop.
Hacky, special case curly brace connector for jsPlumb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/******************* Curly Brace Connector *******************/ | |
jsPlumb.Connectors.CurlyBrace = function(params) { | |
var self = this; | |
params = params || {}; | |
this.majorAnchor = params.curviness || 150; | |
this.minorAnchor = 10; | |
var currentPoints = null; | |
this.type = "CurlyBrace"; | |
this._findControlPoint = function(point, sourceAnchorPosition, targetAnchorPosition, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor) { | |
// determine if the two anchors are perpendicular to each other in their orientation. we swap the control | |
// points around if so (code could be tightened up) | |
var soo = sourceAnchor.getOrientation(sourceEndpoint), | |
too = targetAnchor.getOrientation(targetEndpoint), | |
perpendicular = soo[0] != too[0] || soo[1] == too[1], | |
p = [], | |
ma = self.majorAnchor, mi = self.minorAnchor; | |
if (!perpendicular) { | |
if (soo[0] == 0) // X | |
p.push(sourceAnchorPosition[0] < targetAnchorPosition[0] ? point[0] + mi : point[0] - mi); | |
else p.push(point[0] - (ma * soo[0])); | |
if (soo[1] == 0) // Y | |
p.push(sourceAnchorPosition[1] < targetAnchorPosition[1] ? point[1] + mi : point[1] - mi); | |
else p.push(point[1] + (ma * too[1])); | |
} | |
else { | |
if (too[0] == 0) // X | |
p.push(targetAnchorPosition[0] < sourceAnchorPosition[0] ? point[0] + mi : point[0] - mi); | |
else p.push(point[0] + (ma * too[0])); | |
if (too[1] == 0) // Y | |
p.push(targetAnchorPosition[1] < sourceAnchorPosition[1] ? point[1] + mi : point[1] - mi); | |
else p.push(point[1] + (ma * soo[1])); | |
} | |
return p; | |
}; | |
var _CP, _CP2, _sx, _tx, _ty, _sx, _sy, _canvasX, _canvasY, _w, _h, _sStubX, _sStubY, _tStubX, _tStubY; | |
this.compute = function(sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor, lineWidth, minWidth) { | |
lineWidth = lineWidth || 0; | |
_w = Math.abs(sourcePos[0] - targetPos[0]) + lineWidth; | |
_h = Math.abs(sourcePos[1] - targetPos[1]) + lineWidth; | |
_canvasX = Math.min(sourcePos[0], targetPos[0])-(lineWidth/2); | |
_canvasY = Math.min(sourcePos[1], targetPos[1])-(lineWidth/2); | |
_sx = sourcePos[0] < targetPos[0] ? _w - (lineWidth/2): (lineWidth/2); | |
_sy = sourcePos[1] < targetPos[1] ? _h - (lineWidth/2) : (lineWidth/2); | |
_tx = sourcePos[0] < targetPos[0] ? (lineWidth/2) : _w - (lineWidth/2); | |
_ty = sourcePos[1] < targetPos[1] ? (lineWidth/2) : _h - (lineWidth/2); | |
_CP = self._findControlPoint([_sx,_sy], sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor); | |
_CP2 = self._findControlPoint([_tx,_ty], targetPos, sourcePos, targetEndpoint, sourceEndpoint, targetAnchor, sourceAnchor); | |
var minx1 = Math.min(_sx,_tx), minx2 = Math.min(_CP[0], _CP2[0]), minx = Math.min(minx1,minx2), | |
maxx1 = Math.max(_sx,_tx), maxx2 = Math.max(_CP[0], _CP2[0]), maxx = Math.max(maxx1,maxx2); | |
if (maxx > _w) _w = maxx; | |
if (minx < 0) { | |
_canvasX += minx; var ox = Math.abs(minx); | |
_w += ox; _CP[0] += ox; _sx += ox; _tx +=ox; _CP2[0] += ox; | |
} | |
var miny1 = Math.min(_sy,_ty), miny2 = Math.min(_CP[1], _CP2[1]), miny = Math.min(miny1,miny2), | |
maxy1 = Math.max(_sy,_ty), maxy2 = Math.max(_CP[1], _CP2[1]), maxy = Math.max(maxy1,maxy2); | |
if (maxy > _h) _h = maxy; | |
if (miny < 0) { | |
_canvasY += miny; var oy = Math.abs(miny); | |
_h += oy; _CP[1] += oy; _sy += oy; _ty +=oy; _CP2[1] += oy; | |
} | |
if (minWidth && _w < minWidth) { | |
var posAdjust = (minWidth - _w) / 2; | |
_w = minWidth; | |
_canvasX -= posAdjust; _sx = _sx + posAdjust ; _tx = _tx + posAdjust; _CP[0] = _CP[0] + posAdjust; _CP2[0] = _CP2[0] + posAdjust; | |
} | |
if (minWidth && _h < minWidth) { | |
var posAdjust = (minWidth - _h) / 2; | |
_h = minWidth; | |
_canvasY -= posAdjust; _sy = _sy + posAdjust ; _ty = _ty + posAdjust; _CP[1] = _CP[1] + posAdjust; _CP2[1] = _CP2[1] + posAdjust; | |
} | |
currentPoints = [_canvasX, _canvasY, _w, _h, | |
_sx, _sy, _tx, _ty, | |
_CP[0], _CP[1], _CP2[0], _CP2[1] ]; | |
return currentPoints; | |
}; | |
var _makeCurve = function() { | |
return [ | |
{ x:_sx, y:_sy }, | |
{ x:_CP[0], y:_CP[1] }, | |
{ x:_CP2[0], y:_CP2[1] }, | |
{ x:_tx, y:_ty } | |
]; | |
}; | |
var _translateLocation = function(curve, location, absolute) { | |
if (absolute) | |
location = jsBezier.locationAlongCurveFrom(curve, location > 0 ? 0 : 1, location); | |
return location; | |
}; | |
/** | |
* returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from | |
* 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. | |
*/ | |
this.pointOnPath = function(location, absolute) { | |
var c = _makeCurve(); | |
location = _translateLocation(c, location, absolute); | |
return jsBezier.pointOnCurve(c, location); | |
}; | |
/** | |
* returns the gradient of the connector at the given point. | |
*/ | |
this.gradientAtPoint = function(location, absolute) { | |
var c = _makeCurve(); | |
location = _translateLocation(c, location, absolute); | |
return jsBezier.gradientAtPoint(c, location); | |
}; | |
/** | |
* for Bezier curves this method is a little tricky, cos calculating path distance algebraically is notoriously difficult. | |
* this method is iterative, jumping forward .05% of the path at a time and summing the distance between this point and the previous | |
* one, until the sum reaches 'distance'. the method may turn out to be computationally expensive; we'll see. | |
* another drawback of this method is that if the connector gets quite long, .05% of the length of it is not necessarily smaller | |
* than the desired distance, in which case the loop returns immediately and the arrow is mis-shapen. so a better strategy might be to | |
* calculate the step as a function of distance/distance between endpoints. | |
*/ | |
this.pointAlongPathFrom = function(location, distance, absolute) { | |
var c = _makeCurve(); | |
location = _translateLocation(c, location, absolute); | |
return jsBezier.pointAlongCurveFrom(c, location, distance); | |
}; | |
}; | |
// TODO refactor to renderer common script. put a ref to jsPlumb.sizeCanvas in there too. | |
var _connectionBeingDragged = null, | |
_hasClass = function(el, clazz) { return jsPlumb.CurrentLibrary.hasClass(_getElementObject(el), clazz); }, | |
_getElementObject = function(el) { return jsPlumb.CurrentLibrary.getElementObject(el); }, | |
_getOffset = function(el) { return jsPlumb.CurrentLibrary.getOffset(_getElementObject(el)); }, | |
_pageXY = function(el) { return jsPlumb.CurrentLibrary.getPageXY(el); }, | |
_clientXY = function(el) { return jsPlumb.CurrentLibrary.getClientXY(el); }; | |
/* | |
* Class:CanvasMouseAdapter | |
* Provides support for mouse events on canvases. | |
*/ | |
var CanvasMouseAdapter = function() { | |
var self = this; | |
self.overlayPlacements = []; | |
jsPlumb.jsPlumbUIComponent.apply(this, arguments); | |
jsPlumbUtil.EventGenerator.apply(this, arguments); | |
/** | |
* returns whether or not the given event is ojver a painted area of the canvas. | |
*/ | |
this._over = function(e) { | |
var o = _getOffset(_getElementObject(self.canvas)), | |
pageXY = _pageXY(e), | |
x = pageXY[0] - o.left, y = pageXY[1] - o.top; | |
if (x > 0 && y > 0 && x < self.canvas.width && y < self.canvas.height) { | |
// first check overlays | |
for ( var i = 0; i < self.overlayPlacements.length; i++) { | |
var p = self.overlayPlacements[i]; | |
if (p && (p[0] <= x && p[1] >= x && p[2] <= y && p[3] >= y)) | |
return true; | |
} | |
// then the canvas | |
var d = self.canvas.getContext("2d").getImageData(parseInt(x), parseInt(y), 1, 1); | |
return d.data[0] != 0 || d.data[1] != 0 || d.data[2] != 0 || d.data[3] != 0; | |
} | |
return false; | |
}; | |
var _mouseover = false, _mouseDown = false, _posWhenMouseDown = null, _mouseWasDown = false, | |
_nullSafeHasClass = function(el, clazz) { | |
return el != null && _hasClass(el, clazz); | |
}; | |
this.mousemove = function(e) { | |
var pageXY = _pageXY(e), clientXY = _clientXY(e), | |
ee = document.elementFromPoint(clientXY[0], clientXY[1]), | |
eventSourceWasOverlay = _nullSafeHasClass(ee, "_jsPlumb_overlay"); | |
var _continue = _connectionBeingDragged == null && (_nullSafeHasClass(ee, "_jsPlumb_endpoint") || _nullSafeHasClass(ee, "_jsPlumb_connector")); | |
if (!_mouseover && _continue && self._over(e)) { | |
_mouseover = true; | |
self.fire("mouseenter", self, e); | |
return true; | |
} | |
// TODO here there is a remote chance that the overlay the mouse moved onto | |
// is actually not an overlay for the current component. a more thorough check would | |
// be to ensure the overlay belonged to the current component. | |
else if (_mouseover && (!self._over(e) || !_continue) && !eventSourceWasOverlay) { | |
_mouseover = false; | |
self.fire("mouseexit", self, e); | |
} | |
self.fire("mousemove", self, e); | |
}; | |
this.click = function(e) { | |
if (_mouseover && self._over(e) && !_mouseWasDown) | |
self.fire("click", self, e); | |
_mouseWasDown = false; | |
}; | |
this.dblclick = function(e) { | |
if (_mouseover && self._over(e) && !_mouseWasDown) | |
self.fire("dblclick", self, e); | |
_mouseWasDown = false; | |
}; | |
this.mousedown = function(e) { | |
if(self._over(e) && !_mouseDown) { | |
_mouseDown = true; | |
_posWhenMouseDown = _getOffset(_getElementObject(self.canvas)); | |
self.fire("mousedown", self, e); | |
} | |
}; | |
this.mouseup = function(e) { | |
_mouseDown = false; | |
self.fire("mouseup", self, e); | |
}; | |
this.contextmenu = function(e) { | |
if (_mouseover && self._over(e) && !_mouseWasDown) | |
self.fire("contextmenu", self, e); | |
_mouseWasDown = false; | |
}; | |
}; | |
var _newCanvas = function(params) { | |
var canvas = document.createElement("canvas"); | |
params["_jsPlumb"].appendElement(canvas, params.parent); | |
canvas.style.position = "absolute"; | |
if (params["class"]) canvas.className = params["class"]; | |
// set an id. if no id on the element and if uuid was supplied it | |
// will be used, otherwise we'll create one. | |
params["_jsPlumb"].getId(canvas, params.uuid); | |
if (params.tooltip) canvas.setAttribute("title", params.tooltip); | |
return canvas; | |
}; | |
var CanvasComponent = function(params) { | |
CanvasMouseAdapter.apply(this, arguments); | |
var displayElements = [ ]; | |
this.getDisplayElements = function() { return displayElements; }; | |
this.appendDisplayElement = function(el) { displayElements.push(el); }; | |
}; | |
/** | |
* Class:CanvasConnector | |
* Superclass for Canvas Connector renderers. | |
*/ | |
var CanvasConnector = jsPlumb.CanvasConnector = function(params) { | |
CanvasComponent.apply(this, arguments); | |
var _paintOneStyle = function(dim, aStyle) { | |
self.ctx.save(); | |
jsPlumb.extend(self.ctx, aStyle); | |
if (aStyle.gradient) { | |
var g = self.createGradient(dim, self.ctx); | |
for ( var i = 0; i < aStyle.gradient.stops.length; i++) | |
g.addColorStop(aStyle.gradient.stops[i][0], aStyle.gradient.stops[i][1]); | |
self.ctx.strokeStyle = g; | |
} | |
self._paint(dim, aStyle); | |
self.ctx.restore(); | |
}; | |
var self = this, | |
clazz = self._jsPlumb.connectorClass + " " + (params.cssClass || ""); | |
self.canvas = _newCanvas({ | |
"class":clazz, | |
_jsPlumb:self._jsPlumb, | |
parent:params.parent, | |
tooltip:params.tooltip | |
}); | |
self.ctx = self.canvas.getContext("2d"); | |
self.appendDisplayElement(self.canvas); | |
self.paint = function(dim, style) { | |
if (style != null) { | |
jsPlumb.sizeCanvas(self.canvas, dim[0], dim[1], dim[2], dim[3]); | |
if (self.getZIndex()) | |
self.canvas.style.zIndex = self.getZIndex(); | |
if (style.outlineColor != null) { | |
var outlineWidth = style.outlineWidth || 1, | |
outlineStrokeWidth = style.lineWidth + (2 * outlineWidth), | |
outlineStyle = { | |
strokeStyle:style.outlineColor, | |
lineWidth:outlineStrokeWidth | |
}; | |
_paintOneStyle(dim, outlineStyle); | |
} | |
_paintOneStyle(dim, style); | |
} | |
}; | |
}; | |
/* | |
* Canvas Bezier Connector. Draws a Bezier curve onto a Canvas element. | |
*/ | |
jsPlumb.Connectors.canvas.CurlyBrace = function() { | |
var self = this; | |
jsPlumb.Connectors.Bezier.apply(this, arguments); | |
CanvasConnector.apply(this, arguments); | |
this._paint = function(dimensions, style) { | |
self.ctx.beginPath(); | |
var height = dimensions[5] - dimensions[7]; | |
var midpoint = dimensions[5] - height/2; | |
// draw from bottom to midpoint | |
self.ctx.moveTo(dimensions[4], dimensions[5]); | |
self.ctx.bezierCurveTo(dimensions[6]-7, dimensions[9]-10, dimensions[10]+11, midpoint+height/14, dimensions[8]+2, midpoint); | |
// draw from top to midpoint | |
self.ctx.moveTo(dimensions[4], dimensions[5]-height); | |
self.ctx.bezierCurveTo(dimensions[6]-7, dimensions[9]-5-height, dimensions[10]+11, midpoint-height/14, dimensions[8]+2, midpoint); | |
self.ctx.stroke(); | |
}; | |
// TODO i doubt this handles the case that source and target are swapped. | |
this.createGradient = function(dim, ctx, swap) { | |
return /*(swap) ? self.ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]) : */self.ctx.createLinearGradient(dim[6], dim[7], dim[4], dim[5]); | |
}; | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
The only function I changed is
jsPlumb.Connectors.canvas.Bezier = function()...
. This is the original definition: