Highres quiver plot returning memory error

The best option for me would be the average instead of the subset, but I think that even the subset would be cool.
If I understand correctly, the idea would be to do something like this?

def filter_points(points, x_range, y_range, nmax = 1000):
    if x_range is None or y_range is None:
        return points
    selected_points = points[x_range, y_range]
    xs, ys = selected_points.shape
    ix, iy = nmax // xs, nmax // ys
    return selected_points[::ix, ::iy]

nmax is the number max of points in each dimension.

1 Like

Yes I believe so.

I am not sure I understand every step in the code as the reference has fragmented code snippets and I am not very familiar with holoviz packages. So, beforehand, sorry if there is something weird here. The code should work as it is in terms of imports and variable definition, but this is returning error. Any suggestion?

import numpy as np
import xarray as xr
import hvplot.xarray #noqa
import holoviews as hv
from holoviews.operation.datashader import datashade

#def sample_data(shape=(904, 999)):
def sample_data(shape=(90, 90)):
    x = np.linspace(311.9, 391.1, shape[1])
    y = np.linspace(-23.6, 24.8, shape[0])

    x2d, y2d = np.meshgrid(x, y)
    u = 10 * (2 * np.cos(2 * np.deg2rad(x2d) + 3 * np.deg2rad(y2d + 30)) ** 2)
    v = 20 * np.cos(6 * np.deg2rad(x2d))

    return x, y, u, v

xs, ys, U, V = sample_data()

mag = np.sqrt(U**2 + V**2)
angle = (np.pi/2.) - np.arctan2(U/mag, V/mag)

ds = xr.Dataset({'mag': xr.DataArray(mag, dims=('y', 'x'), coords={'y': ys, 'x': xs}),
                 'angle': xr.DataArray(angle, dims=('y', 'x'), coords={'y': ys, 'x': xs})})


quiver = ds.hvplot.vectorfield(x = "x", y = "y", mag = "mag", angle = "angle", hover = False).opts(magnitude = "mag")

streams = [hv.streams.RangeXY(source = quiver)]

def filter_points(points, x_range, y_range, nmax = 10):
    if x_range is None or y_range is None:
        return points
    selected_points = points[x_range, y_range]
    xs, ys = selected_points.shape
    ix, iy = nmax // xs, nmax // ys
    return selected_points[::ix, ::iy]
    
filtered = quiver.apply(filter_points, streams = streams)
datashade(filtered, width = 400, height = 400, streams = streams)

returns

WARNING:param.dynamic_operation: Callable raised "AttributeError("'VectorField' object has no attribute 'xdensity'")".
Invoked as dynamic_operation(x_range=None, y_range=None)
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
File ~/miniforge3/envs/coringa/lib/python3.9/site-packages/IPython/core/formatters.py:972, in MimeBundleFormatter.__call__(self, obj, include, exclude)
    969     method = get_real_method(obj, self.print_method)
    971     if method is not None:
--> 972         return method(include=include, exclude=exclude)
    973     return None
    974 else:

File ~/miniforge3/envs/coringa/lib/python3.9/site-packages/holoviews/core/dimension.py:1286, in Dimensioned._repr_mimebundle_(self, include, exclude)
   1279 def _repr_mimebundle_(self, include=None, exclude=None):
   1280     """
   1281     Resolves the class hierarchy for the class rendering the
   1282     object using any display hooks registered on Store.display
   1283     hooks.  The output of all registered display_hooks is then
   1284     combined and returned.
   1285     """
-> 1286     return Store.render(self)

File ~/miniforge3/envs/coringa/lib/python3.9/site-packages/holoviews/core/options.py:1428, in Store.render(cls, obj)
   1426 data, metadata = {}, {}
   1427 for hook in hooks:
-> 1428     ret = hook(obj)
   1429     if ret is None:
   1430         continue

File ~/miniforge3/envs/coringa/lib/python3.9/site-packages/holoviews/ipython/display_hooks.py:287, in pprint_display(obj)
    285 if not ip.display_formatter.formatters['text/plain'].pprint:
    286     return None
--> 287 return display(obj, raw_output=True)

File ~/miniforge3/envs/coringa/lib/python3.9/site-packages/holoviews/ipython/display_hooks.py:261, in display(obj, raw_output, **kwargs)
    259 elif isinstance(obj, (HoloMap, DynamicMap)):
    260     with option_state(obj):
--> 261         output = map_display(obj)
    262 elif isinstance(obj, Plot):
    263     output = render(obj)

File ~/miniforge3/envs/coringa/lib/python3.9/site-packages/holoviews/ipython/display_hooks.py:149, in display_hook.<locals>.wrapped(element)
    147 try:
    148     max_frames = OutputSettings.options['max_frames']
--> 149     mimebundle = fn(element, max_frames=max_frames)
    150     if mimebundle is None:
    151         return {}, {}

File ~/miniforge3/envs/coringa/lib/python3.9/site-packages/holoviews/ipython/display_hooks.py:209, in map_display(vmap, max_frames)
    206     max_frame_warning(max_frames)
    207     return None
--> 209 return render(vmap)

File ~/miniforge3/envs/coringa/lib/python3.9/site-packages/holoviews/ipython/display_hooks.py:76, in render(obj, **kwargs)
     73 if renderer.fig == 'pdf':
     74     renderer = renderer.instance(fig='png')
---> 76 return renderer.components(obj, **kwargs)

