Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v4.0 - d3.zoom() prevents binding events on the SVG or it's elements #2887

Closed
yairEO opened this issue Jul 5, 2016 · 12 comments
Closed

v4.0 - d3.zoom() prevents binding events on the SVG or it's elements #2887

yairEO opened this issue Jul 5, 2016 · 12 comments

Comments

@yairEO
Copy link

yairEO commented Jul 5, 2016

After changing to the new v4.0 D3, seems like using d3.zoom() blocks other events somehow, like:

Calling this:

svg.call(d3.zoom()
     .scaleExtent([1, 40])
     .translateExtent([[-100, -100], [width + 90, height + 100]])
     .on("zoom", zoomed));

And then this:

document.querySelector('svg').addEventListener('mouseup', function(){ alert(1) })

Won't alert anything.

Demo page

@mbostock
Copy link
Member

mbostock commented Jul 5, 2016

This is the expected behavior. Per the release notes: “The zoom behavior now consumes handled events.” So, if a mouseup event was part of a zoom gesture, then the zoom behavior stops the immediate propagation of that mouseup event preventing other listeners from receiving it (and where possible prevents the associated browser default behavior). This makes it easier to combine zooming and dragging as in this example:

http://bl.ocks.org/mbostock/3127661b6f13f9316be745e77fdfb084

If you want to prevent a zoom gesture from starting, use zoom.filter to ignore certain events, or use event.stopPropagation or event.stopImmediatePropagation to prevent the zoom behavior from receiving the initiating event. If you want to do something after a zoom gesture, listen to the zoom event.

@mbostock mbostock closed this as completed Jul 5, 2016
@yairEO
Copy link
Author

yairEO commented Jul 5, 2016

Thanks for answering, but I wish to explain further my use case:

I need the zoom to have the default events it occupies (mouse wheel, panning and so on)
but I also need to bind some specific events for a guided tour / analytic purposes / tooltips and these events must be delegated ones.


so for example, I with a line graph with circles points, prior to v4 I had all points (via event delegation) binded to the mouse "click" event to show a tooltip and clicking anywhere outside hides it.

or when in a guided tour scenario, I want to guide the user to click on the point in the line chart and that event fires some callback which shows some element with further explanation on that data.


Do you really think it is a must to cancel all bubbled events? This really makes it difficult for developers

@mbostock
Copy link
Member

mbostock commented Jul 5, 2016

If you put your interactive elements on top of the element to which the zoom behavior is applied (or as a descendent of a G container element to which the zoom behavior is applied) then your interactive elements can stop propagation if they handle input events, and prevent a zoom gesture from starting, as in the drag + zoom example I linked.

@yairEO
Copy link
Author

yairEO commented Jul 5, 2016

All the elements in my case (the circles of a line chart) are inside the zoomed element. there is no exact demo page from the ones you have created which is similar.

My use case is a bit more complex and I do need the mouseup event for circles in a line chart, and it would seem the mouseup event cannot be "released" after calling the .zoom() method.

I had read here, and do understand there were issues with events bubbling in some situations, but would it be too much to ask for some way to control this from outside via some configurable option?

I really do need some events to bubble up. we (at work) have been trying for hours to attack the problem without success, and we do not wish to modify the source code (bad practice).

Thank you Mike for taking the time reading this.
I could produce a test page more like our setup if it helps.

@mbostock
Copy link
Member

mbostock commented Jul 5, 2016

Sorry, but no. I expect you can achieve your desired result without modifying the zoom implementation, but I don’t have time right now to make a more specific example for you other than the ones I have already linked (and are linked from the README).

If you want to allow the zoom gesture but still listen to a mouseup handled by the zoom behavior then again I recommend listening to the zoom behavior’s end event so that you can do something on the end of a zoom gesture (or specifically mouseup by looking at d3.event.sourceEvent).

Alternatively I expect you could still intercede on the mouseup event by using a capturing listener (passing true as the third argument to selection.on). You might need to listen to the window rather than the specific element you want to receive the mouseup, though, since capturing events bubble the other way.

Anyhow, I am afraid this is all the help I can provide. If you want further assistance please try Stack Overflow tag d3.js or the d3-js Google group. I am currently on vacation.

