Port support in Community Edition 4.x Port support in Community Edition 4.x
25 Aug 2020
Nested group support in Community Edition 4.x Nested group support in Community Edition 4.x
28 Jul 2020
Community Edition 4.x Beta Release Community Edition 4.x Beta Release
20 Jun 2020
World Cup 2018, Part 2 World Cup 2018, Part 2
26 Jun 2018
World Cup 2018, Part 1 World Cup 2018, Part 1
25 Jun 2018
Community Edition 2.2.2 Release Community Edition 2.2.2 Release
20 Oct 2016
Connecting SVG Shapes (Raphael, Highcharts etc) (update) Connecting SVG Shapes (Raphael, Highcharts etc) (update)
20 Oct 2016
Writing a Layout Decorator Writing a Layout Decorator
25 Jul 2015
Toolkit Edition 1.0.0 Toolkit Edition 1.0.0
23 Jul 2015
Community Edition 1.7.6 Community Edition 1.7.6
23 Jul 2015
Connecting SVG Shapes (Raphael, Highcharts etc) Connecting SVG Shapes (Raphael, Highcharts etc)
22 Jul 2015
The beforeDrag Interceptor The beforeDrag Interceptor
15 Jan 2015
Integrating Jekyll and YUIDoc Integrating Jekyll and YUIDoc
28 Jan 2014
Dragging Multiple Elements Dragging Multiple Elements
17 Jan 2014
Custom Connectors Custom Connectors
22 Dec 2013

Connecting SVG Shapes (Raphael, Highcharts etc)

Note: the code in this page will not run in Firefox, due to the underlying issues discussed here.

jsPlumb can connect SVG elements in the same way as it does standard DOM elements. Consider the following markup
and code snippet:

<div style="position:relative;width:100%">
  <svg width="150" height="150" fill="red" id="red" x="0" y="0">
    <rect x="0" y="0" width="150" height="150"/>
  </svg>

  <svg width="150" height="150" fill="blue" style="position:absolute;left:250px" id="blue" x="200" y="200" >
    <rect x="0" y="0" width="150" height="150"/>
  </svg>
</div>
var j = jsPlumb.getInstance();
j.connect({
    source:"red", 
    target:"blue", 
    anchor:"Continuous",
    connector:"Straight",
    paintStyle:{ lineWidth:2, strokeStyle:"aliceblue" }
});

The result is this:

<svg width="150" height="150" fill="blue" style="position:absolute;left:250px;" id="blue">
    <rect x="0" y="0" width="150" height="150"/>
</svg>



THE PROBLEM

You cannot connect two rect elements (or any SVG shapes) directly. This is because the shapes inside an SVG element
do not have the same positioning information as all other DOM elements. So now consider you have this markup:

<div style="position:relative;width:100%">
  <svg width="100%" height="200" fill="red" id="red2" x="0" y="0" style="border:1px solid gray;">
    <rect x="10" y="10" width="50" height="50" id="rect1"/>
    <circle cx="220" cy="130" r="20"/>  
    <rect x="290" y="60" width="100" height="50" id="rect2"/>
  </svg>
</div>

which produces:

and you want to connect the two red rectangles. The key here is that both rect elements share the same svg parent;
this happens in quite a few libraries that render to SVG (the one that comes to mind immediately, which spurred this
post, is Raphael. Highcharts is also a well known library that does this. But there are probably a million or more,
and that may or may not be an exaggeration).

THE SOLUTION

The solution here is that you need to override the methods that jsPlumb uses to determine the location and size of
things. First let's define handlers for getting the offset and size for a few known SVG shapes:

var offsetCalculators = {
"CIRCLE":function(el, parentOffset) {
    var cx = parseInt(el.getAttribute("cx"), 10),
        cy = parseInt(el.getAttribute("cy"), 10),
        r = parseInt(el.getAttribute("r"), 10);
    return {
        left: parentOffset.left + (cx - r),
        top:parentOffset.top + (cy - r)
    };
},
"ELLIPSE":function(el, parentOffset) {
    var cx = parseInt(el.getAttribute("cx"), 10),
        cy = parseInt(el.getAttribute("cy"), 10),
        rx = parseInt(el.getAttribute("rx"), 10),
        ry = parseInt(el.getAttribute("ry"), 10);
    return {
        left: parentOffset.left + (cx - rx),
        top:parentOffset.top + (cy - ry)
    };
},
"RECT":function(el, parentOffset) {
    var x = parseInt(el.getAttribute("x"), 10),
        y = parseInt(el.getAttribute("y"), 10);
    return {
        left: parentOffset.left + x,
        top:parentOffset.top + y
    };
}
};

