There is a method available for dragging multiple elements, shared by @Lars, which works well but has some unnatural interactions that I personally didn't find appealing. You can find the code here: Drag multiple elements that aren't grouped in a `g` tag?
After exploring various options, I arrived at the following code that functions flawlessly with a very natural interaction.
<html>
<head>
<title>jQuery UI Selectable - Serialize</title>
<link rel="stylesheet" href="//code.jquery.com/ui/1.11.4/theme/smoothness/jquery-ui.css">
<script src="http://code.jquery.com/jquery-1.11.0.min.js"></script>
<script src="http://code.jquery.com/ui/1.11.4/jquery-ui.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<link rel="stylesheet" href="/resources/demos/style.css">
<style>
rect.selection {
stroke: black;
stroke-dasharray: 4px;
stroke-opacity : 0.9;
fill: transparent;
}
.state circle {
stroke : gray;
cursor : pointer;
}
.state {
fill : black;
}
.selected{
fill : red;
}
</style>
<body>
<div class="div1"></div>
</body>
<script type="text/javascript">
var width = 800,
radius=10,
height = 600;
svg= d3.select("body").select(".div1").append("svg")
.attr("width", width)
.attr("height",height);
var transformed_data= d3.range(10).map(function(d) {
return {
x: parseInt(Math.random() * width),
y: parseInt(Math.random() * height),
}
});
// Drag Event
drag = d3.behavior.drag()
.on("drag", function( d, i) {
var selection = d3.selectAll('.selected');
console.log(selection[0] + "---"+selection[1]);
svg.selectAll( "rect.selection").remove();
if(selection[0].indexOf(this)==-1) {
selection = d3.select(this);
selection.classed("selected", true);
}
selection.attr("cx", function(d){ d.x += d3.event.dx; return d.x;})
selection.attr("cy", function(d,i){d.y += d3.event.dy; return d.y;})
});
gStates = svg.selectAll('circle')
.data(transformed_data)
.enter()
.append("circle")
.attr("class","state")
.attr("id", function(d,i){return "id_" + i.toString();})
.attr("cx", function(d,i){return d.x;})
.attr("cy", function(d,i){return d.y;})
.attr("r",10)
.call(drag);
svg.on( "mousedown", function() {
if(!d3.event.ctrlKey) {
d3.selectAll(".selected").classed( "selected", false);
}
var p = d3.mouse(this);
svg.append( "rect")
.attr({
class : "selection",
x : p[0],
y : p[1],
width : 0,
height : 0
})
})
.on( "mousemove", function() {
var s = svg.select( "rect.selection");
if( !s.empty()) {
var p = d3.mouse( this),
d = {
x : parseInt( s.attr( "x"), 10),
y : parseInt( s.attr( "y"), 10),
width : parseInt( s.attr( "width"), 10),
height : parseInt( s.attr( "height"), 10)
},
move = {
x : p[0] - d.x,
y : p[1] - d.y
};
if( move.x < 1 || (move.x*2<d.width)) {
d.x = p[0];
d.width -= move.x;
} else {
d.width = move.x;
}
if( move.y < 1 || (move.y*2<d.height)) {
d.y = p[1];
d.height -= move.y;
} else {
d.height = move.y;
}
s.attr( d);
d3.selectAll( ".state").each( function( state_data, i) {
if(
!d3.select(this).classed("selected") &&
// inner circle inside selection frame
state_data.x-radius>=d.x && state_data.x+radius<=d.x+d.width &&
state_data.y-radius>=d.y && state_data.y+radius<=d.y+d.height
) {
d3.select(this)
.classed( "selection", true)
.classed( "selected", true);
}
});
}
})
.on( "mouseup", function() {
// remove selection frame
svg.selectAll( "rect.selection").remove();
// remove temporary selection marker class
d3.selectAll( '.state.selection').classed( "selection", false);
})
.on( "mouseout", function() {
if( d3.event.relatedTarget.tagName=='HTML') {
// remove selection frame
svg.selectAll( "rect.selection").remove();
// remove temporary selection marker class
d3.selectAll( '.state.selection').classed( "selection", false);
}
});
</script>
</head>