@yandongCoder
Copy link

My case is very similar with @yairEO , I need judge if click(mouse down, don't move mouse, mouse up) or drag(mouse down, move mouse, mouse up) by using mousedown and mouseup like:

var previousPosition = [];
d3.select("#my-svg").on("mousedown", function (d) {
        previousPosition = [d3.event.clientX, d3.event.clientY];
})
.on("mouseup", function (d) {
        if (previousPosition[0] === d3.event.clientX && previousPosition[1] === d3.event.clientY) {
                //it is click
        }else{
               //it is drag 
        }

After update to v4, I have same problem too, mouseup event not trigger.
But I found D3 has done this judgement(click or drag) if bind zoom to svg.

https://jsfiddle.net/fqq6g8ms/16/
1.click(mousedown, don't move mouse, mouse up) will trigger click event.
2.drag(mousedown, move mouse, mouse up) will not trigger click event.

@mbostock
Copy link
Member

More more on this topic, please read d3/d3-zoom#66 (comment)

@herrvigg
Copy link

Just trying to help (i opened d3/d3-zoom#66 and Mike's comments were very useful): the fact that d3 v4 now eats some event forces you to clearly define who does what on your items. Should d3 apply its zoom (and drag), or do you want a custom handler? At first glance you could see as it as regression, but in reality it forces you to sanitize your events chain.

If you want to combine both the d3 behavior and some events like clicked, things could get complicated and d3v4 solve this by eating the events to avoid these conflicts. You should be able to handle the events properly in your callbacks (such as zoomed). But events like clicked won't trigger. It's not wrong, it means that here you give priority to d3's zoom. You can't have both zoom and click, and you shouldn't have the need to, because it's ambiguous.

In the second case, perhaps you want a specific event handler on some of your items, but still you want d3 to manage the zoom at the highest level on the main stuff. If you want to disable the zoom only on specific items, you have to select them and do something like this:

<your selection>.on("mousedown", function() { d3.event.stopImmediatePropagation(); }

This will prevent the event to be processed by d3 and therefore it will not apply its zoom behavior on these elements. This made the trick for me and solved my issues. Hope it helps.

@johnv94
Copy link

johnv94 commented Dec 27, 2016

iam having the exact same issue, i need moouse up event to fetch data from the mouseup location , any help is much appreciated.

@johnv94
Copy link

johnv94 commented Dec 27, 2016

@herrvigg
example helps to stops zoom from lisitening to the event , but how can we invoke mouse up event after zoom event is completed

@yairEO
any luck with the query ??

@herrvigg
Copy link

herrvigg commented Jan 20, 2017

@johncna sorry for late answer. I don't know if you solved your problem already, but the main conclusion of the discussion is that with d3 v4 you have to choose between your event listeners:

  • either d3 for zoom events (zoom, end, ...)
  • either standard js for mouse events (mousedown, mouseup, ...).

You can't get both at the same time for the reasons explained above. You mention mouseup but this is a standard event you listen to, not something you invoke yourself. If you keep the zoom, maybe what you want is to detect the end of the zoom :

var myZoom = d3.zoom().on("end", zoomended);

Then you could read the position of the mouse at this moment, in a function called zoomended(). Hope it helps.

@IPWright83
Copy link

I came across this too, and have just played around a little. I was a bit confused by @mbostock's comment regarding the d3.event.stopPropagation() because I couldn't seem to see that working.

If however you go a little further, you can prevent the zoom kicking in when clicking on say a circle, by ensuring that you stopPropagation of the mousedown, preventing the zoom from ever triggering.

const zoom = d3.zoom().on("zoom", () => {
    console.log("zoomed");
});

d3.select("svg").call(zoom);

d3.select("svg")
    .selectAll("circle")
    .on("mousedown", () => {
        // Without this, clicking the circle will never trigger a mouseup, because zoom will handle mouseup
        d3.event.stopPropagation();
        console.log("mousedown");
    })
    .on("mouseup", () => {
        d3.event.stopPropagation();
        console.log("mouseup");
    });

using DOM:

<svg width="500" height="500">
  <circle cx="150" cy="150" r="50" />
</svg>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

6 participants