File ~/miniforge3/envs/coringa/lib/python3.9/site-packages/holoviews/plotting/renderer.py:396, in Renderer.components(self, obj, fmt, comm, **kwargs)
    393 embed = (not (dynamic or streams or self.widget_mode == 'live') or config.embed)
    395 if embed or config.comms == 'default':
--> 396     return self._render_panel(plot, embed, comm)
    397 return self._render_ipywidget(plot)

File ~/miniforge3/envs/coringa/lib/python3.9/site-packages/holoviews/plotting/renderer.py:403, in Renderer._render_panel(self, plot, embed, comm)
    401 doc = Document()
    402 with config.set(embed=embed):
--> 403     model = plot.layout._render_model(doc, comm)
    404 if embed:
    405     return render_model(model, comm)

File ~/miniforge3/envs/coringa/lib/python3.9/site-packages/panel/viewable.py:737, in Viewable._render_model(self, doc, comm)
    735 if comm is None:
    736     comm = state._comm_manager.get_server_comm()
--> 737 model = self.get_root(doc, comm)
    739 if self._design and self._design.theme.bokeh_theme:
    740     doc.theme = self._design.theme.bokeh_theme

File ~/miniforge3/envs/coringa/lib/python3.9/site-packages/panel/layout/base.py:306, in Panel.get_root(self, doc, comm, preprocess)
    302 def get_root(
    303     self, doc: Optional[Document] = None, comm: Optional[Comm] = None,
    304     preprocess: bool = True
    305 ) -> Model:
--> 306     root = super().get_root(doc, comm, preprocess)
    307     # ALERT: Find a better way to handle this
    308     if hasattr(root, 'styles') and 'overflow-x' in root.styles:

File ~/miniforge3/envs/coringa/lib/python3.9/site-packages/panel/viewable.py:659, in Renderable.get_root(self, doc, comm, preprocess)
    657 wrapper = self._design._wrapper(self)
    658 if wrapper is self:
--> 659     root = self._get_model(doc, comm=comm)
    660     if preprocess:
    661         self._preprocess(root)

File ~/miniforge3/envs/coringa/lib/python3.9/site-packages/panel/layout/base.py:174, in Panel._get_model(self, doc, root, parent, comm)
    172 root = root or model
    173 self._models[root.ref['id']] = (model, parent)
--> 174 objects, _ = self._get_objects(model, [], doc, root, comm)
    175 props = self._get_properties(doc)
    176 props[self._property_mapping['objects']] = objects

File ~/miniforge3/envs/coringa/lib/python3.9/site-packages/panel/layout/base.py:156, in Panel._get_objects(self, model, old_objects, doc, root, comm)
    154 else:
    155     try:
--> 156         child = pane._get_model(doc, root, model, comm)
    157     except RerenderError as e:
    158         if e.layout is not None and e.layout is not self:

File ~/miniforge3/envs/coringa/lib/python3.9/site-packages/panel/pane/holoviews.py:411, in HoloViews._get_model(self, doc, root, parent, comm)
    409     plot = self.object
    410 else:
--> 411     plot = self._render(doc, comm, root)
    413 plot.pane = self
    414 backend = plot.renderer.backend

File ~/miniforge3/envs/coringa/lib/python3.9/site-packages/panel/pane/holoviews.py:506, in HoloViews._render(self, doc, comm, root)
    503     if comm:
    504         kwargs['comm'] = comm
--> 506 return renderer.get_plot(self.object, **kwargs)

File ~/miniforge3/envs/coringa/lib/python3.9/site-packages/holoviews/plotting/bokeh/renderer.py:68, in BokehRenderer.get_plot(self_or_cls, obj, doc, renderer, **kwargs)
     61 @bothmethod
     62 def get_plot(self_or_cls, obj, doc=None, renderer=None, **kwargs):
     63     """
     64     Given a HoloViews Viewable return a corresponding plot instance.
     65     Allows supplying a document attach the plot to, useful when
     66     combining the bokeh model with another plot.
     67     """
---> 68     plot = super().get_plot(obj, doc, renderer, **kwargs)
     69     if plot.document is None:
     70         plot.document = Document() if self_or_cls.notebook_context else curdoc()

File ~/miniforge3/envs/coringa/lib/python3.9/site-packages/holoviews/plotting/renderer.py:217, in Renderer.get_plot(self_or_cls, obj, doc, renderer, comm, **kwargs)
    214     raise SkipRendering(msg.format(dims=dims))
    216 # Initialize DynamicMaps with first data item
--> 217 initialize_dynamic(obj)
    219 if not renderer:
    220     renderer = self_or_cls

File ~/miniforge3/envs/coringa/lib/python3.9/site-packages/holoviews/plotting/util.py:270, in initialize_dynamic(obj)
    268     continue
    269 if not len(dmap):
--> 270     dmap[dmap._initial_key()]

File ~/miniforge3/envs/coringa/lib/python3.9/site-packages/holoviews/core/spaces.py:1216, in DynamicMap.__getitem__(self, key)
   1214 # Not a cross product and nothing cached so compute element.
   1215 if cache is not None: return cache
-> 1216 val = self._execute_callback(*tuple_key)
   1217 if data_slice:
   1218     val = self._dataslice(val, data_slice)

File ~/miniforge3/envs/coringa/lib/python3.9/site-packages/holoviews/core/spaces.py:983, in DynamicMap._execute_callback(self, *args)
    980     kwargs['_memoization_hash_'] = hash_items
    982 with dynamicmap_memoization(self.callback, self.streams):
--> 983     retval = self.callback(*args, **kwargs)
    984 return self._style(retval)

