d3 / d3-zoom

Pan and zoom SVG, HTML or Canvas using mouse or touch input.
https://d3js.org/d3-zoom
ISC License
502 stars 143 forks source link

d3 v4 pan not working till after wheel scroll #189

Closed velociwabbit closed 4 years ago

velociwabbit commented 4 years ago

I am not sure if this is a bug or a design feature. (I could argue both ways).

I have setup a rect with a zoom call as follows

 scatter.append("rect").attr("width", width )
 .attr("height",  height ).attr("x", 0).attr("y", 0) .style('fill', 'transparent').call(  
        zoom.translateExtent([[0, 0], [  width,  height]]).on("zoom",  dozoom
 ) ))

But it will not generate an event call for mousedown panning until after a wheel event has fired.

Is this on purpose?

Also in looking at the transform x and y coordinates they appear to be in % of screen is this correct?

In other words when i put my mouse in the lower left corner i get [0,97.] and the opposite for the upper right corner. I just want to confirm this because I cannot move the mouse to generate a 99% on either axis.

thanks

velociwabbit commented 4 years ago

I have done some more research about this issue .

I am not at all sure what the x and y values in the transform refer to because i tested a centered zoom (where the xy cross is at w/2 h/2 and came up with some really interesting data

I start zooming and x=y=0 and k=1. then i zoom in and clientX clientY stay the same throughout all of the actions

But transform x and transform y change... which makes no sense to me (given that the clientX and cleintY have not changed... only the wheel zoom in function.

Why would x and y change values if i am zooming in using the wheel at 0,0 offset?

Also the 0,0 values for x and y when i start to pan are the reason why panning does not work. They should reflect the dragging of the mouse after the first mousemove.

I looked at the scaleX scaleY source code and it is a bit of a head spinner. There is no easy way to track the act of transforming back to an individual pixel value for a point.

If this data is all correct then what does the dishwasher do ( ok bad joke but i hope you get my point)

Transform
  x      ,y   ,k , clienX, clientY
     0     0   1.00 342 406 
   -46   -46 1.15 342 406 
   -98   -98 1.32 342 406 
  -159  -159 1.52 342 406 
  -228  -228 1.74 342 406 
  -308  -308 2.00 342 406 
  -400  -400 2.30 342 406 
  -505  -505 2.64 342 406 
  -626  -626 3.03 342 406   zoomin in
  -765  -765 3.48 342 406 
  -924  -924 4.00 342 406 
 -1107 -1107 4.59 342 406 
 -1318 -1318 5.28 342 406 
 -1559 -1559 6.06 342 406 
 -1837 -1837 6.96 342 406 
 -2156 -2156 8.00 342 406  zooming out 
 -1837 -1837 6.96 342 406 
 -1559 -1559 6.06 342 406 
 -1318 -1318 5.28 342 406 
 -1107 -1107 4.59 342 406 
  -924  -924 4.00 342 406 
  -765  -765 3.48 342 406 
  -626  -626 3.03 342 406 
  -505  -505 2.64 342 406 
  -400  -400 2.30 342 406 
  -308  -308 2.00 342 406 
  -228  -228 1.74 342 406 
  -159  -159 1.52 342 406 
   -98   -98 1.32 342 406 
   -46   -46 1.15 342 406 
    -0    -0 1.00 342 406 
    40    40 0.87 342 406 
    75    75 0.76 342 406 
    132  132 0.57 342 406 
    155  155 0.50 342 406 
    175  175 0.44 342 406 
    155  155 0.50 342 406 
    132  132 0.57 342 406   way out
    105  105 0.66 342 406 
mbostock commented 4 years ago

Please use Stack Overflow tag d3.js to ask for help. Stack Overflow provides a better collaborative forum: thousands of D3-related questions have been asked there, and some answers may be relevant to you.

When asking for help, please include a link to demonstrate the issue, preferably as an Observable notebook. It is often impossible to debug from code snippets alone. Isolate the issue and reduce your code as much as possible before asking for help. The less code you post, the easier it is for someone to debug, and the more likely you are to get a helpful response.

If you have a question about D3’s behavior and want to discuss it with other users, also consider the d3-js Google Group or joining the d3-js Slack.

Thank you! 🤗

velociwabbit commented 4 years ago

so it is not a bug?

mbostock commented 4 years ago

It’s hard to tell from your description what problem you are reporting. It’s expected that transform.x and transform.y can change when you zoom in using the wheel if the mouse isn’t exactly at the origin (in the local coordinates of the zoomed element).

When reporting an issue, please include a link to a live example, preferably as an Observable notebook or as a pull request in the appropriate repository with tests. You should demonstrate that the described behavior is not the expected behavior using the minimum amount of code.

A good bug report should isolate specific methods that exhibit unexpected behavior and define precisely how expectations were violated. What did you expect the method or methods to do, and how did the observed behavior differ? By isolating the issue, you assist the investigation.

Non-actionable bugs may be closed until you provide additional information. If you can’t isolate the bug further, please reply and ask for help. However, please be patient: open-source maintainers have many competing responsibilities—fixing other bugs, answering questions, developing features, writing documentation, etc.

velociwabbit commented 4 years ago

The problem is that i am not sure that the observed it is incorrect. I will research it further this evening

sorry for not following protocol.

On Tue, Aug 27, 2019 at 2:50 PM Mike Bostock notifications@github.com wrote:

It’s hard to tell from your description what problem you are reporting. It’s expected that transform.x and transform.y can change when you zoom in using the wheel if the mouse isn’t exactly at the origin (in the local coordinates of the zoomed element).

When reporting an issue, please include a link to a live example, preferably as an Observable notebook https://observablehq.com or as a pull request in the appropriate repository https://github.com/d3 with tests. You should demonstrate that the described behavior is not the expected behavior https://github.com/d3/d3/blob/master/API.md using the minimum amount of code.

A good bug report should isolate specific methods that exhibit unexpected behavior and define precisely how expectations were violated. What did you expect the method or methods to do, and how did the observed behavior differ? By isolating the issue, you assist the investigation.

Non-actionable bugs may be closed until you provide additional information. If you can’t isolate the bug further, please reply and ask for help. However, please be patient: open-source maintainers have many competing responsibilities—fixing other bugs, answering questions, developing features, writing documentation, etc.

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/d3/d3-zoom/issues/189?email_source=notifications&email_token=ACHMT3KAUTTBGNTJTFSWDETQGWOTLA5CNFSM4IQIRO72YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOD5JHHUA#issuecomment-525497296, or mute the thread https://github.com/notifications/unsubscribe-auth/ACHMT3NQHRPLHTZFR6OQLATQGWOTLANCNFSM4IQIRO7Q .

mbostock commented 4 years ago

If you want me to look, please post live code. Good luck with your research!

velociwabbit commented 4 years ago

Ok here is live code.

The crosshairs are at exacly xy 300,300

If you move your mouse to that value and then wheel in or out (it takes some fiddling to get exactly on that value).

But once you are on it (the tool tip will show you) The transform x and transform y values increase or decrease by about 40 for every 0.20 of wheel increase or decrease.

It was my understanding that pure scale (which is a wheel in or wheel out at the center of the svg) should have d3.event.transform.x and d3.event.transform.y values of zero.

I am thinking that this is a matrix affine transformation issue or a viewing frustum change of some sort.

I have looked at the code for scaling which is a normalize then interpolate which is fine. I am struggling to understand why these x and y values are changing when clientX and clientY are staying the same.

I am flummoxed ... where are the x and y values calculated and why are they changing for ostensibly a scale from center?


<html><body style= '  margin :0px; padding:0px;  '>
<svg style= '  margin :0px; padding:0px;  '  >
 <line  class="linex" stroke="#ff00ff"          x2="600"  y1="300" y2="300"     ></line> 
 <line  class="liney" stroke="#ff00ff" x1="300" x2="300"           y2="600"      ></line> 
</svg>
<div id='loc' style="position:absolute; top :-1025px; left:100px;  display:block; z-index:1000;  line-height: 1.5;  font-weight: 400; font-family: Roboto; font-size: 16px; color: #00b4dc;   padding: 20px;  background: #ffffff; border-radius: 3px; " ></div>
<script src="https://d3js.org/d3.v4.js"></script>
<script>
var data = [  { x : "0.0005"  , y : "0.0011" }
            , { x : "0.0039"  , y : "0.0007" }
            , { x : "-0.003"  , y : "0.0005" }
            , { x :  "0.005"  , y : "0.0347" }
            , { x : "0.0005"  , y :  "0.005" }
            , { x : "0.0004"  , y : "0.0006" }
            , { x : "0.0005"  , y : "0.0006" }
            , { x : "0.0005"  , y : "0.0056" }
            , { x : "0.0049"  , y : "0.0043" }
            , { x : "0.0049"  , y : "0.0053" }
            , { x : "0.0049"  , y : "0.0017" } ]
var svg     = d3.select('svg');  
var xScale  = d3.scaleLinear().domain([-.1, .1]).range([0 ,   600])   
var yScale  = d3.scaleLinear().domain([-.1, .1]).range([ 600, 0  ]) 
var zoom    = d3.zoom().on('zoom',  () =>{  
                    document.getElementById('loc').style.left  = ( d3.event.sourceEvent.clientX + 10) + 'px' 
                    document.getElementById('loc').style.top   = ( d3.event.sourceEvent.clientY + 10) + 'px' 
                    document.getElementById('loc').innerHTML   =   '[ ' +  d3.event.sourceEvent.clientX    + ','  + d3.event.sourceEvent.clientY  + ']' + '<br/> tranx = ' +  d3.event.transform.x.toFixed(0) + '<br/>trany = ' +  d3.event.transform.y.toFixed(0) + ' <br/>tran k = ' +  d3.event.transform.k.toFixed(2)  
                    scat.attr('transform', d3.event.transform)  }) 
    var scat = svg.attr('width', 600).attr('height',600).call(zoom).insert('g', ':first-child')    
                scat.selectAll('circle').data(data).enter() .append('circle') .attr('r', 5 ).attr('cx', d => xScale(d.x) ).attr('cy', d=> yScale(d.y )).style('fill',  ()=> '#00b4dc'  )
</script>

mbostock commented 4 years ago

I think perhaps you are confusing coordinate systems.

You should set width and height attributes for your SVG element (or a viewBox), but assuming the defaults are reasonable, [Edit: I see you set them later.] the coordinate system that’s used for your SVG element has [0,0] in the top-left corner and [width,height] in the bottom-right corner. So, per the documented definition of the zoom transform, the origin is the top-left corner, and so you should only expect transform.x and transform.y to remain at 0 if you put the mouse at the top-left corner when zooming.

If you use transform.apply to compute the new location of [300,300] after zooming in on [300,300] you should notice that these coordinates don’t change (while the transform.x and transform.y will).

In other words, the zoom behavior doesn’t know anything about your scales. It uses the local coordinate system of whatever SVG element you attach it to. You can use transform.rescaleX and transform.rescaleY to compute the new transformed scales after zooming.

Side note: pasting your complete code into this thread is better than a snippet or a description, but by live code I meant something like observablehq.com where I can run your code just by clicking a link. Also: from the looks of your code you can upgrade to v5 without making any changes and I would highly recommend it because there have been many improvements over the last couple of years.

velociwabbit commented 4 years ago

Cheers for your advice and timely response.

I will endeavor to learn how to do observablehq.

And will upgrade to v5 immediately.

I spent most of the afternoon reading your code. As a long time javascript lover (who used to code in both c and c++ ) I noticed that you do a neat trick that i did not realize would work (it used to be problematic a long time ago ). (b= (a=+a) ) ? something : otherthing is really cool. it takes care of the coercion issue and assignment in one step. Kudos.

There is another one that mystifies me ... return (bottom, axis) as a closure I have never seen before. I know that someone else very cleverly documents their functions by assign a function with a comma and some value afterward something like this:

function (){}, 'text of some sort'

But i have never seen a return as a functionless closure.

(he said sheepishly hoping for a hint as to how to read it :)

mbostock commented 4 years ago

If you use Observable it will be easier for me to send suggestions on how to fix your code at least. 😄 Were you referring to this?

https://github.com/d3/d3-axis/blob/15e56eb34ddc6c45e81bb89f6cd7e3a25bd13eb7/src/axis.js#L169

D3 tends to do some things for brevity or efficiency, not always for legibility. I try to use notebooks more for explaining. Good luck to you!

velociwabbit commented 4 years ago

After thinking about your explanation for a few minutes I realized that I had a different understanding of how scaling works. I assumed that there was something really tricky or matrixy about how it works.

So for the rest of the mere mortals who might be confused I will do my best to explain it. In fact it is fairly simple.

if you start out at with the mouse at the upper left corner at 0,0 the scaling of will end up pushing the image to the right and down the page like you would expect to see if instead you were to metaphorically drag a little resize tab (on the lower right of many text or dialog boxes ) down and to the right. A picture would get wider and taller but the xy zero zero corner is still at the same location.

When you scale with the mousewheel you increase x b y80 px and y by 80 px per mouse wheel movement (approximately 20%)

If you move your mouse to the absolute middle of the page the scale will adjust for the centeredness by equally clipping the top left right and bottom of the input domain.

WIth the same viewing area (the range) what you are in effect doing is making the domain smaller with a new set of coordinates that have equal clipping on all four sides. This is why it zooms perfectly at the center of the page .

If one were to try and emulate this effect without using 3d zoom one would have to modify the input domain low and high values to be closer together . WIth the same display range this will have the effect of magnification (or zooming).

A little ham fisted but hopefully someone will find it useful.

velociwabbit commented 4 years ago

Yes that link is the one... I stared at it, stood on my head for awhile and shook my laptop up and down like a snow globe and still no luck. I felt like one of the speaking apes in the charlton heston version. of planet of the apes.