From c05d6c3f983e0ac25814f92cb6706dc5c6593b30 Mon Sep 17 00:00:00 2001 From: Raphael Date: Thu, 7 Dec 2023 18:28:32 +0100 Subject: [PATCH] add new "overlay_layer" keypress callback and improve "show_layer" callback --- docs/api_callbacks.rst | 1 + eomaps/callbacks.py | 106 +++++++++++++++++++++++++++++++++++++--- tests/test_callbacks.py | 41 +++++++++++++++- 3 files changed, 138 insertions(+), 10 deletions(-) diff --git a/docs/api_callbacks.rst b/docs/api_callbacks.rst index f3c1f654d..c8a42ced2 100644 --- a/docs/api_callbacks.rst +++ b/docs/api_callbacks.rst @@ -199,6 +199,7 @@ Callbacks that can be used with ``m.cb.keypress`` :nosignatures: switch_layer + overlay_layer fetch_layers diff --git a/eomaps/callbacks.py b/eomaps/callbacks.py index 15eb1129c..0ac66b292 100644 --- a/eomaps/callbacks.py +++ b/eomaps/callbacks.py @@ -1232,7 +1232,7 @@ def __init__(self, *args, **kwargs): class KeypressCallbacks: """Collection of callbacks that are executed if you press a key on the keyboard.""" - _cb_list = ["switch_layer", "fetch_layers"] + _cb_list = ["switch_layer", "fetch_layers", "overlay_layer"] def __init__(self, m, temp_artists): self._temporary_artists = temp_artists @@ -1240,16 +1240,62 @@ def __init__(self, m, temp_artists): def switch_layer(self, layer, key="x"): """ - Change the default layer of the map. + Set the currently visible layer of the map. - Use the keyboard events to set the default layer (e.g. the visible layer) - displayed in the plot. + Parameters + ---------- + layer : str or list + The layer-name to use (or a list of layer-names to combine). + + For details on how to specify layer-names, see :py:meth:`Maps.show_layer` + + Additional Parameters + --------------------- + key : str, optional + The key to use for triggering the callback. + Modifiers are indicated with a "+", e.g. "alt+x". + The default is "x". + + Examples + -------- + Show layer A: + + >>> m.cb.keypress.attach.overlay_layer(layer="A", key="x") + + Show layer B with 50% transparency on top of layer A + + >>> m.cb.keypress.attach.overlay_layer(layer="A|B{0.5}", key="x") + + Show layer B on top of layer A: + + >>> m.cb.keypress.attach.overlay_layer(layer=["A", "B"], key="x") + + Show layer B with 50% transparency on top of layer A + + >>> m.cb.keypress.attach.overlay_layer(layer=["A", ("B", 0.5)], key="x") + + + """ + if isinstance(layer, (list, tuple)): + self._m.show_layer(*layer) + elif isinstance(layer, str): + self._m.show_layer(layer) + + def overlay_layer(self, layer, key="x"): + """ + Toggle displaying a layer on top of the currently visible layers. + + - If the layer is not part of the currently visible layers, it will be + added on top. + - If the layer is part of the currently visible layers, it will be removed. Parameters ---------- - layer : str - The layer-name to use. - If a non-string value is provided, it will be converted to string! + layer : str, tuple or list + The layer-name to use, a tuple (layer, transparency) or a list of + the aforementioned types to combine. + + For details on how to specify layer-names, see :py:meth:`Maps.show_layer` Additional Parameters --------------------- @@ -1258,8 +1304,52 @@ def switch_layer(self, layer, key="x"): Modifiers are indicated with a "+", e.g. "alt+x". The default is "x". + Note + ---- + If the visible layer changes **while the overlay-layer is active**, + triggering the callback again might not properly remove the previous overlay! + (e.g. the overlay is only removed if the top-layer corresponds exactly to + the overlay-layer specifications) + + Examples + -------- + Toggle overlaying layer A: + + >>> m.cb.keypress.attach.overlay_layer(layer="A", key="x") + + Toggle overlaying layer A with 50% transparency: + + >>> m.cb.keypress.attach.overlay_layer(layer=("A", 0.5), key="x") + + Toggle overlaying a combined layer (showing layer B with 50% transparency + on top of layer A) + + >>> m.cb.keypress.attach.overlay_layer(layer="A|B{0.5}", key="x") + + Toggle overlaying a combined layer (showing layer B on top of layer A) + + >>> m.cb.keypress.attach.overlay_layer(layer=["A", "B"], key="x") + + Toggle overlaying a combined layer (showing layer B with 50% transparency + on top of layer A) + + >>> m.cb.keypress.attach.overlay_layer(layer=["A", ("B", 0.5)], key="x") + """ - self._m.show_layer(layer) + + if isinstance(layer, list): + layer = self._m._get_combined_layer_name(*layer) + elif isinstance(layer, tuple): + # e.g. (layer-name, layer-transparency) + layer = self._m._get_combined_layer_name(layer) + + # in case the layer is currently on top, remove it + if not self._m.BM.bg_layer.endswith(f"|{layer}"): + self._m.show_layer(self._m.BM.bg_layer, layer) + else: + newlayer = self._m.BM.bg_layer.removesuffix(f"|{layer}") + if len(newlayer) > 0: + self._m.show_layer(newlayer) def fetch_layers(self, layers=None, verbose=True, key="x"): """ diff --git a/tests/test_callbacks.py b/tests/test_callbacks.py index b98d577ce..12248d16a 100644 --- a/tests/test_callbacks.py +++ b/tests/test_callbacks.py @@ -654,6 +654,43 @@ def loadmethod(db, ID): m.cb.pick.remove(cid) plt.close("all") + def test_overlay_layer(self): + # ---------- test as CLICK callback + m = self.create_basic_map() + m_a = m.new_layer("A") + m_b = m.new_layer("B") + + cid0 = m.all.cb.keypress.attach.overlay_layer(layer="A", key="0") + cid1 = m.all.cb.keypress.attach.overlay_layer(layer=("B", 0.5), key="1") + cid2 = m.all.cb.keypress.attach.overlay_layer(layer=["A", ("B", 0.5)], key="2") + + init_layer = m.layer + + key_press_event(m.f.canvas, "0") + key_release_event(m.f.canvas, "0") + self.assertTrue(m.BM._bg_layer == m._get_combined_layer_name(m.layer, "A")) + key_press_event(m.f.canvas, "0") + key_release_event(m.f.canvas, "0") + self.assertTrue(m.BM._bg_layer == m.layer) + + key_press_event(m.f.canvas, "1") + key_release_event(m.f.canvas, "1") + self.assertTrue( + m.BM._bg_layer == m._get_combined_layer_name(m.layer, ("B", 0.5)) + ) + key_press_event(m.f.canvas, "1") + key_release_event(m.f.canvas, "1") + self.assertTrue(m.BM._bg_layer == m.layer) + + key_press_event(m.f.canvas, "2") + key_release_event(m.f.canvas, "2") + self.assertTrue( + m.BM._bg_layer == m._get_combined_layer_name(m.layer, "A", ("B", 0.5)) + ) + key_press_event(m.f.canvas, "2") + key_release_event(m.f.canvas, "2") + self.assertTrue(m.BM._bg_layer == m.layer) + def test_switch_layer(self): # ---------- test as CLICK callback m = self.create_basic_map() @@ -666,7 +703,7 @@ def test_switch_layer(self): cid1 = m.all.cb.keypress.attach.switch_layer(layer="2", key="2") # a callback only active on the "base" layer - cid3 = m.cb.keypress.attach.switch_layer(layer="3", key="3") + cid3 = m.cb.keypress.attach.switch_layer(layer=["2", ("3", 0.5)], key="3") # switch to layer 2 key_press_event(m.f.canvas, "2") @@ -686,7 +723,7 @@ def test_switch_layer(self): # now the 3rd callback should trigger key_press_event(m.f.canvas, "3") key_release_event(m.f.canvas, "3") - self.assertTrue(m.BM._bg_layer == "3") + self.assertTrue(m.BM._bg_layer == m._get_combined_layer_name("2", ("3", 0.5))) m.all.cb.keypress.remove(cid0) m.all.cb.keypress.remove(cid1)