// custom size calculators for SVG shapes.
var sizeCalculators = {
"CIRCLE":function(el) {
    var r = parseInt(el.getAttribute("r"), 10);
    return [ r * 2, r * 2 ];
},
"ELLIPSE":function(el) {
    var rx = parseInt(el.getAttribute("rx"), 10),
        ry = parseInt(el.getAttribute("ry"), 10);
    return [ rx * 2, ry * 2 ];
},
"RECT":function(el) {
    var w = parseInt(el.getAttribute("width"), 10),
        h = parseInt(el.getAttribute("height"), 10);
    return [ w, h ];
}
};

Now we can override the methods we need to in jsPlumb, handing off control to these handlers when necessary:


// store original jsPlumb prototype methods for getOffset and size.
var originalOffset = jsPlumbInstance.prototype.getOffset;
var originalSize = jsPlumbInstance.prototype.getSize;

jsPlumbInstance.prototype.getOffset = function(el) {
    var tn = el.tagName.toUpperCase();
    if (offsetCalculators[tn]) {
        var so = {left:el.parentNode.offset();
        return offsetCalculators[tn]($(el), so);
    }
    else
        return $(el).offset();
};

jsPlumbInstance.prototype.getSize = function(el) {
    var tn = el.tagName.toUpperCase();
    if (sizeCalculators[tn]) {
        return sizeCalculators[tn]($(el));
    }
    else
        return [ $(el).outerWidth(), $(el).outerHeight() ];
};

And then tell jsPlumb to connect these two rectangles:

var j2 = jsPlumb.getInstance();
j2.connect({
 source:"rect1",
 target:"rect2",
 anchor:"Continuous",
 connector:"Bezier",
 paintStyle:{ lineWidth:2, strokeStyle:"#456" },
 endpoint:["Dot", { radius:2 }]
});

The result is this:

AND SO...RAPHAEL?

To connect nodes created by Raphael, set the element used as Raphael's paper to be the container of your
jsPlumb instance:

<div id="paper" style="width:100%;height:480px;border:1px solid gray"></div>

var j3 = jsPlumb.getInstance();
j3.setContainer("paper");
var paper = Raphael("paper");

Then draw a few things:

var circle1 = paper.circle(140, 110, 90).attr({ fill: '#3D6AA2', stroke: '#000000', 'stroke-width': 8 });
var circle2 = paper.circle(400, 180, 90).attr({ fill: '#3D6AA2', stroke: '#000000', 'stroke-width': 8 });
var rect = paper.rect(50, 280, 90, 70).attr({ fill: '#3D6AA2', stroke: '#000000', 'stroke-width': 8 });    
var ellipse = paper.ellipse(300, 420, 90, 70).attr({ fill: '#3D6AA2', stroke: '#000000', 'stroke-width': 8 });

and connect them up:

j3.connect({source:circle1.node, target:circle2.node, anchor:"Center", connector:"Straight"});
    
j3.connect({source:circle1.node, target:rect.node, anchors:["Center", "Top"], connector:"Straight"});    
    
j3.connect({source:circle2.node, target:ellipse.node, anchor:"Center"});

Easy as falling off a log.

CAN I DRAG THOSE SHAPES?

Yes! Go ahead and try. Raphael supports dragging via the following call:

paper.set(circle1, circle2, ellipse, rect).drag(moveFunction, startFunction, upFunction);

Since our shapes have different attributes (circle and ellipse are located via cx and cy, but rect is
located via x and y), we created a helper class:

var RaphaelDragger = function() {
    var attNames = {
        "circle":["cx", "cy"],
        "ellipse":["cx", "cy"],
        "rect":["x", "y"]
    };
    return [
        function(dx, dy) {
            var args = {};
            args[attNames[this.type][0]] = this.ox + dx;
            args[attNames[this.type][1]] = this.oy + dy;
            this.attr(args);
            j3.revalidate(this.node);
        },
        function() {
            this.ox = this.attr(attNames[this.type][0]);
            this.oy = this.attr(attNames[this.type][1]);
        },
        function() {}
    ];
};

..which returns an array of three functions, one each for move, start and up. Then we do a little JS trick and
call the apply method of the drag object to supply these as arguments:

paper.set(circle1, circle2, ellipse, rect).drag.apply(paper, new RaphaelDragger());

WHAT ABOUT PATHS?

A good question. You could follow the general principles outlined here but you'd have to decide for yourself
how you wanted to treat the geometry of a path. Where does it start? How big is it? Given the arbitrary
nature of paths this is often unclear.

DOWNLOAD

Get the source by clicking here.