Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

d3 v4 - zoom with buttons in conflicts with zoom behaviour

I have created a gist with my problem.

I've started from this example.

So my problem is that I need some zoom in and zoom out buttons in addition to mouse controls.

Mouse controls (zooming with wheel and pan by dragging) are implemented with zoom() behaviour. It works pretty well.

Then I've added two buttons for zoom in and zoom out:

var _zoom = d3.zoom()
    .scaleExtent([1 / 2, 8])
    .on("zoom", zoomed);
var gui = d3.select("#gui")
gui.append("span")
  .classed("zoom in", true)
  .text("+")
  .on("click", function() { _zoom.scaleBy(container, 2); })
gui.append("span")
  .classed("zoom out", true)
  .text("-")
  .on("click", function(){ _zoom.scaleBy(container, 0.5); })

They are in conflict with the mouse behaviour. To reproduce the bug you have to zoom and drag around (with the mouse controls) and then click on the + span: the current transformation is overwritten.

How to resolve it?

like image 773
nkint Avatar asked Oct 25 '16 17:10

nkint


1 Answers

The key thing to remember is that the zoom transform is stored on the node, NOT in the _zoom object.

In your case you set the mouse listener on the svg node,

svg.call(_zoom);

When a zoom event is triggered on the svg element, you then take the updated zoom transform of the svg node and apply it to the g node,

g.attr("transform", d3.event.transform);

All you need to do when updating the zoom of the g element on the button click is grab the correct transform, in your case that of the svg node,

 _zoom.scaleBy(svg, 0.5);

Which will trigger the zoom event on the svg node correctly, updated in full below,

var svg = d3.select("svg"),
  width = +svg.attr("width"),
  height = +svg.attr("height");
svg.style("border", "solid 1px black");
var points = d3.range(2000).map(phyllotaxis(10));
var g = svg.append("g");

var _zoom = d3.zoom()
  .scaleExtent([1 / 2, 8])
  .on("zoom", function() {
    g.attr("transform", d3.event.transform);
  });
svg.call(_zoom);

var container = g.selectAll("circle")
  .data(points)
  .enter().append("circle")
  .attr("cx", function(d) {
    return d.x;
  })
  .attr("cy", function(d) {
    return d.y;
  })
  .attr("r", 2.5)

function phyllotaxis(radius) {
  var theta = Math.PI * (3 - Math.sqrt(5));
  return function(i) {
    var r = radius * Math.sqrt(i),
      a = theta * i;
    return {
      x: width / 2 + r * Math.cos(a),
      y: height / 2 + r * Math.sin(a)
    };
  };
}
var gui = d3.select("#gui");
gui.append("span")
  .classed("zoom in", true)
  .text("+")
  .on("click", function() {
    _zoom.scaleBy(svg, 2);
  });
gui.append("span")
  .classed("zoom out", true)
  .text("-")
  .on("click", function() {
    _zoom.scaleBy(svg, 0.5);
  })
<svg width="960" height="500"></svg>
<div id="gui"></div>
<script src="https://d3js.org/d3.v4.min.js"></script>
like image 78
Andrew Avatar answered Sep 23 '22 06:09

Andrew