This quite a tricky one overall. Here's most of the solution which should at least set you off right as one possible method. For the distance checking, I used the code in the original fiddle, so credit to the person who wrote that, as its potentially tricky (and maybe worthy of its own SO question, I think it will need a tweak though).
fiddle here edit: You'll need to tweak to allow for starting position better.
Drag the circle to start it off, as I haven't set the start positions. You will want to adjust the elements starting positions, depending on whether you will zero offset them or whatever (otherwise you will need to allow for this when moving/transforming). You may also want to check for if the first/last element reaches the end and stops them all, so they all stop if one element reaches the path end.
It works by putting all the of the objects in a set, and attaching a handler to each of them (you could possibly just have one handler on the group, more elegant but may be a bit trickier).
We keep track of each elements index
this.data('index')
So when it comes to moving them along the line, we know where it is in the 'chain' and can offset to compensate, ie the following line...
var whichDrag = this;
....
mySet.forEach( function( el, i ) {
var which = whichDrag.data("index") - i;
pt = path.getPointAtLength(l + (which * spacer ));
if( !isNaN(pt.x) && !isNaN(pt.x) ) { // check if over end
el.transform('t' + pt.x + ',' + pt.y );
};
} );
Complete code...
var paper = Snap('#panel');
var spacer = 70;
var path = paper.path('M44.16,44.16 L44.16,44.16 L73.6,14.719999999999999 L132.48,73.6 L14.719999999999999,191.35999999999999 L132.48,309.12 L103.03999999999999,338.55999999999995 L44.16,279.67999999999995 L44.16,279.67999999999995')
.attr({
stroke: 'gray',
strokeWidth: 3,
fill: 'none'
});
var pt = path.getPointAtLength(l);
//e = r.ellipse(pt.x, pt.y, 4, 4).attr({stroke: "none", fill: "#f00"}),
var totLen = path.getTotalLength();
var r1 = paper.rect(0,0,10,10);
var c3 = paper.circle(0,0, 15);
var c2 = paper.circle(0,0, 15);
var c1 = paper.circle(0,0, 15);
var l = 0;
var searchDl = 1;
var cGroup = paper.g();
cGroup.add(c3,c2,c1,r1);
var mySet = cGroup.selectAll("*");
start = function () {
this.data("ox", +this.getBBox().cx );
this.data("oy", +this.getBBox().cy );
this.attr({opacity: 1});
},
move = function (dx, dy) {
var whichDrag = this;
var tmpPt = {
x : this.data("ox") + dx,
y : this.data("oy") + dy
};
// move will be called with dx and dy
l = gradSearch(l, tmpPt);
pt = path.getPointAtLength(l);
// this.attr({cx: pt.x, cy: pt.y});
mySet.forEach( function( el, i ) {
var which = whichDrag.data("index") - i;
pt = path.getPointAtLength(l + (which * spacer ));
if( !isNaN(pt.x) && !isNaN(pt.x) ) {
//el.attr({cx: pt.x, cy: pt.y});
el.transform('t' + pt.x + ',' + pt.y );
};
} );
},
up = function () {
// restoring state
this.attr({opacity: 1});
},
gradSearch = function (l0, pt) {
l0 = l0 + totLen;
var l1 = l0,
dist0 = dist(path.getPointAtLength(l0 % totLen), pt),
dist1,
searchDir;
if (dist(path.getPointAtLength((l0 - searchDl) % totLen), pt) >
dist(path.getPointAtLength((l0 + searchDl) % totLen), pt)) {
searchDir = searchDl;
} else {
searchDir = -searchDl;
}
l1 += searchDir;
dist1 = dist(path.getPointAtLength(l1 % totLen), pt);
while (dist1