diff --git a/js/models/event_handler.js b/js/models/event_handler.js index c3a4bcc1..9a8e2a00 100644 --- a/js/models/event_handler.js +++ b/js/models/event_handler.js @@ -310,6 +310,8 @@ export default class EventHandler { add_MOC_from_URL: this.messageHandler.handleAddMOCFromURL, add_MOC_from_dict: this.messageHandler.handleAddMOCFromDict, add_overlay: this.messageHandler.handleAddOverlay, + remove_overlay: this.messageHandler.handleRemoveOverlay, + get_overlays: this.messageHandler.handleGetOverlays, change_colormap: this.messageHandler.handleChangeColormap, get_JPG_thumbnail: this.messageHandler.handleGetJPGThumbnail, trigger_selection: this.messageHandler.handleTriggerSelection, diff --git a/js/models/message_handler.js b/js/models/message_handler.js index 63e70439..4bcda890 100644 --- a/js/models/message_handler.js +++ b/js/models/message_handler.js @@ -144,6 +144,31 @@ export default class MessageHandler { } } + handleRemoveOverlay = (msg) => { + const overlay_names = msg["overlay_names"]; + for (const overlay_name of overlay_names) { + console.info(`Sending removeOverlay for ${overlay_name}`); + this.aladin.removeOverlay(overlay_name); + } + this.handleGetOverlays(); + }; + + handleGetOverlays = () => { + const overlay_names = []; + const overlays = this.aladin.getOverlays(); + for (const overlay of overlays) { + const overlay_name = overlay["name"]; + overlay_names.push(overlay_name); + } + console.info(`Current overlays are ${overlay_names}`); + this.model.send({ + event_type: "current_overlays", + content: { + overlays: overlay_names, + }, + }); + }; + handleChangeColormap(msg) { this.aladin.getBaseImageLayer().setColormap(msg["colormap"]); } diff --git a/src/ipyaladin/widget.py b/src/ipyaladin/widget.py index 059f2491..d8b73aa5 100644 --- a/src/ipyaladin/widget.py +++ b/src/ipyaladin/widget.py @@ -178,6 +178,11 @@ class Aladin(anywidget.AnyWidget): ).tag(sync=True) _wcs = traitlets.Dict().tag(sync=True) _fov_xy = traitlets.Dict().tag(sync=True) + # Overlays + _overlays = traitlets.List( + [], + help="A list of overlays on the widget.", + ).tag(sync=True) # content of the last click clicked_object = traitlets.Dict().tag(sync=True) @@ -259,6 +264,9 @@ def _handle_custom_message(self, _: any, message: dict, buffers: any) -> None: self.listener_callback["select"](message["content"]) elif event_type == "save_view_as_image": self._save_file(message["path"], buffers[0]) + elif event_type == "current_overlays": + self._overlays = message["content"]["overlays"] + self.listener_callback["current_overlays"](message["content"]) @property def selected_objects(self) -> List[Table]: @@ -276,6 +284,17 @@ def selected_objects(self) -> List[Table]: catalogs.append(Table(objects_data)) return catalogs + @property + def overlays(self) -> List: + """The list of overlays on the widget. + + Returns + ------- + list + A list of strings representing the widget overlays. + """ + return self._overlays + @property def height(self) -> int: """The height of the widget. @@ -992,6 +1011,35 @@ def add_graphic_overlay_from_stcs( } ) + @widget_should_be_loaded + def remove_overlay(self, overlay_name: Union[Iterable[str], str]) -> None: + """Remove an overlay layer defined by a string. + + Parameters + ---------- + overlay_name : str, Iterable[str] + The string or an iterable of strings. + """ + overlay_names = ( + [overlay_name] if isinstance(overlay_name, str) else overlay_name + ) + + self.send( + { + "event_name": "remove_overlay", + "overlay_names": overlay_names, + } + ) + + @widget_should_be_loaded + def get_overlays(self) -> List: + """Update the current overlays defined by their names.""" + self.send( + { + "event_name": "get_overlays", + } + ) + @widget_should_be_loaded def set_color_map(self, color_map_name: str) -> None: """Change the color map of the Aladin Lite widget. @@ -1043,7 +1091,8 @@ def set_listener(self, listener_type: str, callback: Callable) -> None: Parameters ---------- listener_type : str - Can either be 'object_hovered', 'object_clicked', 'click' or 'select' + Can either be 'object_hovered', 'object_clicked', 'click', 'select', + or 'current_overlays' callback : Callable A python function to be called when the event corresponding to the listener_type is detected @@ -1057,10 +1106,13 @@ def set_listener(self, listener_type: str, callback: Callable) -> None: self.listener_callback["click"] = callback elif listener_type == "select": self.listener_callback["select"] = callback + elif listener_type == "current_overlays": + self.listener_callback["current_overlays"] = callback else: raise ValueError( "listener_type must be 'object_hovered', " - "'object_clicked', 'click' or 'select'" + "'object_clicked', 'click', 'select', " + "or 'current_overlays'" ) @widget_should_be_loaded diff --git a/src/tests/test_aladin.py b/src/tests/test_aladin.py index 096ee352..74adbd66 100644 --- a/src/tests/test_aladin.py +++ b/src/tests/test_aladin.py @@ -322,3 +322,51 @@ def test_add_table(monkeypatch: Callable) -> None: "conversion_maj_axis": 1, } assert table_sent_message["options"]["ellipse_error"] == ellipse_options + + +test_overlay_names = [ + "overlay", + ["overlay", "overlay_1", "2MASS"], + "catalog", + ["overlay_1", "2MASS"], +] + + +@pytest.mark.parametrize("overlay_names", test_overlay_names) +def test_remove_overlay( + monkeypatch: Callable, + overlay_names: Union[Iterable[str], str], +) -> None: + """Test proper messages sent for removing overlays using their name string(s). + + Parameters + ---------- + overlay_names : Union[Iterable[str], str] + The name strings of overlays. + """ + mock_send = Mock() + monkeypatch.setattr(Aladin, "send", mock_send) + aladin.remove_overlay(overlay_names) + + event_name = mock_send.call_args[0][0]["event_name"] + assert isinstance(event_name, str) + assert event_name == "remove_overlay" + + name_info = mock_send.call_args[0][0]["overlay_names"] + assert isinstance(name_info, list) + assert name_info[0] in overlay_names + + if isinstance(overlay_names, list): + assert name_info == overlay_names + + +def test_get_overlays( + monkeypatch: Callable, +) -> None: + """Test proper message sent for getting current overlays.""" + mock_send = Mock() + monkeypatch.setattr(Aladin, "send", mock_send) + aladin.get_overlays() + event_name = mock_send.call_args[0][0]["event_name"] + assert isinstance(event_name, str) + assert event_name == "get_overlays"