Giter Site home page Giter Site logo

Animated Zoom about polymaps HOT 15 OPEN

simplegeo avatar simplegeo commented on June 23, 2024
Animated Zoom

from polymaps.

Comments (15)

RandomEtc avatar RandomEtc commented on June 23, 2024

It sounds like something that's worth profiling - for example it might be that creating new DOM nodes for svg images is time consuming (in which case we could try an object pool) or it might be that we could spread the transition from one layer to the next over a couple of frames.

Do you have a test case with setTimeout animation already that we could use for benchmarking?

If you share your code as a gist on github we can run it with Mike Bostock's http://bl.ocks.org - e.g. http://bl.ocks.org/600144 is my demo of smooth zooming that could benefit from some of the work you're proposing.

from polymaps.

mbostock avatar mbostock commented on June 23, 2024

My guess is the problem is software interpolation of images. We should push browser vendors to do better hardware acceleration of SVG. :)

from polymaps.

bentomas avatar bentomas commented on June 23, 2024

I couldn't get bl.ocks.org to work to display both my maps: http://media.benjaminthomas.org/polymaps-zooming/ That shows the issue.

In the first map, which uses just stock polymaps.js, when I zoom in and out by double clicking (hold down shift to zoom out), the animation just isn't completely smooth. It is most apparent when you are zooming to a layer you have already zoomed to before.

In the second map, I've added a new function to po.map called loadNewTiles. You pass true to it, and it'll load new tiles, pass false and polymaps will load no new tiles. Here's the gist for how it works: https://gist.github.com/789374 po.layer uses this attribute to decide whether or not to try and load in new tiles. My second map turns off new tile loading before doing the zoom animation. The resulting animation is very smooth and snappy on my computer.

On my friends computer I don't notice much of a difference between the two maps, so not everyone needs this performance benefit. However, for those of us that do (and maybe on mobile?) it is a nice feature to have.

Though, I don't know what effect this change would have on other parts of polymaps. All it affects right now is the move function in po.layer.

Anyway, there's my test case.

from polymaps.

RandomEtc avatar RandomEtc commented on June 23, 2024

Thanks for following through with this.

It's quite a big difference for me in Safari on a new MacBook Pro so it's not just mobile devices that would benefit...

I'm sure we'll see lots more people pick up Polymaps this year so this kind of investigation will be very useful. I'd like to see how this affects mousewheel zooming and zooming out as well at some point.

Perhaps we could think about allowing people to tune performance for themselves. I wonder if some sort of quality settings should be exposed, to favor animation over seamlessness for example.

from polymaps.

mbostock avatar mbostock commented on June 23, 2024

You can somewhat emulate this behavior by temporarily setting the layer's zoom attribute:

http://polymaps.org/docs/layer.html#zoom

If you lock the layer's zoom level, it won't load any new tiles as you zoom in. Then when the animation completes you can reset the zoom constraint to null. This is a little bit different than preventing new tiles from loading, though it should be practically the same if the tiles are already displayed pre-animation.

from polymaps.

bentomas avatar bentomas commented on June 23, 2024

I'll have to try setting the layer's zoom attribute. That makes sense. Though, it'll still be a pain to have to loop through all the layers and do that to all of them.

I'll try it out this weekend and see what affect it has on performance.

from polymaps.

bentomas avatar bentomas commented on June 23, 2024

I finally got around to checking this out (busy week) and setting the layer's zoom attribute won't work for me because I don't know what layers have been added to the map at runtime. And at least as far as I can tell from the documentation and looking at the source code, the map doesn't actually know what layers have been added to itself. It just notifies the events. If I need to I could write a wrapper API around a map and add my layers via that, and then have that keep track of them, but I'd rather not have to take that extra step.

That said, I've updated my comparison page: http://media.benjaminthomas.org/polymaps-zooming/ with this new technique. It works quite well for zooming in, but zooming out is still slowed down a little bit, from loading in the new tiles around the edge.

