faerin
faerin

Reputation: 1925

d3js jumpy zoom behavior

I am exploring the d3js (version 4) library and ran into the following issue while playing around with the zoom behavior.

When using the mousewheel in order to zoom after triggering a programmatic zoom by clicking at the svg it causes a laagy/jumpy behavior where it loses it's position.

I found this Stack Overflow resource: d3.js pan and zoom jumps when using mouse after programatic zoom and figured that might help me out. But it did not.

I've set up a simple example so that you can see what I mean. What am I missing here?

$(document).ready(function() {
    var svg = d3.select('svg');
    var group = d3.select('g#content');
    
    svg.call(
        d3.zoom().scaleExtent([1, 30]).on('zoom', function() {
            group.attr('transform', d3.event.transform);
        })
    );
    
    svg.on('click', function() {
        group
          .transition()
          .duration(1000)
          .attr("transform", "translate(100,100) scale(2)");
    });
});
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<svg width="1200" height="500" viewBox="0 0 1200 500">
    <g id="content">
       <path d="M10,10   l50,0  0,50  -50,0  0,-50 Z" />
       <path d="M70,10   l50,0  0,50  -50,0  0,-50 Z" />
       <path d="M130,10   l50,0  0,50  -50,0  0,-50 Z" />
       <path d="M190,10   l50,0  0,50  -50,0  0,-50 Z" />
       <path d="M250,10   l50,0  0,50  -50,0  0,-50 Z" />
       <path d="M310,10   l50,0  0,50  -50,0  0,-50 Z" />
    </g>
</svg>

Edit By adding the following lines to my event listener the behavior improves while zooming in. It still jumps while I zoom out;

var transform = d3.zoomTransform(group.node());
transform.x = m.x;
transform.y = m.y;
transform.k = scale;

Full updated snippet:

$(document).ready(function() {
    var svg = d3.select('svg');
    var group = d3.select('g#content');
    
    svg.call(
        d3.zoom().scaleExtent([1, 30]).on('zoom', function() {
            group.attr('transform', d3.event.transform);
        })
    );
    
    svg.on('click', function() {
        group
          .transition()
          .duration(1000)
          .attr("transform", "translate(100,100) scale(2)");

          var transform = d3.zoomTransform(group.node());
          transform.x = 100;
          transform.y = 100;
          transform.k = 2;

    });
});
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<svg width="1200" height="500" viewBox="0 0 1200 500">
    <g id="content">
       <path d="M10,10   l50,0  0,50  -50,0  0,-50 Z" />
       <path d="M70,10   l50,0  0,50  -50,0  0,-50 Z" />
       <path d="M130,10   l50,0  0,50  -50,0  0,-50 Z" />
       <path d="M190,10   l50,0  0,50  -50,0  0,-50 Z" />
       <path d="M250,10   l50,0  0,50  -50,0  0,-50 Z" />
       <path d="M310,10   l50,0  0,50  -50,0  0,-50 Z" />
    </g>
</svg>

Thanks in advance!

Upvotes: 3

Views: 872

Answers (2)

Mikhail Shabrikov
Mikhail Shabrikov

Reputation: 8509

When you apply zoom programmatically you should do it this way:

  svg.on('click', function() {
    svg.transition()
      .duration(750)
      .call(zoom.transform, d3.zoomIdentity
        .translate(100, 100) // set x and y transition
        .scale(2) // set scale value
      );
  });

Note, that the translate must be applied before the scale.

Also, pay attention that d3-zoom have a default behaviour (zooming) for double-click event. If you do not want it, you can disable it with this code:

svg.on("dblclick.zoom", null);

Working demo:

$(document).ready(function() {
  var svg = d3.select('svg');
  var group = d3.select('g#content');
  var zoom = d3.zoom().scaleExtent([1, 30]).on('zoom', function() {
    group.attr('transform', d3.event.transform);
  });

  svg.call(zoom);

	svg.on("dblclick.zoom", null);
  
  svg.on('click', function() {
    svg.transition()
      .duration(750)
      .call(zoom.transform, d3.zoomIdentity
        .translate(100, 100)
        .scale(2)
      );
  });
});
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<svg width="1200" height="500" viewBox="0 0 1200 500">
  <g id="content">
    <path d="M10,10   l50,0  0,50  -50,0  0,-50 Z" />
    <path d="M70,10   l50,0  0,50  -50,0  0,-50 Z" />
    <path d="M130,10   l50,0  0,50  -50,0  0,-50 Z" />
    <path d="M190,10   l50,0  0,50  -50,0  0,-50 Z" />
    <path d="M250,10   l50,0  0,50  -50,0  0,-50 Z" />
    <path d="M310,10   l50,0  0,50  -50,0  0,-50 Z" />
  </g>
</svg>

Upvotes: 2

kirupakaranh
kirupakaranh

Reputation: 239

This is because you are setting zoom values from event.transform (mouse wheel zoom events) while initializing zoom whereas in your click event handler you're setting a static value for transform attribute.

So the next time, a zoom event happens, event.transform value and your svg transform value will be different. So it jumps.

For eg: You zoom using mouse wheel. event.transform will be (500x, 500y, 5k). You do a mouse click and transform attribute will be set to (100x, 100y, 2k). The next time you do a mouse wheel event, event.transform will change from (500x, 500y,5k) say (600x, 600y, 6k). But since your svg transform attribute is (100x, 100y, 2k), it seems like it jumps.

Upvotes: 1

Related Questions