File ~/miniforge3/envs/coringa/lib/python3.9/site-packages/holoviews/core/spaces.py:581, in Callable.__call__(self, *args, **kwargs)
    578     args, kwargs = (), dict(pos_kwargs, **kwargs)
    580 try:
--> 581     ret = self.callable(*args, **kwargs)
    582 except KeyError:
    583     # KeyError is caught separately because it is used to signal
    584     # invalid keys on DynamicMap and should not warn
    585     raise

File ~/miniforge3/envs/coringa/lib/python3.9/site-packages/holoviews/util/__init__.py:1034, in Dynamic._dynamic_operation.<locals>.dynamic_operation(*key, **kwargs)
   1032 def dynamic_operation(*key, **kwargs):
   1033     key, obj = resolve(key, kwargs)
-> 1034     return apply(obj, *key, **kwargs)

File ~/miniforge3/envs/coringa/lib/python3.9/site-packages/holoviews/util/__init__.py:1026, in Dynamic._dynamic_operation.<locals>.apply(element, *key, **kwargs)
   1024 def apply(element, *key, **kwargs):
   1025     kwargs = dict(util.resolve_dependent_kwargs(self.p.kwargs), **kwargs)
-> 1026     processed = self._process(element, key, kwargs)
   1027     if (self.p.link_dataset and isinstance(element, Dataset) and
   1028         isinstance(processed, Dataset) and processed._dataset is None):
   1029         processed._dataset = element.dataset

File ~/miniforge3/envs/coringa/lib/python3.9/site-packages/holoviews/util/__init__.py:1008, in Dynamic._process(self, element, key, kwargs)
   1006 elif isinstance(self.p.operation, Operation):
   1007     kwargs = {k: v for k, v in kwargs.items() if k in self.p.operation.param}
-> 1008     return self.p.operation.process_element(element, key, **kwargs)
   1009 else:
   1010     return self.p.operation(element, **kwargs)

File ~/miniforge3/envs/coringa/lib/python3.9/site-packages/holoviews/core/operation.py:194, in Operation.process_element(self, element, key, **params)
    191 else:
    192     self.p = param.ParamOverrides(self, params,
    193                                   allow_extra_keywords=self._allow_extra_keywords)
--> 194 return self._apply(element, key)

File ~/miniforge3/envs/coringa/lib/python3.9/site-packages/holoviews/core/operation.py:141, in Operation._apply(self, element, key)
    139     if not in_method:
    140         element._in_method = True
--> 141 ret = self._process(element, key)
    142 if hasattr(element, '_in_method') and not in_method:
    143     element._in_method = in_method

File ~/miniforge3/envs/coringa/lib/python3.9/site-packages/holoviews/operation/datashader.py:1526, in datashade._process(self, element, key)
   1524 def _process(self, element, key=None):
   1525     agg = rasterize._process(self, element, key)
-> 1526     shaded = shade._process(self, agg, key)
   1527     return shaded

File ~/miniforge3/envs/coringa/lib/python3.9/site-packages/holoviews/operation/datashader.py:1268, in shade._process(self, element, key)
   1266     return element.map(partial(shade._process, self), [Element])
   1267 else:
-> 1268     xdensity = element.xdensity
   1269     ydensity = element.ydensity
   1270     bounds = element.bounds

AttributeError: 'VectorField' object has no attribute 'xdensity'

