Search code examples
javascriptd3.jsdragforce-layoutpan

Implement panning while keeping nodes draggable in d3 force layout


I use the d3.layout.force to visualize graph structures. And I want both node dragging feature and panning feature. (By 'panning', I mean something like google maps behavior.)

As shown in several d3 examples, nodes can be made draggable by nodes.call(force.drag). And panning can be implemented by d3.behavior.drag.

However, it seems they can not be used simultaneously.

Here is a code example:

var width = 400, height = 300;
var svg = d3.select('body').append('svg').attr({width: width, height: height});

var nodes = [{}, {}, {}, {}];
var links = [
    {source: 1, target: 0},
    {source: 2, target: 0},
    {source: 0, target: 3}
];

var nodeSel = svg.selectAll('circle')
    .data(nodes).enter().append('circle')
    .attr('r', 10).attr('fill', 'black');
var linkSel = svg.selectAll('line').data(links).enter().append('line')
    .attr('stroke', 'black');

var force = d3.layout.force()
    .size([width, height])
    .nodes(nodes).links(links)
    .linkDistance(80)
    .charge(-300)
    .on('tick', function() {
        linkSel
            .attr('x1', function(d) { return d.source.x; })
            .attr('y1', function(d) { return d.source.y; })
            .attr('x2', function(d) { return d.target.x; })
            .attr('y2', function(d) { return d.target.y; });
        nodeSel
            .attr('cx', function(d) { return d.x; })
            .attr('cy', function(d) { return d.y; });
    });

nodeSel.call(force.drag);


/* This block of codes spoils force.drag */
/* ****************************************
var drag = d3.behavior.drag();
var viewBoxX = 0, viewBoxY = 0;
drag.on('dragstart', function() {
    console.log('new dragstart is called');
}).on('drag', function() {
    viewBoxX -= d3.event.dx;
    viewBoxY -= d3.event.dy;
    svg.attr('viewBox', [viewBoxX, viewBoxY, width, height].join(' '));
}).on('dragend', function() {
    console.log('new dragend is called');
});
svg.call(drag);
**************************************** */


force.start();

(Working one is on jsfiddle: http://jsfiddle.net/dersinces/hzL8T/1/.)

This code enables only node dragging, and not panning. Activating a code block at bottom (line 36) enables panning but it makes nodes undraggable.

How can I implement graph panning while keeping nodes draggable?


Solution

  • Here is how you can do it: Demo

    The idea is to put the region which had the draggable behavior under the area which contains the nodes so that nodes can still receive mouseevents.

    // First append the area where the dragging will happen.
    svg.append('rect')
      .classed('bg', true)
      .attr('stroke', 'transparent')
      .attr('fill', 'transparent')
      .attr('x', 0)
      .attr('y', 0)
      .attr('width', width)
      .attr('height', height)
      .call(drag);
    
    
    // Then add the area which will contain the nodes.
    var nodeArea = svg.append('g')
                     .classed('node-area', true);