-->

How to temporarily disable the zooming in d3.js

2020-07-06 08:28发布

问题:

I am searching for a possibility to temporarily disable the zooming functionality provided by the d3 library. I tried to save the cave the current scale/translation values when the zooming is deactivated and set the zoom/translate-values when the zooming is active again. Unfortunately this will not work.

Here is a code example I created :

var savedTranslation = null;
var savedScale = null;

var body = d3.select("body");

var svg = body.append("svg");

var svgContainer = svg.append("svg:g");

var circle = svgContainer.append("svg:circle")
    .attr('cx', 100)
    .attr('cy', 100)
    .attr('r',30)
    .attr('fill', 'red');

circle.on('click', clickFn);

function clickFn(){
    if (circle.attr('fill') === 'red'){
        circle.attr('fill','blue')
    }
    else if (circle.attr('fill') === 'blue'){
        circle.attr('fill','red')
    }
}; 

svg.call(zoom = d3.behavior.zoom().on('zoom', redrawOnZoom)).on('dblclick.zoom', null);


 function redrawOnZoom(){
     if (circle.attr('fill') === 'red'){
         if (savedScale !== null){
             zoom.scale(savedScale)
             savedScale = null
         }
         if (savedTranslation !== null){
             zoom.translate(savedTranslation)
             savedTranslation = null
         }
         // the actual "zooming"
         svgContainer.attr('transform', 'translate(' + d3.event.translate + ')' + ' scale(' +         d3.event.scale + ')');
     }
     else {
         // save the current scales
         savedScale = zoom.scale()
         savedTranslation = zoom.translate()
     }
};

Here is a working jsfiddle example.

EDIT:

The false behavior can be reproduced by following steps :

  1. Click on the circle, the color changes to blue,zooming is not working
  2. Use mouse wheel IN ONE DIRECTION several times as if you would be zooming (e.g. zoom in)
  3. Click again on the circle, the color chnages to red, zoom is re-enabled
  4. Use mouse wheel, the circle will be huge/tiny

回答1:

The easiest way I've found is to simply disable all the .zoom events on the selection. You'll have to re-call zoom to enable the behavior again.

if (zoomEnabled) {
    svg.call(zoom);
} else {
    svg.on('.zoom', null);
}

jsfiddle



回答2:

I have been struggling with the same problem. And, I have found a solution that saves zoom and translation without the jumpiness that you see with the current solution.

The main change is to perform the saving/updating of zoom and translate in the "click" function. And so that a reference to the zoom function is available, the click must be set after the zoom behavior. The solution looks like this. The same boilerplate from your problem:

var savedTranslation = null;
var savedScale = null;

var body = d3.select("body");

var svg = body.append("svg");

var svgContainer = svg.append("svg:g");

var circle = svgContainer.append("svg:circle")
    .attr('cx', 100)
    .attr('cy', 100)
    .attr('r',30)
    .attr('fill', 'red');

Then the zoom function, without managing the saved scale and translate:

svg.call(zoom = d3.behavior.zoom().on('zoom', redrawOnZoom)).on('dblclick.zoom', null);

function redrawOnZoom(){
     if (circle.attr('fill') === 'red'){
         // the actual "zooming"
         svgContainer.attr('transform', 'translate(' + zoom.translate() + ')' + ' scale(' + zoom.scale() + ')');
     }
};

Finally, attach the click behavior below, with the saving and setting of scale and translation:

circle.on('click', clickFn);

function clickFn(){
    if (circle.attr('fill') === 'red'){
        circle.attr('fill','blue')
         if (savedScale === null){
             savedScale = zoom.scale();
         }
          if (savedTranslation === null){
             savedTranslation = zoom.translate();
         }      
    }
    else if (circle.attr('fill') === 'blue'){
        circle.attr('fill','red')
        if (savedScale !== null){
             zoom.scale(savedScale);
             savedScale = null;
         }
         if (savedTranslation !== null){
             zoom.translate(savedTranslation);
             savedTranslation = null;
         }
    }
}; 

Here is a working version: http://jsfiddle.net/cb3Zm/1/.

However, the click event still happens when a drag occurs, and that doesn't seem ideal, but I haven't been able to fix it yet.



回答3:

See the updated fiddle: http://jsfiddle.net/prayerslayer/La8PR/1/

There I reassign an empty zoom behavior in the click handler.

function clickFn(){
    if (circle.attr('fill') === 'red'){
        circle.attr('fill','blue');
        svg.call( fake );
    }
    else if (circle.attr('fill') === 'blue'){
        circle.attr('fill','red');
        svg.call( zoom );
    }
}; 

I suppose there is a better solution as mine probably introduces memory leaks.

The advantage over a global doZoom flag is that you don't have to save and check scale and translation values because the zoom behavior continues to work (e.g. setting d3.event.scale) even though you're not altering the view.



回答4:

Yabba Dabba Doo!

Ok, the problem was in the

else {
     // save the current scales
     savedScale = zoom.scale()
     savedTranslation = zoom.translate()
 }

part. The values were called on every event, not only once after the circle changed its color. So the solution was:

else {
     // save the current scales
     if (savedScale === null){
         savedScale = zoom.scale();
     }
      if (savedTranslation === null){
         savedTranslation = zoom.translate();
     }         

and now IT WORKS ! Updated jsFiddle here.



回答5:

I wanted to catch up on this as I found a solution! Trick is to reset scale and translate in the zoomstart- and zoomend-events as well.

var zoom = d3.behavior.zoom()
    .scaleExtent([1, 10])
    .on("zoomstart", zoomstart)
    .on("zoomend", zoomend)
    .on("zoom", zoomed);

function zoomed() {
    if (circle.attr('fill') === 'red') {
        if (savedScale !== null){
             zoom.scale(savedScale);
         }
         if (savedTranslation !== null){
            zoom.translate(savedTranslation);
         }
        svgContainer.attr('transform', 'translate(' +   d3.event.translate + ')' + ' scale(' +         d3.event.scale + ')');
    }
}

function zoomend () {
    if (circle.attr('fill') === 'red') {
        if (savedScale !== null){
             zoom.scale(savedScale);
             savedScale = null;
         }
         if (savedTranslation !== null){
            zoom.translate(savedTranslation);
             savedTranslation = null;
         }
     }
}

function zoomstart (d) {
    if (circle.attr('fill') === 'red'){
         if (savedScale !== null){
             zoom.scale(savedScale)
         }
         if (savedTranslation !== null){
             zoom.translate(savedTranslation)
         }
     } else {
        if (savedScale === null) {
            savedScale = zoom.scale();
        }
        if (savedTranslation === null) {
            savedTranslation = zoom.translate();
        }
     }
}


回答6:

The way I would implement this is with a global flag that tells you whether zooming is enabled or not. Then you simply need to check whether this flag is set in the function that handles the zoom. If it is, the function does nothing.



回答7:

I think it's more beautiful to do it this way.

function clickFn(){
    if (circle.attr('fill') === 'red'){
        circle.attr('fill','blue');
        savedScale = zoom.scale();
        savedTranslation = zoom.translate();
    }
    else if (circle.attr('fill') === 'blue'){
         circle.attr('fill','red');
         zoom.scale(savedScale);
         zoom.translate(savedTranslation);
    }
}; 

svg.call(zoom = d3.behavior.zoom().on('zoom', redrawOnZoom)).on('dblclick.zoom', null);


 function redrawOnZoom(){
     if (circle.attr('fill') === 'red'){
         // the actual "zooming"
        svgContainer.attr('transform', 'translate(' + d3.event.translate + ')' + ' scale(' +         d3.event.scale + ')');
     }

};