Nice! You’re 99.9% there. The issue is I don’t think vectorfield can be datashaded (see Datashade support for VectorField · Issue #5690 · holoviz/holoviews (github.com)) I suppose the error message could be improved so if you can, please submit a GitHub issue for that.

Anyways, all you have to do is drop the datashade line, and return filtered.

import numpy as np
import xarray as xr
import hvplot.xarray #noqa
import holoviews as hv

#def sample_data(shape=(904, 999)):
def sample_data(shape=(90, 90)):
    x = np.linspace(311.9, 391.1, shape[1])
    y = np.linspace(-23.6, 24.8, shape[0])

    x2d, y2d = np.meshgrid(x, y)
    u = 10 * (2 * np.cos(2 * np.deg2rad(x2d) + 3 * np.deg2rad(y2d + 30)) ** 2)
    v = 20 * np.cos(6 * np.deg2rad(x2d))

    return x, y, u, v

xs, ys, U, V = sample_data()

mag = np.sqrt(U**2 + V**2)
angle = (np.pi/2.) - np.arctan2(U/mag, V/mag)

ds = xr.Dataset({'mag': xr.DataArray(mag, dims=('y', 'x'), coords={'y': ys, 'x': xs}),
                 'angle': xr.DataArray(angle, dims=('y', 'x'), coords={'y': ys, 'x': xs})})


quiver = ds.hvplot.vectorfield(x = "x", y = "y", mag = "mag", angle = "angle", hover = False).opts(magnitude = "mag")

streams = [hv.streams.RangeXY(source = quiver)]

def filter_points(points, x_range, y_range, nmax = 10):
    if x_range is None or y_range is None:
        return points
    selected_points = points[x_range, y_range]
    xs, ys = selected_points.shape
    ix, iy = nmax // xs, nmax // ys
    return selected_points[::ix, ::iy]
    
filtered = quiver.apply(filter_points, streams = streams)
filtered

You should see something like: which is pretty cool honestly.

ezgif.com-video-to-gif (2)

One other thing you can do is tweak the number of quivers and also the quiver’s size on zoom in/out.

That is awesome! I can make a PR for the better error message. I belive that would mean to create a new error message before even reaching the part that check if xdensity is in the attributes list. Any suggestion?

@ahuang11

I just noticed that this doesn’t seem like it is working.
It was supposed to keep at max 10 x 10 arrows in the screen.
Any suggestion?

I added some print statements and fixed a problem with the proportion. It should be xs // nmax, not nmax // xs I think. I also added an if statement to convert to 1 if ix or iy are zero.

But I don’t understand these prints.

import numpy as np
import xarray as xr
import hvplot.xarray #noqa
import holoviews as hv

#def sample_data(shape=(904, 999)):
def sample_data(shape=(90, 90)):
    x = np.linspace(311.9, 391.1, shape[1])
    y = np.linspace(-23.6, 24.8, shape[0])

    x2d, y2d = np.meshgrid(x, y)
    u = 10 * (2 * np.cos(2 * np.deg2rad(x2d) + 3 * np.deg2rad(y2d + 30)) ** 2)
    v = 20 * np.cos(6 * np.deg2rad(x2d))

    return x, y, u, v

xs, ys, U, V = sample_data()

mag = np.sqrt(U**2 + V**2)
angle = (np.pi/2.) - np.arctan2(U/mag, V/mag)

ds = xr.Dataset({'mag': xr.DataArray(mag, dims=('y', 'x'), coords={'y': ys, 'x': xs}),
                 'angle': xr.DataArray(angle, dims=('y', 'x'), coords={'y': ys, 'x': xs})})


quiver = ds.hvplot.vectorfield(x = "x", y = "y", mag = "mag", angle = "angle", hover = False).opts(magnitude = "mag")

streams = [hv.streams.RangeXY(source = quiver)]

def filter_points(points, x_range, y_range, nmax = 10):
    if x_range is None or y_range is None:
        return points
    print(points.shape)
    print((x_range, y_range))
    selected_points = points[x_range, y_range]
    xs, ys = selected_points.shape
    print((xs, ys))
    ix, iy = xs // nmax, ys // nmax
    print((ix, iy))
    ix = 1 if ix <= 0 else ix
    iy = 1 if iy <= 0 else iy
    filtered_points = selected_points[::ix, ::iy]
    print(filtered_points.shape)
    return filtered_points
    
filtered = quiver.apply(filter_points, streams = streams)
filtered

It prints only once, no matter the zoom.

(8100, 4)
((310.2028571428571, 392.7971428571429), (-26.020000000000003, 27.220000000000002))
(8100, 4)
(810, 0)
(8100, 4)

I understand that it looks like it is getting the x and y range correctly, but first of all, these are absolute values, not indices, thus, it does not make sense to do points[x_range, y_range]. Btw, this command should give an error and it is not, for some reason. Also, the shape of points is actually the total number of points versus 4, which I don’t understand. Maybe it is actually something like [x, y, mag, angle]?

Anyway, it looks like this command is not being executed filtered_points = selected_points[::ix, ::iy]. Any reason why?

I tried a different solution for now as I realized points is the same object as quiver, thus, containing the .data attribute as a pandas.dataframe (btw, why? if we used xarray).

image

Therefore, I am trying to do a simple thing for now. Once this is working it will be easy to use groupby to resample and aggregate by mean as the original idea. For now, I first tried to basically do

quiver.data = quiver.data[::10]
quiver

and it returns somewhat what I would expect to, as converting to a dataframe looses the two-dimensionality, I am just resampling by one of the coordinates.

Then I simply tried to implement that on filter_points():

def filter_points(points, x_range, y_range, nmax = 10):
    if x_range is None or y_range is None:
        return points
    
    data = points.data
    print(data.shape)
    
    inside = (
        (data.x > x_range[0]) & (data.x < x_range[1]) &
        (data.y > y_range[0]) & (data.y < y_range[1])
    )
    
    data = data[inside]

    ip = data.shape[0] // nmax
    ip = 1 if ip <= 0 else ip
    
    data = data[::ip].reset_index(drop = True)

    print(data.shape)
    
    points.data = data

    print(points.data.shape)
    
    return points

which prints the correct new shape for the data and affected points, but doesn’t change the plot.

Any thoughts?

Full reproducible code:

import numpy as np
import xarray as xr
import hvplot.xarray #noqa
import holoviews as hv

#def sample_data(shape=(904, 999)):
def sample_data(shape=(90, 90)):
    x = np.linspace(311.9, 391.1, shape[1])
    y = np.linspace(-23.6, 24.8, shape[0])

    x2d, y2d = np.meshgrid(x, y)
    u = 10 * (2 * np.cos(2 * np.deg2rad(x2d) + 3 * np.deg2rad(y2d + 30)) ** 2)
    v = 20 * np.cos(6 * np.deg2rad(x2d))

    return x, y, u, v

xs, ys, U, V = sample_data()

mag = np.sqrt(U**2 + V**2)
angle = (np.pi/2.) - np.arctan2(U/mag, V/mag)

ds = xr.Dataset({'mag': xr.DataArray(mag, dims=('y', 'x'), coords={'y': ys, 'x': xs}),
                 'angle': xr.DataArray(angle, dims=('y', 'x'), coords={'y': ys, 'x': xs})})


quiver = ds.hvplot.vectorfield(x = "x", y = "y", mag = "mag", angle = "angle", hover = False).opts(magnitude = "mag")

streams = [hv.streams.RangeXY(source = quiver)]

def filter_points(points, x_range, y_range, nmax = 10):
    if x_range is None or y_range is None:
        return points
    
    data = points.data
    print(data.shape)
    
    inside = (
        (data.x > x_range[0]) & (data.x < x_range[1]) &
        (data.y > y_range[0]) & (data.y < y_range[1])
    )
    
    data = data[inside]

    ip = data.shape[0] // nmax
    ip = 1 if ip <= 0 else ip
    
    data = data[::ip].reset_index(drop = True)

    print(data.shape)
    
    points.data = data

    print(points.data.shape)
    
    return points


filtered = quiver.apply(filter_points, streams = streams)
filtered

Similar to

    selected_points = points[x_range, y_range]
    xs, ys = selected_points.shape
    ix, iy = nmax // xs, nmax // ys
    return selected_points[::ix, ::iy]

Thanks for the clarification. Can you explain what you mean by modifying points directly and how I could do that?

Maybe this is something specific to VectorField or the way I am implementing it, but the points object that is passed to filter_points is not indexable like this. It is actually the same class as the quiver object (holoviews.element.geom.VectorField) which I couldn’t find a documentation about the indexing. But reading the code I could find out that they work as slicing, thus, points[x_range, y_range] should work, but could not find any way to skip indices, i.e., quiver[::10, ::10] return exactly the same plot. It would be cool to be able to change the plot by altering .data, because then I could use everything pandas provide, such as grouping and aggregation… I don’t know how to proceed. Should I report to holoviews?

Indexing should theoretically work; docs on that:
https://holoviews.org/user_guide/Indexing_and_Selecting_Data.html

1 Like

I am confused, because indexing doesn’t work as I would expect and it might be just a case of not being used to the package yet.

import numpy as np
import xarray as xr
import hvplot.xarray #noqa
import holoviews as hv

#def sample_data(shape=(904, 999)):
def sample_data(shape=(90, 90)):
    x = np.linspace(311.9, 391.1, shape[1])
    y = np.linspace(-23.6, 24.8, shape[0])

    x2d, y2d = np.meshgrid(x, y)
    u = 10 * (2 * np.cos(2 * np.deg2rad(x2d) + 3 * np.deg2rad(y2d + 30)) ** 2)
    v = 20 * np.cos(6 * np.deg2rad(x2d))

    return x, y, u, v

xs, ys, U, V = sample_data()

mag = np.sqrt(U**2 + V**2)
angle = (np.pi/2.) - np.arctan2(U/mag, V/mag)

ds = xr.Dataset({'mag': xr.DataArray(mag, dims=('y', 'x'), coords={'y': ys, 'x': xs}),
                 'angle': xr.DataArray(angle, dims=('y', 'x'), coords={'y': ys, 'x': xs})})

quiver = ds.hvplot.vectorfield(x = "x", y = "y", mag = "mag", angle = "angle", hover = False).opts(magnitude = "mag")
quiver

returns

if I do

quiver[(320,360), (-10,10)]

it returns

But all the options for skipping values doesn’t seem to work.

quiver[(320,360,2), (-10,10,2)]
# or
# quiver.select(x = (320,360,2), y = (-10,10,2))

return exactly the same object as quiver[(320,360), (-10,10)]. I tried skipping values as 2, 5, 10 etc …
The only way I could skip values was by .iloc, but it only works as if the data was onedimensional, i.e.

quiver.select(x = (320,360), y = (-10,10)).iloc[::5]

subsets, but not as I wanted.

and

quiver.select(x = (320,360), y = (-10,10)).iloc[::5,::5]

returns

---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[72], line 1
----> 1 quiver.select(x = (320,360), y = (-10,10)).iloc[::5,::5]

File ~/miniforge3/envs/coringa/lib/python3.9/site-packages/holoviews/core/data/interface.py:33, in Accessor.__getitem__(self, index)
     31     self.dataset._in_method = True
     32 try:
---> 33     res = self._perform_getitem(self.dataset, index)
     34     if not in_method and isinstance(res, Dataset):
     35         getitem_op = method.instance(
     36             input_type=type(self),
     37             output_type=type(self.dataset),
     38             method_name='_perform_getitem',
     39             args=[index],
     40         )

File ~/miniforge3/envs/coringa/lib/python3.9/site-packages/holoviews/core/data/interface.py:98, in iloc._perform_getitem(cls, dataset, index)
     95 datatype = [dt for dt in datatypes if dt in Interface.interfaces and
     96             not Interface.interfaces[dt].gridded]
     97 if not datatype: datatype = ['dataframe', 'dictionary']
---> 98 return dataset.clone(data, kdims=kdims, vdims=vdims, datatype=datatype)

File ~/miniforge3/envs/coringa/lib/python3.9/site-packages/holoviews/core/data/__init__.py:1203, in Dataset.clone(self, data, shared_data, new_type, link, *args, **overrides)
   1200 elif self._in_method and 'dataset' not in overrides:
   1201     overrides['dataset'] = self.dataset
-> 1203 return super().clone(data, shared_data, new_type, *args, **overrides)

File ~/miniforge3/envs/coringa/lib/python3.9/site-packages/holoviews/core/dimension.py:563, in LabelledData.clone(self, data, shared_data, new_type, link, *args, **overrides)
    561 # Apply name mangling for __ attribute
    562 pos_args = getattr(self, '_' + type(self).__name__ + '__pos_params', [])
--> 563 return clone_type(data, *args, **{k:v for k,v in settings.items()
    564                                   if k not in pos_args})

File ~/miniforge3/envs/coringa/lib/python3.9/site-packages/holoviews/element/selection.py:24, in SelectionIndexExpr.__init__(self, *args, **kwargs)
     23 def __init__(self, *args, **kwargs):
---> 24     super().__init__(*args, **kwargs)
     25     self._index_skip = False

File ~/miniforge3/envs/coringa/lib/python3.9/site-packages/holoviews/core/data/__init__.py:332, in Dataset.__init__(self, data, kdims, vdims, **kwargs)
    329 initialized = Interface.initialize(type(self), data, kdims, vdims,
    330                                    datatype=kwargs.get('datatype'))
    331 (data, self.interface, dims, extra_kws) = initialized
--> 332 super().__init__(data, **dict(kwargs, **dict(dims, **extra_kws)))
    333 self.interface.validate(self, validate_vdims)
    335 # Handle _pipeline property

File ~/miniforge3/envs/coringa/lib/python3.9/site-packages/holoviews/core/dimension.py:841, in Dimensioned.__init__(self, data, kdims, vdims, **params)
    838 if 'cdims' in params:
    839     params['cdims'] = {d if isinstance(d, Dimension) else Dimension(d): val
    840                        for d, val in params['cdims'].items()}
--> 841 super().__init__(data, **params)
    842 self.ndims = len(self.kdims)
    843 cdims = [(d.name, val) for d, val in self.cdims.items()]

File ~/miniforge3/envs/coringa/lib/python3.9/site-packages/holoviews/core/dimension.py:500, in LabelledData.__init__(self, data, id, plot_id, **params)
    497     util.group_sanitizer.add_aliases(**{alias:long_name})
    498     params['group'] = long_name
--> 500 super().__init__(**params)
    501 if not util.group_sanitizer.allowable(self.group):
    502     raise ValueError("Supplied group %r contains invalid characters." %
    503                      self.group)

File ~/miniforge3/envs/coringa/lib/python3.9/site-packages/param/parameterized.py:3173, in Parameterized.__init__(self, **params)
   3170 self._dynamic_watchers = defaultdict(list)
   3172 self.param._generate_name()
-> 3173 self.param._setup_params(**params)
   3174 object_count += 1
   3176 self.param._update_deps(init=True)

File ~/miniforge3/envs/coringa/lib/python3.9/site-packages/param/parameterized.py:1387, in as_uninitialized.<locals>.override_initialization(self_, *args, **kw)
   1385 original_initialized = parameterized_instance.initialized
   1386 parameterized_instance.initialized = False
-> 1387 fn(parameterized_instance, *args, **kw)
   1388 parameterized_instance.initialized = original_initialized

File ~/miniforge3/envs/coringa/lib/python3.9/site-packages/param/parameterized.py:1641, in Parameters._setup_params(self_, **params)
   1639     self.param.warning("Setting non-parameter attribute %s=%s using a mechanism intended only for parameters", name, val)
   1640 # i.e. if not desc it's setting an attribute in __dict__, not a Parameter
-> 1641 setattr(self, name, val)

File ~/miniforge3/envs/coringa/lib/python3.9/site-packages/param/parameterized.py:369, in instance_descriptor.<locals>._f(self, obj, val)
    367     instance_param.__set__(obj, val)
    368     return
--> 369 return f(self, obj, val)

File ~/miniforge3/envs/coringa/lib/python3.9/site-packages/param/parameterized.py:1201, in Parameter.__set__(self, obj, val)
   1198 if hasattr(self, 'set_hook'):
   1199     val = self.set_hook(obj,val)
-> 1201 self._validate(val)
   1203 _old = NotImplemented
   1204 # obj can be None if __set__ is called for a Parameterized class

File ~/miniforge3/envs/coringa/lib/python3.9/site-packages/param/__init__.py:1442, in List._validate(self, val)
   1437 """
   1438 Checks that the value is numeric and that it is within the hard
   1439 bounds; if not, an exception is raised.
   1440 """
   1441 self._validate_value(val, self.allow_None)
-> 1442 self._validate_bounds(val, self.bounds)
   1443 self._validate_item_type(val, self.item_type)

File ~/miniforge3/envs/coringa/lib/python3.9/site-packages/param/__init__.py:1453, in List._validate_bounds(self, val, bounds)
   1451 if min_length is not None and max_length is not None:
   1452     if not (min_length <= l <= max_length):
-> 1453         raise ValueError("%s: list length must be between %s and %s (inclusive)"%(self.name,min_length,max_length))
   1454 elif min_length is not None:
   1455     if not min_length <= l:

ValueError: kdims: list length must be between 2 and 2 (inclusive)

Update:

After discussing with a colleague, she gave me an idea, but it doesn’t seem like this is the best solution.

quiver.select(x = (320,360), y = (-10,10)).iloc[::5].iloc[::5]

Anyway, I tried to do the following and it didn’t affect the plot:

def filter_points(vectorfield, x_range, y_range):
    if x_range is None or y_range is None:
        return vectorfield
    
    return vectorfield[x_range, y_range].iloc[::10].iloc[::10]

Oops, I think you have the right idea; I think you need something like:

import numpy as np
import xarray as xr
import hvplot.xarray  # noqa
import holoviews as hv
import panel as pn


# def sample_data(shape=(904, 999)):
def sample_data(shape=(90, 90)):
    x = np.linspace(311.9, 391.1, shape[1])
    y = np.linspace(-23.6, 24.8, shape[0])

    x2d, y2d = np.meshgrid(x, y)
    u = 10 * (2 * np.cos(2 * np.deg2rad(x2d) + 3 * np.deg2rad(y2d + 30)) ** 2)
    v = 20 * np.cos(6 * np.deg2rad(x2d))

    return x, y, u, v


xs, ys, U, V = sample_data()

mag = np.sqrt(U**2 + V**2)
angle = (np.pi / 2.0) - np.arctan2(U / mag, V / mag)

ds = xr.Dataset(
    {
        "mag": xr.DataArray(mag, dims=("y", "x"), coords={"y": ys, "x": xs}),
        "angle": xr.DataArray(angle, dims=("y", "x"), coords={"y": ys, "x": xs}),
    }
)


def create_quiver(x_range, y_range, nmax=10):
    quiver = ds.hvplot.vectorfield(
        x="x",
        y="y",
        mag="mag",
        angle="angle",
        hover=False,
    ).opts(magnitude="mag")
    if x_range is None or y_range is None:
        return quiver
    # dump logic here
    return quiver


range_xy = hv.streams.RangeXY()
filtered = hv.DynamicMap(create_quiver, streams=[range_xy])
range_xy.source = filtered
filtered

So this should work, but doesn’t…

import numpy as np
import xarray as xr
import hvplot.xarray  # noqa
import holoviews as hv
import panel as pn


# def sample_data(shape=(904, 999)):
def sample_data(shape=(90, 90)):
    x = np.linspace(311.9, 391.1, shape[1])
    y = np.linspace(-23.6, 24.8, shape[0])

    x2d, y2d = np.meshgrid(x, y)
    u = 10 * (2 * np.cos(2 * np.deg2rad(x2d) + 3 * np.deg2rad(y2d + 30)) ** 2)
    v = 20 * np.cos(6 * np.deg2rad(x2d))

    return x, y, u, v


xs, ys, U, V = sample_data()

mag = np.sqrt(U**2 + V**2)
angle = (np.pi / 2.0) - np.arctan2(U / mag, V / mag)

ds = xr.Dataset(
    {
        "mag": xr.DataArray(mag, dims=("y", "x"), coords={"y": ys, "x": xs}),
        "angle": xr.DataArray(angle, dims=("y", "x"), coords={"y": ys, "x": xs}),
    }
)




def create_quiver(x_range, y_range, nmax = 10):
    quiver = ds.hvplot.vectorfield(
        x="x",
        y="y",
        mag="mag",
        angle="angle",
        hover=False,
    ).opts(magnitude="mag")
    
    if x_range is None or y_range is None:
        return quiver

    sub = ds.sel(x = slice(*x_range), y = slice(*y_range))

    xs, ys = sub.x.size, sub.y.size
    ix, iy = xs // nmax, ys // nmax
    
    ix = 1 if ix <= 0 else ix
    iy = 1 if iy <= 0 else iy
    
    return quiver.select(x = x_range, y = y_range).iloc[::ix].iloc[::iy]


range_xy = hv.streams.RangeXY()
filtered = hv.DynamicMap(create_quiver, streams=[range_xy])
range_xy.source = filtered
filtered

I feel I am doing something dumb here… I even got excited with the example you gave me and tried a little bit further, but also dont work.

def create_quiver(x_range, y_range, nmax = 10):
    if x_range is None or y_range is None:
        quiver = ds.hvplot.vectorfield(
            x="x",
            y="y",
            mag="mag",
            angle="angle",
            hover=False,
        ).opts(magnitude="mag")
        return quiver

    sub = ds.sel(x = slice(*x_range), y = slice(*y_range))

    xs, ys = sub.x.size, sub.y.size
    ix, iy = xs // nmax, ys // nmax

    ix = 1 if ix <= 0 else ix
    iy = 1 if iy <= 0 else iy

    sub = sub.coarsen(x = ix, y = iy, side = "center", boundary = "trim").mean()
    
    quiver = sub.hvplot.vectorfield(
        x="x",
        y="y",
        mag="mag",
        angle="angle",
        hover=False,
    ).opts(magnitude="mag")
    
    return quiver

Again, thanks for all help…

Can you clarify what’s not working?

Your first example seems to work fine at a glance:

import numpy as np
import xarray as xr
import hvplot.xarray  # noqa
import holoviews as hv
import panel as pn


# def sample_data(shape=(904, 999)):
def sample_data(shape=(90, 90)):
    x = np.linspace(311.9, 391.1, shape[1])
    y = np.linspace(-23.6, 24.8, shape[0])

    x2d, y2d = np.meshgrid(x, y)
    u = 10 * (2 * np.cos(2 * np.deg2rad(x2d) + 3 * np.deg2rad(y2d + 30)) ** 2)
    v = 20 * np.cos(6 * np.deg2rad(x2d))

    return x, y, u, v


xs, ys, U, V = sample_data()

mag = np.sqrt(U**2 + V**2)
angle = (np.pi / 2.0) - np.arctan2(U / mag, V / mag)

ds = xr.Dataset(
    {
        "mag": xr.DataArray(mag, dims=("y", "x"), coords={"y": ys, "x": xs}),
        "angle": xr.DataArray(angle, dims=("y", "x"), coords={"y": ys, "x": xs}),
    }
)




def create_quiver(x_range, y_range, nmax = 10):
    quiver = ds.hvplot.vectorfield(
        x="x",
        y="y",
        mag="mag",
        angle="angle",
        hover=False,
    ).opts(magnitude="mag")
    
    if x_range is None or y_range is None:
        return quiver

    sub = ds.sel(x = slice(*x_range), y = slice(*y_range))

    xs, ys = sub.x.size, sub.y.size
    ix, iy = xs // nmax, ys // nmax
    
    ix = 1 if ix <= 0 else ix
    iy = 1 if iy <= 0 else iy
    print(ix, iy, x_range, y_range)
    return quiver.select(x = x_range, y = y_range).iloc[::ix].iloc[::iy]


range_xy = hv.streams.RangeXY()
filtered = hv.DynamicMap(create_quiver, streams=[range_xy])
range_xy.source = filtered
filtered

This also works:

import numpy as np
import xarray as xr
import hvplot.xarray  # noqa
import holoviews as hv
import panel as pn


# def sample_data(shape=(904, 999)):
def sample_data(shape=(90, 90)):
    x = np.linspace(311.9, 391.1, shape[1])
    y = np.linspace(-23.6, 24.8, shape[0])

    x2d, y2d = np.meshgrid(x, y)
    u = 10 * (2 * np.cos(2 * np.deg2rad(x2d) + 3 * np.deg2rad(y2d + 30)) ** 2)
    v = 20 * np.cos(6 * np.deg2rad(x2d))

    return x, y, u, v


xs, ys, U, V = sample_data()

mag = np.sqrt(U**2 + V**2)
angle = (np.pi / 2.0) - np.arctan2(U / mag, V / mag)

ds = xr.Dataset(
    {
        "mag": xr.DataArray(mag, dims=("y", "x"), coords={"y": ys, "x": xs}),
        "angle": xr.DataArray(angle, dims=("y", "x"), coords={"y": ys, "x": xs}),
    }
)


def subset_quiver(quiver, x_range, y_range, nmax=10):
    if x_range is None or y_range is None:
        return quiver

    sub = ds.sel(x=slice(*x_range), y=slice(*y_range))

    xs, ys = sub.x.size, sub.y.size
    ix, iy = xs // nmax, ys // nmax

    ix = 1 if ix <= 0 else ix
    iy = 1 if iy <= 0 else iy
    return quiver[x_range, y_range].iloc[::ix].iloc[::iy]


quiver = ds.hvplot.vectorfield(
    x="x",
    y="y",
    mag="mag",
    angle="angle",
    hover=False,
    width=300,
).opts(magnitude="mag")
range_xy = hv.streams.RangeXY(source=quiver)
filtered = quiver.apply(subset_quiver, streams=[range_xy]).clone()
filtered.opts(title="filtered") + quiver.opts(title="original")

I am sorry, I thought that as I was not using panel controls it would be fine using JupyterLab Editor Console, but it turns out things in general doesn’t work well on it, as discussed here.

I ran the following code and it works fine and I consider this the solution of the original problem.
Thank you very much @ahuang11 and @jbednar . I plan to create an example to add to the gallery. Any suggestion on where to put it?

Code that works very sharp even though with 2000 x 2000 = 4,000,000 points:
To make it clear, the code works because it coarsen the data box averaging the data to keep always 10 x 10 = 100 arrows in the view. Of course, if there is nothing to average, it will just show all arrows. This is a very nice combination of xarray and hvplot.

import numpy as np
import xarray as xr
import hvplot.xarray  # noqa
import holoviews as hv
import panel as pn


def sample_data(shape=(2000, 2000)):
    x = np.linspace(311.9, 391.1, shape[1])
    y = np.linspace(-23.6, 24.8, shape[0])

    x2d, y2d = np.meshgrid(x, y)
    u = 10 * (2 * np.cos(2 * np.deg2rad(x2d) + 3 * np.deg2rad(y2d + 30)) ** 2)
    v = 20 * np.cos(6 * np.deg2rad(x2d))

    return x, y, u, v


xs, ys, U, V = sample_data()

mag = np.sqrt(U**2 + V**2)
angle = (np.pi / 2.0) - np.arctan2(U / mag, V / mag)

ds = xr.Dataset(
    {
        "mag": xr.DataArray(mag, dims=("y", "x"), coords={"y": ys, "x": xs}),
        "angle": xr.DataArray(angle, dims=("y", "x"), coords={"y": ys, "x": xs}),
    }
)


def create_quiver(x_range, y_range, nmax = 10):
    if x_range is None or y_range is None:
        xs, ys = ds.x.size, ds.y.size
        ix, iy = xs // nmax, ys // nmax
    
        ix = 1 if ix <= 0 else ix
        iy = 1 if iy <= 0 else iy
    
        sub = ds.coarsen(x = ix, y = iy, side = "center", boundary = "trim").mean()
        quiver = sub.hvplot.vectorfield(
            x="x",
            y="y",
            mag="mag",
            angle="angle",
            hover=False,
        ).opts(magnitude="mag")
        return quiver

    sub = ds.sel(x = slice(*x_range), y = slice(*y_range))

    xs, ys = sub.x.size, sub.y.size
    ix, iy = xs // nmax, ys // nmax

    ix = 1 if ix <= 0 else ix
    iy = 1 if iy <= 0 else iy

    sub = sub.coarsen(x = ix, y = iy, side = "center", boundary = "trim").mean()
    
    quiver = sub.hvplot.vectorfield(
        x="x",
        y="y",
        mag="mag",
        angle="angle",
        hover=False,
    ).opts(magnitude="mag")
    
    return quiver


range_xy = hv.streams.RangeXY()
filtered = hv.DynamicMap(create_quiver, streams=[range_xy])
range_xy.source = filtered
filtered

Generates this very cool result!

ezgif.com-video-to-gif (4)

2 Likes

The immediate place is the Discourse Showcase here. Other than that, perhaps here too?
https://holoviews.org/gallery/index.html

Also, if you use social media, we’d love to repost it!

1 Like