My solution to that new issue would be to make it so you can tell it to load in new tiles around the edge before you do the animation. This would also be useful in the case of panning the map when you know ahead of time you are going to need more tiles.

Basically, I think more control over how and when new tiles are loaded would really go a long ways to providing a really smooth, crisp UI.

Any advice would be appreciated. I'm happy to work more on this (and will for my own personal project), but would love it if my contributions could be helpful to the greater polymaps community. So, if we could settle on an API, I'll make it happen.

from polymaps.

bentomas avatar bentomas commented on June 23, 2024

Alright, I didn't get any API suggestions so I had to be left to my own devices :)

I've updated my comparison page (http://media.benjaminthomas.org/polymaps-zooming/) with a 4th option, which I have in my forked version (https://github.com/bentomas/polymaps). It adds to methods, one (1) to a layer to address this animation issue and another (2) to the map to address my issue about being able to run a layer method on all the current layers of the map.

  1. To mirror the Layer zoom method, I added a method called tileArea for explicitly setting the area that polymaps uses when pulling in new tiles. You can check out the commit message (https://github.com/bentomas/polymaps/commit/2cf26bc09c1f50e08d35316b3fb3d1556cc1ae0d) for the details. But basically, it allows you to specify a hard coded pixel region to use (which I would use for my zoom animation) or specify a function which should calculate this region at run time (to give you lots of flexibility, like the ability to buffer tiles outside the visibile region). My only other idea for this is to allow you to specify an array of regions/functions so you could do any ol' configuration imaginable.

  2. I added a method called runLayerMethod to the map, which dispatches a 'runLayerMethod' event that the layers listen on. This allows you to run a method on all attached layers (like zoom() or tileArea()) without having to explicitly have a reference to all the layers. This isn't necessary, but I find it handy.

You'll notice that the 4th map on the comparison page (the one with the features I just described) is just as snappy in both directions as my previous attempt, and snappier than the existing Polymaps options.

from polymaps.

bentomas avatar bentomas commented on June 23, 2024

Any thoughts on this?

from polymaps.

mbostock avatar mbostock commented on June 23, 2024

Hi Benjamin,

Thanks for implementing the helpful comparison page so that we can evaluate the different options. I agree that the performance of smooth zooming can be dramatically improved.

I'm inspired by your diymaps.com and would love to make this the default behavior for pan and zoom. It is nary impossible to get identical zoom behavior across browsers due to lack of standardization in mousewheel events, so analog zooming is often too slow or too fast. And analog zooming results in fractional zoom levels, reducing performances and introducing anti-aliasing artifacts.

It looks like you implemented the scrolling behavior by using an invisible scrolling div overlay. Is this behavior more reliable than listening to mouseevents directly, and using wheelDeltaX and wheelDeltaY? Or using MozMousePixelScroll?

Back on-topic, let's enumerate the (non-exclusive) options for improving performance of smooth zooming:

  1. Disable loading of new tiles.
  2. Disable swapping of tiles from the cache.
  3. Setting a transform on the entire map rather than individual tiles.

I expect that the highest performance penalty comes from #1. Fixing the zoom level on a layer doesn't explicitly disable loading of new tiles or swapping tiles in from the cache, but in the common case it will on zoom in, as the currently-displayed tiles will remain. (Some of the tiles may be removed on zoom-in.) On zoom-out, new tiles may be displayed even with a fixed zoom level, hence the need for tileArea.

I do like the idea that you can tell the layer to preload tiles that are off-screen, which might be useful for an animated pan & zoom (e.g., van Wijk's). However, preloading would typically be implemented as a hint to the layer, rather than a hard constraint.

#3 is tempting; for example, you could set a -webkit-transform-3d on the entire map, and then get hardware acceleration for the zoom in and out. However, it'd be difficult to do that without disabling interaction during the zoom animation, as you'd want to make sure that the map understands that the coordinate space is changing during zoom. Also, I'd guess that setting the transform on the enclosing layer (the highest container) would not improve performance noticeably over simply updating the transforms on the individual tiles as we do now.

So that leaves loadNewTiles, which seems like the most explicit way to achieve the desired performance boost for zooming. I'm not sure about the name, though, because it also stops swapping in tiles from the cache (which are not by some definition "new"). Maybe lockTiles?

Or perhaps we have a way to pause the queue, so that Polymaps can swap in tiles from the cache, but cannot load new tiles from the server? That might be the simplest option, because it seems like the default behavior is just as fast as the optimized behavior, provided all the tiles are cached.

from polymaps.

bentomas avatar bentomas commented on June 23, 2024

Sorry, for taking so long to respond to this.

About the panning on diymaps.com, I really like the way it works but it
has it's flaws. In Firefox and Opera it can be a little "stuttery" (but the
bigger the map is, the better it feels), in IE I can only scroll vertically, but
I don't know if that is a limitation of running Windows in a Virtual Machine.
See http://bentomas.github.com/smooth-scrolling/ for most of my thoughts on it.).
The reason I didn't use other mouse events or wheelDeltaX and Y was that I
didn't think those achieved as natural a feel. I wanted it to feel like
scrolling a normal page, and didn't want to make any assumptions about user
preferences in terms of scrolling speeds or what not. I can't definitively say
that achieving a perfectly natural feel isn't possible using those other
techniques, because my tests were limited. But this was the best way I could
figure out how to do it easily and consistently in all browsers.

Right now it actually puts the map inside a div with scroll bars (and then
hides those scrollbars with overflow: hidden;). Then as scrolls happen it
resets the scroll position. It would be possible to not get the firefox/opera
stutter by not putting the content inside the scrollbar'ed element but behind
it. Sort of like what I think you were guessing above. The problem with this
method is that then no mouse click events propegate to the layer below. So, if
you say, want to add svg markers via geojson or something, those markers will
not be able to receive mouse events. Now, right now I have written this scrolling
business in a way that tries to make no assumptions about how it is being used,
but if you wanted to have a polymaps specific solution, I think you could maybe
get a better solution:

Basically, add a 'panning' layer, that goes above the map tiles (or above any layers
that don't need to receive mouse events). This layer will receive the scroll events
but most importantly will not have any content inside it, so you don't get the stutter.
Then add other layers above it with all elements that should be interacted
with.

The one other idea I had was to use dispatchEvent
to try and pass click events to the layer underneath, but I haven't had time to
test this to see how versatile it can be.

Sorry, if none of that makes much sense, I've been thinking a lot about it, but
am not sure how clear I'm being. Next weekend I'll try and test some of these
different methods (some more) to see how they work and try and get a comparison
page up for them.

As for this animation business, 3 thoughts:

  1. I think lockTiles is an infinitely better name than loadNewTiles.
  2. I still get some performance degredation even once the tiles have been cached,
    I think this comes from calculating what tiles should be visible. So, I'm not sure
    just working with the cache would be the best solution.
  3. Regarding preloading, if the tile loading properely prioritizes
    preloading visible tiles, then tileArea is a hint no matter what... I remember
    reading that it already loads tiles from the center out so it seems to me that isn't
    and issue. Something that would be useful I guess is
    if the map is panned and some tiles that were requested (via tileArea) but aren't
    loaded yet, it could indicate that those tiles shouldn't be loaded. It may already
    do this, too, but I'm not sure, I haven't looked at that part of the code.

In conclusion, lockTiles is definitely the simplest solution, but I thought the
tileArea method added the most power while still getting the performance gains.
I think it could be worked so that the area is a hint, but I'd have to look into
how tiles are loaded more thoroughly.

from polymaps.

mbostock avatar mbostock commented on June 23, 2024

Coincidentally, I played with using dispatchEvent last night. From what I can tell, you can listen to "mousewheel" events and make them exactly compatible with the default scrolling behavior, the only problem is this bug that affects Safari 5 and certain versions of Chrome:

https://bugs.webkit.org/show_bug.cgi?id=29601
https://lists.webkit.org/pipermail/webkit-dev/2010-June/013152.html

What I did what wait until I receive the first "mousewheel" event, then dispatch that to an invisible scroll area. Essentially something like this:

<div style="position:absolute;width:0;height:0;overflow:scroll;">
  <div style="width:2048px;height:2048px;">
  </div>
</div>

Then, after dispatching the event, look at scrollLeft and scrollTop on the outer div, and you can see what the mapping is between the mousewheel event's wheelDelta and the scroll amount. If the wheel delta is 120x the scroll amount then you have the bug, and you also know how to normalize future scroll events. (You have to be careful to initialize the scroll bars to allow scrolling up/down/left/right, and also large enough so that the first even you receive doesn't hit the end.)

So, once you have this normalization factor, then the wheelDelta should correspond exactly to the number of pixels that would be scrolled natively, and you can eliminate the extra scrollable containers. It's a pain in the butt, yes, but it appears to work 100% of the time, much more reliable than my speed-detection heuristic in Polymaps. Of course, you still have the handle nonstandard behavior in Opera, Firefox and presumably IE9. But at least those never had WebKit's bug so the interpretation of events is straightforward.

from polymaps.

RandomEtc avatar RandomEtc commented on June 23, 2024

I'm just posting to say I love this thread and I'm sorry I can't contribute more fully.

Ben, I agree with Mike that your scrolling experiment is really informative and interesting and points the way for a more diverse post-Google-Maps world. I'm also glad you're pushing on some of the performance limits of polymaps and I think you're onto something with the tile lock. I'd urge you to profile the visibility calculations before optimizing around them though, I'm not convinced they'll be anywhere near as slow as DOM manipulation or loading/parsing images. (Sorry to be vague, but I did a lot of profiling in modestmaps js which does similar calculations and on modern browsers only 1 or 2ms typically elapse - I'd expect similar in polymaps). Perhaps time for some judicious use of console.profile?

Mike, I'm psyched that you're still trying to solve the mouse wheel normalization issues - and getting somewhere!

Sorry I can only offer cheerleading at this time, but I hope it's better than nothing!

from polymaps.

bentomas avatar bentomas commented on June 23, 2024

RE: panning with the scroll wheel

I have a new version of my scrolling library out: https://github.com/bentomas/smooth-scrolling

I looked into the stuff you mentioned you had done with dispatchEvent, and was impressed by how well using dispatchEvent worked in webkit. No extra elements, and as smooth as pie! However, I found using it to normalize the wheelDeltaX didn't produce a reliable normalization factor (though I'm not sure if was doing it in exactly the same way you were). I also didn't like that I was checking for the existence of a bug though, and since just dispatching the event seems to work great left it at that.

Firefox can't handle dispatchEvent but its MozMousePixelScroll event does everything we need it to. So, I just use that. But fall back to DOMMouseScroll if that event isn't available.

IE and Opera both can't handle horizontal scrolls, so they just scroll vertically. Which is technically a regression over the previous version, where it could scroll any direction you could make it with a scroll wheel. But I figured smooth and simple scrolling in Firefox, Chrome, Safari outweighed that.

So, now my map panning is a lot simpler. And doesn't involve embedding the map inside two divs and such.

I made a polymaps layer which does all this, called wheelPan. So it can work seamlessly with polymaps and not depend on an external library. I'll send a pull request over right after I comment this.

from polymaps.

dcporter avatar dcporter commented on June 23, 2024

Hi – for future googlers, this issue appears to be resolved by the po.wheel() method, documented here. For current polymaps maintainers, this issue appears to be resolved by the po.wheel() method and might be closable. =)

from polymaps.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.