holoviz / holoviews

With Holoviews, your data visualizes itself.
https://holoviews.org
BSD 3-Clause "New" or "Revised" License
2.69k stars 402 forks source link

Smooth datashader zoom/pan updates #6404

Open droumis opened 5 days ago

droumis commented 5 days ago

Is your feature request related to a problem? Please describe.

The update from a zoom/pan interaction on a datashaded plot is currently choppy/course/(all at once), as soon as the new raster is received.

Describe the solution you'd like

Would be nice to have a smooth transition that doesn't noticeably impact latency.

Describe alternatives you've considered

Could we use CSS transition from one image to the next?

jbednar commented 3 days ago

I'd like to see a mockup or similar approach in some other library to know what's being requested here.

ahuang11 commented 1 day ago

I presume something like Google Maps where it comes by tile, and the next pyramid level comes pretty quickly

droumis commented 1 day ago

I'd like to see a mockup or similar approach in some other library to know what's being requested here.

Here are clips of zooming/panning on the same 1 million point dataset. Of course there are significant tradeoffs and drawbacks to jupyter-scatter and pydeck/deckGL, but the point is to show the difference in the smoothness of interaction updates between solutions.

Attractor func ```python def roesslerAttractor(num): from math import inf points = [] xn = 2.644838333129883 yn = 4.060488700866699 zn = 2.8982460498809814 a = 0.2 b = 0.2 c = 5.7 dt = 0.006 minX = inf maxX = -inf minY = inf maxY = -inf for i in range(num): dx = -yn - zn dy = xn + a * yn dz = b + zn * (xn - c) xh = xn + 0.5 * dt * dx yh = yn + 0.5 * dt * dy zh = zn + 0.5 * dt * dz dx = -yh - zh dy = xh + a * yh dz = b + zh * (xh - c) xn1 = xn + dt * dx yn1 = yn + dt * dy zn1 = zn + dt * dz points.append([xn1, yn1]) minX = min(xn1, minX) maxX = max(xn1, maxX) minY = min(yn1, minY) maxY = max(yn1, maxY) xn = xn1; yn = yn1; zn = zn1; dX = maxX - minX dY = maxY - minY for i in range(num): points[i][0] -= minX points[i][0] /= dX / 2 points[i][0] -= 1 points[i][1] -= minY points[i][1] /= dY / 2 points[i][1] -= 1 return points ```

jupyter-scatter

Code ```python import numpy as np import pandas as pd import jscatter points = np.asarray(roesslerAttractor(1000000)) jscatter.plot(points[:,0], points[:,1], height=640) ```

https://github.com/user-attachments/assets/93419239-adaa-4256-95bb-798e1a7f54bc

pydeck (deckGL)

https://github.com/user-attachments/assets/57215f66-0d36-4940-9686-f68a6b6cb7b7

Code ```python import numpy as np import pandas as pd import pydeck as pdk points = np.asarray(roesslerAttractor(1000000)) df = pd.DataFrame(points, columns=["x", "y"]) layer = pdk.Layer( "ScatterplotLayer", data=df, get_position=["x", "y"], get_radius=0.005, get_fill_color=[0, 0, 0, 128], # ~50% transparency 128/255 ) view_state = pdk.data_utils.compute_view(df[['x', 'y']], view_proportion=1) deck = pdk.Deck(layers=[layer], initial_view_state=view_state, map_style=None) deck.show() ```

datashader/holoviews/bokeh

Code ```python import numpy as np import holoviews as hv from holoviews.operation.datashader import rasterize, datashade, dynspread from bokeh.models import HoverTool hv.extension('bokeh') points = np.asarray(roesslerAttractor(1000000)) ds_points = dynspread(rasterize(hv.Points(points, kdims=['x', 'y']))) ds_points.opts( width=800, height=800, tools=['hover'], active_tools=['wheel_zoom'], cmap='greys', ) ```

https://github.com/user-attachments/assets/4b1dc797-93f4-4945-b1dc-cbdce24bcfa8