Jake Wilson
Jake Wilson

Reputation: 91233

d3.behavior.zoom() jumps around

I have the following d3 chart. It's simply a handful of nodes and links between them. The chart does 2 things:

  1. You can click and drag individual nodes around the graph.
  2. You can click and drag around the svg in order to pan (and zoom with mousewheel) using d3.behavior.zoom.

My problem I'm seeing is that if I drag a node around (for example, move a node 20px to the right), and then try to pan the chart, the entire graph immediately jumps 20px to the right (per the example). d3.behavior.zoom seems to initially use the d3.event of dragging the node around or something like that. Not sure. Here is the code:

var data = {
  nodes: [{
    name: "A",
    x: 200,
    y: 150,
    size: 30
  }, {
    name: "B",
    x: 140,
    y: 300,
    size: 15
  }, {
    name: "C",
    x: 300,
    y: 300,
    size: 15
  }, {
    name: "D",
    x: 300,
    y: 180,
    size: 45
  }],
  links: [{
    source: 0,
    target: 1
  }, {
    source: 1,
    target: 2
  }, {
    source: 2,
    target: 3
  }, ]
};

var dragging = false;

var svg = d3.select("body")
  .append("svg")
  .attr("width", "100%")
  .attr("height", "100%")
  .call(d3.behavior.zoom().on("zoom", function() {
    if (dragging === false) {
      svg.attr("transform", "translate(" + d3.event.translate + ") scale(" + d3.event.scale + ")")
    }
  })).append("g");

var links = svg.selectAll("link")
  .data(data.links)
  .enter()
  .append("line")
  .attr("class", "link")
  .attr("x1", function(l) {
    var sourceNode = data.nodes.filter(function(d, i) {
      return i == l.source
    })[0];
    d3.select(this).attr("y1", sourceNode.y);
    return sourceNode.x
  })
  .attr("x2", function(l) {
    var targetNode = data.nodes.filter(function(d, i) {
      return i == l.target
    })[0];
    d3.select(this).attr("y2", targetNode.y);
    return targetNode.x
  })
  .attr("fill", "none")
  .attr("stroke", "white");

var nodes = svg.selectAll("node")
  .data(data.nodes)
  .enter()
  .append("circle")
  .each(function(d, i) {
    d.object = d3.select(this);
    console.log(d);
  })
  .attr("class", "node")
  .attr("cx", function(d) {
    return d.x
  })
  .attr("cy", function(d) {
    return d.y
  })
  .attr("r", function(d) {
    return d.size;
  })
  .on('click', function(d) {

  })
  .call(d3.behavior.drag()
    .on('dragstart', function() {
      dragging = true;
    })
    .on('dragend', function() {
      dragging = false;
    })
    .on("drag", function(d, i) {
      d.x += d3.event.dx
      d.y += d3.event.dy
      d3.select(this).attr("cx", d.x).attr("cy", d.y);
      links.each(function(l, li) {
        if (l.source == i) {
          d3.select(this).attr("x1", d.x).attr("y1", d.y);
        } else if (l.target == i) {
          d3.select(this).attr("x2", d.x).attr("y2", d.y);
        }
      });
    })
  );

Here is a demo:

https://jsfiddle.net/25q1Lu3x/2/

How can I prevent that initially jump when trying to pan around? I have read a few things that suggest making an extra nested svg g element and move that around, but that doesn't seem to make a difference.

Upvotes: 1

Views: 454

Answers (1)

Gerardo Furtado
Gerardo Furtado

Reputation: 102208

According to the D3 v3 API:

When combining drag behaviors with other event listeners for interaction events (such as having drag take precedence over zoom), you may also consider stopping propagation on the source event to prevent multiple actions

So, in your dragstart, just add d3.event.sourceEvent.stopPropagation();:

.on('dragstart', function() {
    d3.event.sourceEvent.stopPropagation();
    dragging = true;
});

Here is your fiddle: https://jsfiddle.net/hj7krohd/

Upvotes: 2

Related Questions