How to temporarily disable the zooming in d3.js
Asked Answered
S

8

13

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
Sorbitol answered 13/9, 2013 at 13:56 Comment(0)
R
11

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

Rich answered 21/4, 2015 at 3:29 Comment(0)
W
4

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.

Worldbeater answered 1/3, 2014 at 4:11 Comment(0)
S
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.

Soule answered 13/9, 2013 at 15:30 Comment(1)
Thank you for your proposal. The problem is that in my project I do not assign the zoom behavior from the on('click', ... ) method. The redrawOnZomm() function should behave according to the state (color) of the circle.Sorbitol
S
2

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.

Sorbitol answered 16/9, 2013 at 8:22 Comment(1)
This still only works for zooming, for translation there is still jumpy behaviour if you drag the circle while disabled, re-enable it, and drag it again.Tortuosity
E
2

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();
        }
     }
}
Eb answered 28/1, 2015 at 11:38 Comment(1)
The zoomstart and zoomend events have been renamed start and end in one of the latest releasesGovernance
U
1

https://github.com/d3/d3-zoom/issues/156

const svg = d3.select(svgElement);
const zoom = d3.zoom();

function startZoomPan() {
  svg.call(zoom); // attach the zoom listeners
}

function stopZoomPan() {
  svg.on('.zoom', null); // remove the zoom listeners
}

mousemove handlers are hot code, so branching there is slow

Upstage answered 2/3, 2021 at 11:43 Comment(0)
H
0

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.

Hying answered 13/9, 2013 at 15:26 Comment(8)
But how should I implement that the function 'does nothing' but I can re-enable the zooming on some point and it would continue from the point where I disabled it ? I got jumps/movements in my visualization, because the event still reacts on the mouse-wheel/pointer.Sorbitol
You could simply save the current state of zoom/translate every time the function does something and then resume from that by reassigning when the zoom is reenabled.Hying
That is what I tried to achieve ... Could you please provide more detailed information how to achieve this with dr.js ?Sorbitol
You save the values of zoom.translate and zoom.scale in a global variable each time you're executing the zoom function, but not if zooming is disabled. Then, when zooming is reenabled, you assign those values back to your zoom behaviour.Hying
Is this not what I am doing in my example ? The problem is, that the zoom interacts with the mouse wheel/pointer the whole time so the d3.event.scale/translate values changes too. My problem is how set/revert those values to the saved ones after the zooming is re-enabled. Apparently zoom.scale(savedScale)/zoom.translate(savedTranslation) does not the trick.Sorbitol
Actually your example works fine for me -- I don't get any jumping.Hying
I edited the description and attached several simply steps, how can you test the described behavior. Use the original jsfidle - link in the description, not the one edited by prayerslayer. Use the mousewheel in one direction, try to zoom in when zooming is disabled.Sorbitol
Thank you very much for your patience. I found the error myself, yet thanks for trying to help me!Sorbitol
H
0

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 + ')');
     }

};
Haff answered 3/1, 2014 at 5:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.