Skip to content

Instantly share code, notes, and snippets.

@explunit
Created May 18, 2013 04:31
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save explunit/5603250 to your computer and use it in GitHub Desktop.
Save explunit/5603250 to your computer and use it in GitHub Desktop.
D3.js tree with "drag near" logic

Use a larger transparent shape as an alternative to intersection or collision detection when dragging a node. Sparked by this StackOverflow question

<!DOCTYPE html>
<meta charset="utf-8">
<html>
<head>
<title>Simple Tree Demo</title>
<script src="http://d3js.org/d3.v3.min.js"></script>
<style>
.nodelink {
fill: none;
stroke: #ccc;
stroke-width: 4.5px;
}
.templink {
fill: none;
stroke: red;
stroke-width: 3px;
}
</style>
</head>
<body>
Drag the lonely circle near the others
<div id="viz"></div>
<script type="text/javascript">
var treeData = {"name" : "A", "children" : [ {"name" : "A1" }, {"name" : "A2" }, {"name" : "A3" } ] };
var selectedNode = null;
var draggingNode = null;
// ------------- moving -------------------------------
var overCircle = function(d) {
selectedNode = d;
updateTempConnector();
}
var outCircle = function(d) {
selectedNode = null;
updateTempConnector();
}
var circleDragger = d3.behavior.drag()
.on("dragstart", function(d){
draggingNode = d;
// it's important that we suppress the mouseover event on the node being dragged. Otherwise it will absorb the mouseover event and the underlying node will not detect it
d3.select(this).attr( 'pointer-events', 'none' );
})
.on("drag", function(d) {
d.x += d3.event.dx;
d.y += d3.event.dy;
var node = d3.select(this);
node.attr( { cx: d.x, cy: d.y } );
updateTempConnector();
})
.on("dragend", function(d){
draggingNode = null;
// now restore the mouseover event or we won't be able to drag a 2nd time
d3.select(this).attr( 'pointer-events', '' );
})
var updateTempConnector = function() {
var data = [];
if ( draggingNode != null && selectedNode != null) {
// have to flip the source coordinates since we did this for the existing connectors on the original tree
data = [ {source: {x: selectedNode.y, y: selectedNode.x},
target: {x: draggingNode.x, y: draggingNode.y} } ];
}
var link = vis.selectAll(".templink").data(data);
link.enter().append("path")
.attr("class", "templink")
.attr("d", d3.svg.diagonal() )
.attr('pointer-events', 'none');
link.attr("d", d3.svg.diagonal() )
link.exit().remove();
}
// ------------- normal tree drawing code --------
var vis = d3.select("#viz").append("svg").attr("width", 400).attr("height", 300).append("svg:g").attr("transform", "translate(50, 0)")
var tree = d3.layout.tree().size([200,200]);
var nodes = tree.nodes(treeData);
var links = tree.links(nodes);
var diagonalHorizontal = d3.svg.diagonal().projection( function(d) { return [d.y, d.x]; } );
var link = vis.selectAll(".nodelink")
.data(links)
.enter().append("path")
.attr("class", "nodelink")
.attr("d", diagonalHorizontal)
.attr('pointer-events', 'none');
var node = vis.selectAll("g.node")
.data(nodes)
.enter().append("g")
.attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; })
node.append("circle")
.attr("r", 5)
.attr('pointer-events', 'none');
// ------------- trickery to avoid collision detection
// phantom node to give us mouseover in a radius around it
node.append("circle")
.attr("r", 60)
.attr("opacity", 0.0) // change this to non-zero to see the target area
.attr('pointer-events', 'mouseover')
.on("mouseover", overCircle)
.on("mouseout", outCircle)
// a new, unconnected node that can be dragged near others to connect it
newNodes = [ {x:300,y:5, name: 'new'} ];
vis.selectAll(".lonely")
.data(newNodes).enter().append("circle")
.attr("r", 5)
.attr("class", "lonely")
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.call(circleDragger)
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment