diff --git a/js/models/message_handler.js b/js/models/message_handler.js index 74f29f6b..f9114a78 100644 --- a/js/models/message_handler.js +++ b/js/models/message_handler.js @@ -30,28 +30,47 @@ export default class MessageHandler { } handleAddOverlay(msg) { - const infos = msg["infos"]; - const options = convertOptionNamesToCamelCase(msg["options"] || {}); - const overlay = A.graphicOverlay(options); + const regions = msg["regions_infos"]; + const graphic_options = convertOptionNamesToCamelCase( + msg["graphic_options"] || {}, + ); + if (!graphic_options["color"]) graphic_options["color"] = "red"; + const overlay = A.graphicOverlay(graphic_options); this.aladin.addOverlay(overlay); - switch (msg["region_type"]) { - case "stcs": - overlay.addFootprints(A.footprintsFromSTCS(infos.stcs)); - break; - case "circle": - overlay.add(A.circle(infos.ra, infos.dec, infos.radius)); - break; - case "ellipse": - overlay.add( - A.ellipse(infos.ra, infos.dec, infos.a, infos.b, infos.theta), - ); - break; - case "line": - overlay.add(A.line(infos.ra1, infos.dec1, infos.ra2, infos.dec2)); - break; - case "polygon": - overlay.add(A.polygon(infos.vertices)); - break; + for (const region of regions) { + const infos = region["infos"]; + switch (region["region_type"]) { + case "stcs": + overlay.addFootprints( + A.footprintsFromSTCS(infos.stcs, region.options), + ); + break; + case "circle": + overlay.add( + A.circle(infos.ra, infos.dec, infos.radius, region.options), + ); + break; + case "ellipse": + overlay.add( + A.ellipse(infos.ra, infos.dec, infos.a, infos.b, infos.theta), + region.options, + ); + break; + case "line": + overlay.add( + A.line( + infos.ra1, + infos.dec1, + infos.ra2, + infos.dec2, + region.options, + ), + ); + break; + case "polygon": + overlay.add(A.polygon(infos.vertices, region.options)); + break; + } } } diff --git a/src/ipyaladin/aladin.py b/src/ipyaladin/aladin.py index c380287f..143561ba 100644 --- a/src/ipyaladin/aladin.py +++ b/src/ipyaladin/aladin.py @@ -356,6 +356,16 @@ def add_table(self, table: Union[QTable, Table], **table_options: any) -> None: def add_overlay( self, region: Union[ + typing.List[ + Union[ + str, + CircleSkyRegion, + EllipseSkyRegion, + LineSkyRegion, + PolygonSkyRegion, + RectangleSkyRegion, + ] + ], str, CircleSkyRegion, EllipseSkyRegion, @@ -363,56 +373,53 @@ def add_overlay( PolygonSkyRegion, RectangleSkyRegion, ], - **overlay_options: any, + **graphic_options: any, ) -> None: """Add an overlay layer to the Aladin Lite widget. Parameters ---------- - region: str, CircleSkyRegion, EllipseSkyRegion, LineSkyRegion - The region to overlay. It can be a string, a CircleSkyRegion, - an EllipseSkyRegion, a LineSkyRegion or a RectangleSkyRegion. - overlay_options: keyword arguments + region: str, `~CircleSkyRegion`, `~EllipseSkyRegion`, `~LineSkyRegion`, + `~PolygonSkyRegion`, `~RectangleSkyRegion` + The region to add in Aladin Lite. It can be given + as a string or one of the supported regions + graphic_options: keyword arguments + The options for the graphic overlay. Use Region visual for region options. """ - # Check if the regions library is installed and raise an error if not - if ( - not isinstance(region, str) and CircleSkyRegion is None - ): # Only need to check one of the imports - raise ValueError( - "A region can be given as an STC-S string or a regions " - "object. To read regions objects, you need to install the regions " - "library with 'pip install regions'." - ) - - if not isinstance(region, str) and not isinstance(region, Region): - raise ValueError( - "region must be a string or a regions object. See the documentation " - "for the supported region types." - ) - - from .region_converter import RegionInfos + if not isinstance(region, list): + region = [region] + + regions_infos = [] + for region_element in region: + # Check if the regions library is installed and raise an error if not + if ( + not isinstance(region_element, str) and Region is None + ): # Only need to check one of the imports + raise ValueError( + "A region can be given as an STC-S string or a regions " + "object. To read regions objects, you need to install the regions " + "library with 'pip install regions'." + ) + + if not isinstance(region_element, str) and not isinstance( + region_element, Region + ): + raise ValueError( + "region must be a string or a regions object. See the " + "documentation for the supported region types." + ) - # Visual mapping to Aladin Lite overlay options - if isinstance(region, Region): - visual = dict(region.visual) - if "linewidth" in visual: - visual["line_width"] = visual.pop("linewidth") - if "facecolor" in visual: - visual["fill_color"] = visual.pop("facecolor") - if "edgecolor" in visual: - visual["color"] = visual.pop("edgecolor") - overlay_options = {**overlay_options, **region.visual} + from .region_converter import RegionInfos - # Define behavior for each region type - region_infos = RegionInfos(region) + # Define behavior for each region type + regions_infos.append(RegionInfos(region_element).to_clean_dict()) self.send( { "event_name": "add_overlay", - "region_type": region_infos.region_type, - "infos": region_infos.infos, - "options": overlay_options, + "regions_infos": regions_infos, + "graphic_options": graphic_options, } ) diff --git a/src/ipyaladin/region_converter.py b/src/ipyaladin/region_converter.py index 3e207fb9..95e8d6d0 100644 --- a/src/ipyaladin/region_converter.py +++ b/src/ipyaladin/region_converter.py @@ -1,12 +1,21 @@ import math -from regions import ( - RectangleSkyRegion, - PolygonSkyRegion, - Region, - CircleSkyRegion, - EllipseSkyRegion, - LineSkyRegion, -) + +try: + from regions import ( + RectangleSkyRegion, + PolygonSkyRegion, + Region, + CircleSkyRegion, + EllipseSkyRegion, + LineSkyRegion, + ) +except ImportError: + RectangleSkyRegion = None + PolygonSkyRegion = None + Region = None + CircleSkyRegion = None + EllipseSkyRegion = None + LineSkyRegion = None from astropy.coordinates import SkyCoord from typing import Union @@ -220,6 +229,8 @@ def from_region(self, region: Union[str, Region]) -> None: raise ValueError(f"Unsupported region type: {type(region).__name__}") region_parser = self._region_parsers[type(region).__name__] region_parser(region) + if isinstance(region, Region): + self._parse_visuals(region) def _from_stcs(self, stcs: str) -> None: self.region_type = "stcs" @@ -260,3 +271,28 @@ def _from_polygon_sky_region(self, region: PolygonSkyRegion) -> None: def _from_rectangle_sky_region(self, region: RectangleSkyRegion) -> None: # Rectangle is interpreted as a polygon in Aladin Lite self._from_polygon_sky_region(rectangle_to_polygon_region(region)) + + def _parse_visuals(self, region: Region) -> None: + visual = dict(region.visual) + if "linewidth" in visual: + visual["line_width"] = visual.pop("linewidth") + if "facecolor" in visual: + visual["fill_color"] = visual.pop("facecolor") + if "edgecolor" in visual: + visual["color"] = visual.pop("edgecolor") + self.options = visual + + def to_clean_dict(self) -> dict: + """Return a clean dictionary representation of the region. + + Returns + ------- + dict + The dictionary representation of the region. + + """ + return { + "region_type": self.region_type, + "infos": self.infos, + "options": self.options, + }