From 00b5579b20e7b151a3eec3ab13989e1ac2fdb963 Mon Sep 17 00:00:00 2001 From: Raphael Date: Thu, 5 Oct 2023 13:33:20 +0200 Subject: [PATCH 01/16] add "mode" as possible aggregation method --- eomaps/_data_manager.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/eomaps/_data_manager.py b/eomaps/_data_manager.py index 3eaaf7de3..42eacc1eb 100644 --- a/eomaps/_data_manager.py +++ b/eomaps/_data_manager.py @@ -840,6 +840,11 @@ def _zoom_block(self, maxsize, method, valid_fraction, blocksize): self._current_data["z_data"] = blocks.sum(axis=(-1, -2)) elif method == "median": self._current_data["z_data"] = np.median(blocks, axis=(-1, -2)) + elif method == "mode": + from scipy.stats import mode + + out, counts = mode(blocks, axis=(-1, -2), nan_policy="propagate") + self._current_data["z_data"] = out elif method == "fast_sum": self._current_data["z_data"] = self._fast_block_metric(blocks, bs, False) elif method == "fast_mean": From c22fc56d2f0fc8338445398913db45e13fbe53bf Mon Sep 17 00:00:00 2001 From: Raphael Date: Thu, 5 Oct 2023 15:17:57 +0200 Subject: [PATCH 02/16] fix issues with misplaced grid-labels on figure export fix dataset for some shapes not properly exported with "agg" backend --- eomaps/eomaps.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/eomaps/eomaps.py b/eomaps/eomaps.py index b299cd6f4..a8c75d77b 100644 --- a/eomaps/eomaps.py +++ b/eomaps/eomaps.py @@ -3484,6 +3484,12 @@ def text(self, *args, layer=None, **kwargs): @wraps(plt.savefig) def savefig(self, *args, refetch_wms=False, rasterize_data=True, **kwargs): """Save the figure.""" + if plt.get_backend() == "agg": + # make sure that a draw-event was triggered when using the agg backend + # (to avoid export-issues with some shapes) + # TODO properly assess why this is necessary! + self.f.canvas.draw_idle() + with ExitStack() as stack: if refetch_wms is False: if _cx_refetch_wms_on_size_change is not None: @@ -3602,6 +3608,9 @@ def savefig(self, *args, refetch_wms=False, rasterize_data=True, **kwargs): # trigger a redraw of all savelayers to make sure unmanaged artists # and ordinary matplotlib axes are properly drawn self.redraw(*savelayers) + # flush events prior to savefig to avoi dissues with pending draw events + # that cause wrong positioning of grid-labels and missing artists! + self.f.canvas.flush_events() self.f.savefig(*args, **kwargs) if redraw is True: From 87a656013391ec860e558e3c94cd13575c32ac59 Mon Sep 17 00:00:00 2001 From: Raphael Date: Fri, 6 Oct 2023 12:10:05 +0200 Subject: [PATCH 03/16] catch exceptions on default keypress callbacks to avoid crashing (e.g. when using backends that cannot initialize the widget) --- eomaps/eomaps.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/eomaps/eomaps.py b/eomaps/eomaps.py index a8c75d77b..9677cc589 100644 --- a/eomaps/eomaps.py +++ b/eomaps/eomaps.py @@ -4018,9 +4018,23 @@ def _save_to_clipboard(self, **kwargs): def _on_keypress(self, event): # NOTE: callback is only attached to the parent Maps object! if event.key == self._companion_widget_key: - self._open_companion_widget((event.x, event.y)) + try: + self._open_companion_widget((event.x, event.y)) + except Exception: + _log.exception( + "EOmaps: Encountered a problem while trying to open " + "the companion widget", + exec_info=_log.getEffectiveLevel() <= logging.DEBUG, + ) elif event.key == "ctrl+c": - self._save_to_clipboard(**Maps._clipboard_kwargs) + try: + self._save_to_clipboard(**Maps._clipboard_kwargs) + except Exception: + _log.exception( + "EOmaps: Encountered a problem while trying to export the figure " + "to the clipboard.", + exec_info=_log.getEffectiveLevel() <= logging.DEBUG, + ) def _init_figure(self, ax=None, plot_crs=None, **kwargs): if self.parent.f is None: From 4459b0a41f1ca532f7dd2c8f719e14ef8c15aed4 Mon Sep 17 00:00:00 2001 From: Raphael Date: Fri, 6 Oct 2023 13:51:19 +0200 Subject: [PATCH 04/16] make sure annotations use a black fontcolor by default (to avoid issues until proper treatment of rcparams is introduced) --- eomaps/callbacks.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/eomaps/callbacks.py b/eomaps/callbacks.py index 0eec89ca1..17ad957aa 100644 --- a/eomaps/callbacks.py +++ b/eomaps/callbacks.py @@ -445,6 +445,9 @@ def annotate( ) styledict.update(**kwargs) + # use a black font-color by default to avoid issues if rcparams are + # set differently + styledict.setdefault("color", "k") annotation = self.m.ax.annotate("", xy=picked_pos, **styledict) annotation.set_zorder(zorder) From 344bebd281c7b0b14ac3e99e7b0b936b0a68ba6e Mon Sep 17 00:00:00 2001 From: Raphael Date: Fri, 6 Oct 2023 13:52:42 +0200 Subject: [PATCH 05/16] remove duplicate restore_region call in _get_background --- eomaps/helpers.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/eomaps/helpers.py b/eomaps/helpers.py index ebefe40d2..47a7a5a12 100644 --- a/eomaps/helpers.py +++ b/eomaps/helpers.py @@ -2043,8 +2043,6 @@ def _get_background(self, layer, bbox=None, cache=False): else: self.fetch_bg(layer, bbox=bbox) bg = self._bg_layers[layer] - - self.canvas.restore_region(current_bg) else: bg = self._bg_layers[layer] From 4e6ccf563ccf989a34929a75959448b452be4e7d Mon Sep 17 00:00:00 2001 From: Raphael Date: Fri, 6 Oct 2023 17:19:03 +0200 Subject: [PATCH 06/16] fix issues due to plotted datasets outside the current extent --- eomaps/_data_manager.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/eomaps/_data_manager.py b/eomaps/_data_manager.py index 42eacc1eb..1a3f0cde5 100644 --- a/eomaps/_data_manager.py +++ b/eomaps/_data_manager.py @@ -468,7 +468,7 @@ def on_fetch_bg(self, layer=None, bbox=None, check_redraw=True): return False props = self.get_props() - if props is None: + if props is None or props["x0"] is None or props["y0"] is None: # fail-fast in case the data is completely outside the extent return @@ -524,9 +524,10 @@ def on_fetch_bg(self, layer=None, bbox=None, check_redraw=True): self.m.cb.pick._set_artist(coll) except Exception as ex: - raise AssertionError( - f"EOmaps: Unable to plot the data for the layer '{layer}'!" - ) from ex + _log.exception( + f"EOmaps: Unable to plot the data for the layer '{layer}'!", + exc_info=_log.getEffectiveLevel() <= logging.DEBUG, + ) def data_in_extent(self, extent): # check if the data extent collides with the map extent @@ -773,7 +774,9 @@ def _zoom(self, blocksize): valid_fraction = getattr(self.m.shape, "_valid_fraction", 0) # only zoom if the shape provides a _maxsize attribute - if maxsize is None or self._current_data["z_data"].size < maxsize: + if self._current_data["z_data"] is None or maxsize is None: + return + elif self._current_data["z_data"].size < maxsize: return if method == "spline": From a599b96819918068146f5d5b6c99195349320024 Mon Sep 17 00:00:00 2001 From: Raphael Date: Fri, 6 Oct 2023 17:28:24 +0200 Subject: [PATCH 07/16] improved implementation for background fetching + caching for combined_bgs --- eomaps/helpers.py | 72 ++++++++++++++++------------------------------- 1 file changed, 25 insertions(+), 47 deletions(-) diff --git a/eomaps/helpers.py b/eomaps/helpers.py index 47a7a5a12..ed45987c2 100644 --- a/eomaps/helpers.py +++ b/eomaps/helpers.py @@ -903,8 +903,9 @@ def cb_pick(self, event): self.blit_artists() def fetch_current_background(self): - # make sure blank background has been fetched - self.m.BM._do_fetch_blank() + # clear the renderer to avoid drawing on existing backgrounds + renderer = self.m.BM.frcanvas.get_renderer() + renderer.clear() with ExitStack() as stack: for ax in self._ax_picked: @@ -914,7 +915,7 @@ def fetch_current_background(self): stack.enter_context(cb.ax_cb._cm_set(visible=False)) stack.enter_context(cb.ax_cb_plot._cm_set(visible=False)) - self.m.BM.blit_artists(self.axes, self.m.BM._blank_bg, False) + self.m.BM.blit_artists(self.axes, None, False) grid = getattr(self, "_snap_grid_artist", None) if grid is not None: @@ -1987,6 +1988,10 @@ def _get_layers_alphas(self, layer=None): alphas.append(1) return layers, alphas + # cache the last 10 combined backgrounds to avoid re-combining backgrounds + # on updates of interactive artists + # cache is automatically cleared on draw if any layer is tagged for re-fetch! + @lru_cache(10) def _combine_bgs(self, layer): layers, alphas = self._get_layers_alphas(layer) @@ -1996,17 +2001,15 @@ def _combine_bgs(self, layer): # execute actions on layer-changes # (to make sure all lazy WMS services are properly added) self._do_on_layer_change(layer=l, new=False) - self._do_fetch_bg(l) + self.fetch_bg(l) renderer = self._get_renderer() + # clear the renderer to avoid drawing on existing backgrounds + renderer.clear() if renderer: gc = renderer.new_gc() gc.set_clip_rectangle(self.canvas.figure.bbox) - # switch to a blank background layer before merging backgrounds - # TODO is there a beter way to avoid drawing on initial backgrounds? - self.canvas.restore_region(self._blank_bg) - x0, y0, w, h = self.figure.bbox.bounds for l, a in zip(layers, alphas): rgba = self._get_array(l, a=a) @@ -2019,9 +2022,7 @@ def _combine_bgs(self, layer): int(y0), rgba[int(y0) : int(y0 + h), int(x0) : int(x0 + w), :], ) - # cache the combined background - bg = self._m.f.canvas.copy_from_bbox(self._m.f.bbox) - # self._bg_layers[layer] = bg + bg = renderer.copy_from_bbox(self._m.f.bbox) gc.restore() return bg @@ -2036,8 +2037,6 @@ def _get_array(self, l, a=1): def _get_background(self, layer, bbox=None, cache=False): if layer not in self._bg_layers: - current_bg = self.canvas.copy_from_bbox(self.figure.bbox) - if "|" in layer: bg = self._combine_bgs(layer) else: @@ -2056,6 +2055,7 @@ def _get_background(self, layer, bbox=None, cache=False): def _do_fetch_bg(self, layer, bbox=None): cv = self.canvas renderer = self._get_renderer() + renderer.clear() if bbox is None: bbox = self.figure.bbox @@ -2104,28 +2104,12 @@ def _do_fetch_bg(self, layer, bbox=None): return if renderer: - self.canvas.restore_region(self._blank_bg) - if not self._m.parent._layout_editor._modifier_pressed: for art in allartists: if art not in self._hidden_artists: art.draw(renderer) art.stale = False - self._bg_layers[layer] = cv.copy_from_bbox(bbox) - - def _do_fetch_blank(self): - # fetch a blank background - if self._refetch_blank is False and self._blank_bg is not None: - # don't re-fetch the background if it is not necessary - return - try: - self._m.f.set_visible(False) - self.canvas._force_full = True - self.canvas.draw() - self._blank_bg = self.canvas.copy_from_bbox(self.figure.bbox) - self._refetch_blank = False - finally: - self._m.f.set_visible(True) + self._bg_layers[layer] = renderer.copy_from_bbox(bbox) def fetch_bg(self, layer=None, bbox=None): """ @@ -2149,20 +2133,14 @@ def fetch_bg(self, layer=None, bbox=None): if layer is None: layer = self.bg_layer - initial_layer = self.canvas.copy_from_bbox(self.figure.bbox) - if layer in self._bg_layers: # don't re-fetch existing layers # (layers get cleared automatically if re-draw is necessary) return with self._disconnect_draw(): - self._do_fetch_blank() self._do_fetch_bg(layer, bbox) - if initial_layer in self._bg_layers: - self.canvas.restore_region(initial_layer) - @contextmanager def _disconnect_draw(self): try: @@ -2180,6 +2158,7 @@ def on_draw(self, event): """Callback to register with 'draw_event'.""" cv = self.canvas _log.log(5, "draw") + try: if ( "RendererBase._draw_disabled" @@ -2208,8 +2187,9 @@ def on_draw(self, event): self._bg_layers.clear() self._layers_to_refetch.clear() self._refetch_bg = False - else: + type(self)._combine_bgs.cache_clear() # clear combined_bg cache + else: # in case there is a stale (unmanaged) artists and the # stale-artist layer is attempted to be drawn, re-draw the # cached background for the unmanaged-artists layer @@ -2217,15 +2197,12 @@ def on_draw(self, event): a.stale for a in self._get_unmanaged_artists() ): self._refetch_layer(self._unmanaged_artists_layer) + type(self)._combine_bgs.cache_clear() # clear combined_bg cache # remove all cached backgrounds that were tagged for refetch while len(self._layers_to_refetch) > 0: self._bg_layers.pop(self._layers_to_refetch.pop(), None) - - # # fetching relevant backgrounds is done in self.update()! - # show_layer = self._get_showlayer_name() - # if show_layer not in self._bg_layers: - # self.fetch_bg(layer=show_layer) + type(self)._combine_bgs.cache_clear() # clear combined_bg cache # workaround for nbagg backend to avoid glitches # it's slow but at least it works... @@ -2780,16 +2757,17 @@ def action(): if self.bg_layer == layer: return - # make sure the background is fetched - if layer not in self._bg_layers: - self.fetch_bg(layer) - # update to make sure spines etc. are properly displayed - self.update() + # TODO properly check if this is necessary... + # update to make sure spines etc. are properly displayed + # self.update() x0, y0, w, h = bbox.bounds + # make sure to restore the initial background + init_bg = renderer.copy_from_bbox(self._m.f.bbox) # convert the buffer to rgba so that we can add transparency buffer = self._get_background(layer, cache=True) + self.canvas.restore_region(init_bg) x = buffer.get_extents() ncols, nrows = x[2] - x[0], x[3] - x[1] From 63ac035964fdab1c80c56d02ff362905bd485be6 Mon Sep 17 00:00:00 2001 From: Raphael Date: Sat, 7 Oct 2023 12:25:50 +0200 Subject: [PATCH 08/16] fix typo --- eomaps/helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eomaps/helpers.py b/eomaps/helpers.py index ed45987c2..fb05d6064 100644 --- a/eomaps/helpers.py +++ b/eomaps/helpers.py @@ -904,7 +904,7 @@ def cb_pick(self, event): def fetch_current_background(self): # clear the renderer to avoid drawing on existing backgrounds - renderer = self.m.BM.frcanvas.get_renderer() + renderer = self.m.BM.canvas.get_renderer() renderer.clear() with ExitStack() as stack: From 934c666ca53ee008c64688e1cf748fe1db8ba0b9 Mon Sep 17 00:00:00 2001 From: Raphael Date: Sat, 7 Oct 2023 12:44:40 +0200 Subject: [PATCH 09/16] avoid triggering companion-widget keypress callbacks in webagg (otherwise pyqt issues might cause a crash) --- eomaps/eomaps.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/eomaps/eomaps.py b/eomaps/eomaps.py index 9677cc589..9d5925124 100644 --- a/eomaps/eomaps.py +++ b/eomaps/eomaps.py @@ -4016,6 +4016,9 @@ def _save_to_clipboard(self, **kwargs): cb.setImage(QImage.fromData(buffer.getvalue())) def _on_keypress(self, event): + if plt.get_backend().lower() == "webagg": + return + # NOTE: callback is only attached to the parent Maps object! if event.key == self._companion_widget_key: try: From 6ac45e9ed3f1063f365ba361df8ddbd60b5223cc Mon Sep 17 00:00:00 2001 From: Raphael Date: Sun, 8 Oct 2023 20:33:09 +0200 Subject: [PATCH 10/16] don't trigger updates in move callbacks by default (to avoid issues with slow updates in webagg backend) --- eomaps/cb_container.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/eomaps/cb_container.py b/eomaps/cb_container.py index 3650a9983..985fe05eb 100644 --- a/eomaps/cb_container.py +++ b/eomaps/cb_container.py @@ -1121,13 +1121,16 @@ class MoveContainer(ClickContainer): # this is just a copy of ClickContainer to manage motion-sensitive callbacks - def __init__(self, button_down=False, *args, **kwargs): + def __init__(self, button_down=False, update_on_trigger=True, *args, **kwargs): + super().__init__(*args, **kwargs) self._cid_motion_event = None self._button_down = button_down + self._update_on_trigger = update_on_trigger + def _init_cbs(self): if self._m.parent is self._m: self._add_move_callback() @@ -1148,7 +1151,7 @@ def movecb(event): try: self._event = event - # only execute movecb if a mouse-button is holded down + # only execute movecb if a mouse-button is held down # and only if the motion is happening inside the axes if self._button_down: if not event.button: # or (event.inaxes != self._m.ax): @@ -1192,8 +1195,8 @@ def movecb(event): obj._fwd_cb(event) # only update if a callback is attached - # (to avoid constantly calling update) - if update: + # (to avoid lag in webagg backed due to slow updates) + if self._update_on_trigger and update: if self._button_down: if event.button: self._m.parent.BM.update(clear=self._method) @@ -1963,6 +1966,7 @@ def __init__(self, m): method="_click_move", parent_container=self._click, button_down=True, + update_on_trigger=True, # automatically trigger updates for click+move! ) self._move = MoveContainer( @@ -1971,6 +1975,7 @@ def __init__(self, m): method="move", button_down=False, default_button=None, + update_on_trigger=False, # dont trigger updates for move! ) self._pick = PickContainer( From abf97326ddb0cc35e9296773881c6e06a2e098d1 Mon Sep 17 00:00:00 2001 From: Raphael Date: Sun, 8 Oct 2023 22:43:58 +0200 Subject: [PATCH 11/16] add update triggers for default move-callbacks --- eomaps/callbacks.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/eomaps/callbacks.py b/eomaps/callbacks.py index 17ad957aa..2e852e85f 100644 --- a/eomaps/callbacks.py +++ b/eomaps/callbacks.py @@ -1211,9 +1211,20 @@ class MoveCallbacks(_ClickCallbacks): "peek_layer", ] + def _decorate(self, f): + def inner(*args, **kwargs): + f(*args, **kwargs) + self.m.BM.update() + + return inner + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) + for cb in self._cb_list: + if cb not in ["print_to_console"]: + setattr(self, cb, self._decorate(getattr(super(), cb))) + class KeypressCallbacks: """Collection of callbacks that are executed if you press a key on the keyboard.""" From 308df460c84afc5397a4c3478b3a672fb833f50e Mon Sep 17 00:00:00 2001 From: Raphael Date: Sun, 8 Oct 2023 22:51:58 +0200 Subject: [PATCH 12/16] update version to v7.2.1 --- eomaps/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eomaps/_version.py b/eomaps/_version.py index 7707c08a8..065cf443c 100644 --- a/eomaps/_version.py +++ b/eomaps/_version.py @@ -1 +1 @@ -__version__ = "7.2" +__version__ = "7.2.1" From fdefd26c644cf37ff93e845d2e71abdf70daeba0 Mon Sep 17 00:00:00 2001 From: Raphael Date: Mon, 9 Oct 2023 10:34:44 +0200 Subject: [PATCH 13/16] allow overriding override callback behavior during toolbar actions --- eomaps/cb_container.py | 50 +++++++++++++++++++++++++++++------------- 1 file changed, 35 insertions(+), 15 deletions(-) diff --git a/eomaps/cb_container.py b/eomaps/cb_container.py index 985fe05eb..5a423e3aa 100644 --- a/eomaps/cb_container.py +++ b/eomaps/cb_container.py @@ -131,6 +131,7 @@ def __init__(self, m, cb_class=None, method="click", parent_container=None): self._event = None self._execute_on_all_layers = False + self._execute_while_toolbar_active = False def _getobj(self, m): """Get the equivalent callback container on another maps object.""" @@ -328,6 +329,35 @@ def set_execute_on_all_layers(self, q): ) self._execute_on_all_layers = q + def _check_toolbar_mode(self): + if self._execute_while_toolbar_active: + return False + + # returns True if a toolbar mode is active and False otherwise + if ( + self._m.f.canvas.toolbar is not None + ) and self._m.f.canvas.toolbar.mode != "": + return True + else: + return False + + def set_execute_during_toolbar_action(self, q): + """ + Set if callbacks should be executed during a toolbar action (e.g. pan/zoom). + + By default, callbacks are not executed during toolbar actions to make sure + pan/zoom is smooth. (e.g. to avoid things like constant re-fetching of webmaps + if a peek-layer callback is active during pan/zoom) + + Parameters + ---------- + q : bool + If True, callbacks will be triggered independent of the toolbar state. + if False, callbacks will only trigger if no toolbar action is active. + + """ + self._execute_while_toolbar_active = q + class _ClickContainer(_CallbackContainer): """ @@ -993,9 +1023,7 @@ def clickcb(event): self._event = event # don't execute callbacks if a toolbar-action is active - if ( - self._m.f.canvas.toolbar is not None - ) and self._m.f.canvas.toolbar.mode != "": + if self._check_toolbar_mode(): return # execute onclick on the maps object that belongs to the clicked axis @@ -1023,9 +1051,7 @@ def releasecb(event): self._event = event # don't execute callbacks if a toolbar-action is active - if ( - self._m.f.canvas.toolbar is not None - ) and self._m.f.canvas.toolbar.mode != "": + if self._check_toolbar_mode(): return # execute onclick on the maps object that belongs to the clicked axis @@ -1171,9 +1197,7 @@ def movecb(event): return # don't execute callbacks if a toolbar-action is active - if ( - self._m.f.canvas.toolbar is not None - ) and self._m.f.canvas.toolbar.mode != "": + if self._check_toolbar_mode(): return # execute onclick on the maps object that belongs to the clicked axis @@ -1473,9 +1497,7 @@ def _onpick(self, event): return # don't execute callbacks if a toolbar-action is active - if ( - self._m.f.canvas.toolbar is not None - ) and self._m.f.canvas.toolbar.mode != "": + if self._check_toolbar_mode(): return # make sure temporary artists are cleared before executing new callbacks @@ -1553,9 +1575,7 @@ def pickcb(event): return # don't execute callbacks if a toolbar-action is active - if ( - self._m.f.canvas.toolbar is not None - ) and self._m.f.canvas.toolbar.mode != "": + if self._check_toolbar_mode(): return if not self._artist_picked(event): From 449c035361afc016bf1af57a0640fa2ffbad67e4 Mon Sep 17 00:00:00 2001 From: Raphael Date: Mon, 9 Oct 2023 10:50:47 +0200 Subject: [PATCH 14/16] minor --- docs/api_callbacks.rst | 1 + eomaps/eomaps.py | 15 +++++---------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/docs/api_callbacks.rst b/docs/api_callbacks.rst index 34b3e7a86..ecaa16ecb 100644 --- a/docs/api_callbacks.rst +++ b/docs/api_callbacks.rst @@ -115,6 +115,7 @@ In addition, each callback-container supports the following useful methods: share_events forward_events add_temporary_artist + set_execute_during_toolbar_action Using callbacks with the companion-widget diff --git a/eomaps/eomaps.py b/eomaps/eomaps.py index 9d5925124..e391861b2 100644 --- a/eomaps/eomaps.py +++ b/eomaps/eomaps.py @@ -1958,29 +1958,24 @@ def add_gdf( - if "gpd": re-project geometries geopandas - if "cartopy": re-project geometries with cartopy (slower but more robust) + The default is "gpd". + >>> mg = MapsGrid(2, 1, crs=Maps.CRS.Stereographic()) >>> mg.m_0_0.add_feature.preset.ocean(reproject="gpd") >>> mg.m_1_0.add_feature.preset.ocean(reproject="cartopy") - The default is "gpd". - verbose : bool, optional Indicator if a progressbar should be printed when re-projecting - geometries with "use_gpd=False". - The default is False. - + geometries with "use_gpd=False". The default is False. only_valid : bool, optional - - If True, only valid geometries (e.g. `gdf.is_valid`) are plotted. - If False, all geometries are attempted to be plotted (this might result in errors for infinite geometries etc.) The default is True set_extent: bool, optional - - - if True, set the map extent to the extent of the geometries with - a +-5% margin. - - if float, use the value se margin. + - if True, set map extent to the extent of the geometries with +-5% margin. + - if float, use the value as margin (0-1). The default is True. kwargs : From ba42feb284774db595a1706cd67ff58446f12e30 Mon Sep 17 00:00:00 2001 From: Raphael Date: Mon, 9 Oct 2023 10:51:48 +0200 Subject: [PATCH 15/16] add option to add gridlines as dynamic artists --- eomaps/grid.py | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/eomaps/grid.py b/eomaps/grid.py index 078acbf0a..dd2ddf34b 100644 --- a/eomaps/grid.py +++ b/eomaps/grid.py @@ -14,7 +14,9 @@ class GridLines: """Class to draw grid-lines.""" - def __init__(self, m, d=None, auto_n=10, layer=None, bounds=None, n=100): + def __init__( + self, m, d=None, auto_n=10, layer=None, bounds=None, n=100, dynamic=False + ): self.m = m._proxy(m) self._d = d @@ -28,6 +30,8 @@ def __init__(self, m, d=None, auto_n=10, layer=None, bounds=None, n=100): self._layer = layer self._grid_labels = [] + self._dynamic = dynamic + @property def d(self): """The fixed grid-spacing distance (if specified).""" @@ -316,7 +320,10 @@ def _add_grid(self, **kwargs): self.m.ax.add_collection(self._coll) # don't trigger draw since this would result in a recursion! # (_redraw is called on each fetch-bg event) - self.m.BM.add_bg_artist(self._coll, layer=self.layer, draw=False) + if self._dynamic: + self.m.BM.add_artist(self._coll, layer=self.layer) + else: + self.m.BM.add_bg_artist(self._coll, layer=self.layer, draw=False) def _redraw(self): self._get_lines.cache_clear() @@ -338,7 +345,11 @@ def _remove(self): # don't trigger draw since this would result in a recursion! # (_redraw is called on each fetch-bg event) - self.m.BM.remove_bg_artist(self._coll, layer=self.layer, draw=False) + if self._dynamic: + self.m.BM.remove_artist(self._coll, layer=self.layer) + else: + self.m.BM.remove_bg_artist(self._coll, layer=self.layer, draw=False) + try: self._coll.remove() except ValueError: @@ -856,7 +867,10 @@ def _add_axis_labels(self, lines, axis): ) # exclude artist in companion widget editor t.set_label("__EOmaps_exclude") - m.BM.add_bg_artist(t, layer=self._g.layer, draw=False) + if self._g._dynamic: + m.BM.add_artist(t, layer=self._g.layer) + else: + m.BM.add_bg_artist(t, layer=self._g.layer, draw=False) self._texts.append(t) def add_labels(self): @@ -898,6 +912,7 @@ def add_grid( *, m=None, labels=False, + dynamic=False, **kwargs, ): """ @@ -991,7 +1006,9 @@ def add_grid( kwargs.setdefault("lw", lw) kwargs.setdefault("zorder", 100) - g = GridLines(m=m, d=d, auto_n=auto_n, n=n, bounds=bounds, layer=layer) + g = GridLines( + m=m, d=d, auto_n=auto_n, n=n, bounds=bounds, layer=layer, dynamic=dynamic + ) g._add_grid(**kwargs) self._gridlines.append(g) From fb4ba846d5898620a20ba4551ba57e711eac47d0 Mon Sep 17 00:00:00 2001 From: Raphael Date: Mon, 9 Oct 2023 11:11:46 +0200 Subject: [PATCH 16/16] add proper remove method for grid-labels --- eomaps/grid.py | 36 +++++++++++++++++++++++------------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/eomaps/grid.py b/eomaps/grid.py index dd2ddf34b..05d3c8970 100644 --- a/eomaps/grid.py +++ b/eomaps/grid.py @@ -589,6 +589,27 @@ def _get_intersect(self, a1, a2, b1, b2): return (float("inf"), float("inf")) return (x / z, y / z) + def remove(self): + """Remove the grid-labels from the map.""" + if self._redraw in self._g.m.BM._before_fetch_bg_actions: + self._g.m.BM._before_fetch_bg_actions.remove(self._redraw) + + while len(self._texts) > 0: + try: + t = self._texts.pop(-1) + try: + t.remove() + except ValueError: + pass + + if self._g._dynamic: + self._g.m.BM.remove_artist(t) + else: + self._g.m.BM.remove_bg_artist(t, draw=False) + except Exception: + _log.exception("EOmaps: Problem while trying to remove a grid-label:") + pass + def _redraw(self, **kwargs): try: m = self._g.m @@ -609,19 +630,7 @@ def _redraw(self, **kwargs): self._last_extent = extent self._last_ax_pos = pos - while len(self._texts) > 0: - try: - t = self._texts.pop(-1) - try: - t.remove() - except ValueError: - pass - self._g.m.BM.remove_bg_artist(t, draw=False) - except Exception: - _log.exception( - "EOmaps: Problem while trying to remove a grid-label:" - ) - pass + self.remove() self.add_labels() except Exception: @@ -867,6 +876,7 @@ def _add_axis_labels(self, lines, axis): ) # exclude artist in companion widget editor t.set_label("__EOmaps_exclude") + if self._g._dynamic: m.BM.add_artist(t, layer=self._g.layer) else: