/** * Originally grabbed from the official RaphaelJS Documentation * http://raphaeljs.com/graffle.html * Adopted (arrows) and commented by Philipp Strathausen http://blog.ameisenbar.de * Licenced under the MIT licence. */ /** * Usage: * connect two shapes * parameters: * source shape [or connection for redrawing], * target shape, * style with { fg : linecolor, bg : background color, directed: boolean } * returns: * connection { draw = function() } */ Raphael.fn.connection = function Connection(obj1, obj2, style) { var selfRef = this; /* create and return new connection */ var edge = {/* from : obj1, to : obj2, style : style,*/ draw : function() { /* get bounding boxes of target and source */ var bb1 = obj1.getBBox(); var bb2 = obj2.getBBox(); var off1 = 0; var off2 = 0; /* coordinates for potential connection coordinates from/to the objects */ var p = [ /* NORTH 1 */ { x: bb1.x + bb1.width / 2, y: bb1.y - off1 }, /* SOUTH 1 */ { x: bb1.x + bb1.width / 2, y: bb1.y + bb1.height + off1 }, /* WEST */ { x: bb1.x - off1, y: bb1.y + bb1.height / 2 }, /* EAST 1 */ { x: bb1.x + bb1.width + off1, y: bb1.y + bb1.height / 2 }, /* NORTH 2 */ { x: bb2.x + bb2.width / 2, y: bb2.y - off2 }, /* SOUTH 2 */ { x: bb2.x + bb2.width / 2, y: bb2.y + bb2.height + off2 }, /* WEST 2 */ { x: bb2.x - off2, y: bb2.y + bb2.height / 2 }, /* EAST 2 */ { x: bb2.x + bb2.width + off2, y: bb2.y + bb2.height / 2 } ]; /* distances between objects and according coordinates connection */ var d = {}, dis = [], dx, dy; /* * find out the best connection coordinates by trying all possible ways */ /* loop the first object's connection coordinates */ for (var i = 0; i < 4; i++) { /* loop the seond object's connection coordinates */ for (var j = 4; j < 8; j++) { dx = Math.abs(p[i].x - p[j].x); dy = Math.abs(p[i].y - p[j].y); if ((i === j - 4) || (((i !== 3 && j !== 6) || p[i].x < p[j].x) && ((i !== 2 && j !== 7) || p[i].x > p[j].x) && ((i !== 0 && j !== 5) || p[i].y > p[j].y) && ((i !== 1 && j !== 4) || p[i].y < p[j].y))) { dis.push(dx + dy); d[dis[dis.length - 1].toFixed(3)] = [i, j]; } } } var res = dis.length === 0 ? [0, 4] : d[Math.min.apply(Math, dis).toFixed(3)]; /* bezier path */ var x1 = p[res[0]].x, y1 = p[res[0]].y, x4 = p[res[1]].x, y4 = p[res[1]].y; dx = Math.max(Math.abs(x1 - x4) / 2, 10); dy = Math.max(Math.abs(y1 - y4) / 2, 10); var x2 = [ x1, x1, x1 - dx, x1 + dx ][res[0]].toFixed(3), y2 = [ y1 - dy, y1 + dy, y1, y1 ][res[0]].toFixed(3), x3 = [ 0, 0, 0, 0, x4, x4, x4 - dx, x4 + dx ][res[1]].toFixed(3), y3 = [ 0, 0, 0, 0, y1 + dy, y1 - dy, y4, y4 ][res[1]].toFixed(3); /* assemble path and arrow */ var path = [ "M" + x1.toFixed(3), y1.toFixed(3), "C" + x2, y2, x3, y3, x4.toFixed(3), y4.toFixed(3) ].join(","); /* arrow */ if(style && style.directed) { // magnitude, length of the last path vector var mag = Math.sqrt((y4 - y3) * (y4 - y3) + (x4 - x3) * (x4 - x3)); // vector normalisation to specified length var norm = function(x,l){return (-x*(l||5)/mag);}; // calculate array coordinates (two lines orthogonal to the path vector) var arr = [ { x:(norm(x4-x3)+norm(y4-y3)+x4).toFixed(3), y:(norm(y4-y3)+norm(x4-x3)+y4).toFixed(3) }, { x:(norm(x4-x3)-norm(y4-y3)+x4).toFixed(3), y:(norm(y4-y3)-norm(x4-x3)+y4).toFixed(3) } ]; path = path + ",M" + arr[0].x + "," + arr[0].y + ",L" + x4 + "," + y4 + ",L" + arr[1].x + "," + arr[1].y; } /* function to be used for moving existent path(s), e.g. animate() or attr() */ var move = "attr"; /* applying path(s) */ edge.fg && edge.fg[move]({path:path}) || (edge.fg = selfRef.path(path) .attr({ stroke: style && style.stroke || "#000", fill: "none" }) .toBack()); edge.bg && edge.bg[move]({path:path}) || style && style.fill && (edge.bg = style.fill.split && selfRef.path(path) .attr({ stroke: style.fill.split("|")[0], fill: "none", "stroke-width": style.fill.split("|")[1] || 3 }).toBack()); /* setting label */ style && style.label && (edge.label && edge.label.attr({x:(x1+x4)/2, y:(y1+y4)/2}) || (edge.label = selfRef.text((x1+x4)/2, (y1+y4)/2, style.label) .attr({fill: "#000", "font-size": style["font-size"] || "12px"}))); style && style.label && style["label-style"] && edge.label && edge.label.attr(style["label-style"]); style && style.callback && style.callback(edge); } }; edge.draw(); return edge; };