Skip to content

Commit

Permalink
feat: add_markers (#111)
Browse files Browse the repository at this point in the history
* feat: Implement add_marker to add a marker to the Aladin Lite view

* docs: Add documentation and update changelog

* 🐛 Force marker popup text color to black

* fix: wrong position of markers due to string parsing in JS

---------

Co-authored-by: MARCHAND MANON <[email protected]>
  • Loading branch information
Xen0Xys and ManonMarchand authored Sep 16, 2024
1 parent 8123c49 commit d751864
Show file tree
Hide file tree
Showing 11 changed files with 136 additions and 69 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Add selected sources export as `astropy.Table` list with property `selected_objects` (#100)
- Add function `get_view_as_fits` to export the view as a `astropy.io.fits.HDUList` (#98)
- Add function `save_view_as_image` to save the view as an image file (#108)
- Support for planetary objects for Aladin Lite target (#103)
- Support planetary objects for ipyaladin targets (#103)
- new method `add_marker` to add a marker to the view (#111)

### Deprecated

Expand Down
111 changes: 44 additions & 67 deletions examples/02_Base_Commands.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
"metadata": {},
"outputs": [],
"source": [
"from ipyaladin import Aladin\n",
"from astropy.coordinates import Angle, SkyCoord\n",
"from ipyaladin import Aladin, Marker\n",
"from pathlib import Path"
]
},
Expand All @@ -23,24 +24,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"A list of all available commands can be displayed as such."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"print(dir(Aladin))"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"A few of them are illustrated in the next cells. Let's first, create the widget with a few initial parameters:"
"`ipyaladin`'s full list of methods can be found in the documentation [here](https://cds-astro.github.io/ipyaladin/autoapi/ipyaladin/widget/index.html). A few of them are illustrated in the next cells. Let's first, create the widget with a few initial parameters:"
]
},
{
Expand All @@ -64,22 +48,20 @@
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"tags": []
},
"metadata": {},
"outputs": [],
"source": [
"aladin.target = \"sgr a*\""
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"aladin.target"
],
"outputs": [],
"execution_count": null
]
},
{
"cell_type": "markdown",
Expand All @@ -91,9 +73,7 @@
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"tags": []
},
"metadata": {},
"outputs": [],
"source": [
"aladin.fov = 2"
Expand All @@ -118,9 +98,7 @@
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"tags": []
},
"metadata": {},
"outputs": [],
"source": [
"aladin.overlay_survey = \"P/allWISE/color\"\n",
Expand All @@ -137,9 +115,7 @@
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"tags": []
},
"metadata": {},
"outputs": [],
"source": [
"aladin.coo_frame = \"ICRSd\" # ICRS, and angles expressed in degrees"
Expand All @@ -161,15 +137,6 @@
"The target and field of view can be set with astropy objects"
]
},
{
"cell_type": "code",
"metadata": {},
"source": [
"from astropy.coordinates import Angle, SkyCoord"
],
"outputs": [],
"execution_count": null
},
{
"cell_type": "code",
"execution_count": null,
Expand Down Expand Up @@ -204,32 +171,42 @@
"source": [
"aladin.add_fits(Path(\"images/m31.fits\"), name=\"M31\", opacity=0.5)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"You can add markers to the view of the widget with custom popup title and description.\n",
"Here we will add markers for Messier objects M1 to M10."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"markers = []\n",
"for i in range(1, 11):\n",
" name = f\"M{i}\"\n",
" markers.append(\n",
" Marker(\n",
" position=name,\n",
" title=name,\n",
" description=(\n",
" '<a href=\"https://simbad.cds.unistra.fr/simbad/'\n",
" f'sim-basic?Ident={name}&submit=SIMBAD+search\"> '\n",
" \"Read more on SIMBAD</a>\"\n",
" ),\n",
" )\n",
" )\n",
"aladin.add_markers(markers, name=\"M1-M10\", color=\"pink\", shape=\"cross\", source_size=15)\n",
"aladin.target = \"M1\"\n",
"aladin.fov = 0.2"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.8"
},
"vscode": {
"interpreter": {
"hash": "916dbcbb3f70747c44a77c7bcd40155683ae19c65e1c03b4aa3499c5328201f1"
}
}
},
"metadata": {},
"nbformat": 4,
"nbformat_minor": 4
}
1 change: 1 addition & 0 deletions js/models/event_handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,7 @@ export default class EventHandler {
});

this.eventHandlers = {
add_marker: this.messageHandler.handleAddMarker,
change_fov: this.messageHandler.handleChangeFoV,
goto_ra_dec: this.messageHandler.handleGotoRaDec,
save_view_as_image: this.messageHandler.handleSaveViewAsImage,
Expand Down
20 changes: 20 additions & 0 deletions js/models/message_handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,26 @@ export default class MessageHandler {
this.model = model;
}

handleAddMarker(msg) {
const options = convertOptionNamesToCamelCase(msg["options"] || {});
// default name
if (!options.name) options.name = "markers";
// create catalog
const catalog = A.catalog(options);
this.aladin.addCatalog(catalog);
const pythonMarkers = msg["markers"];
const markers = [];
for (const marker of pythonMarkers) {
markers.push(
A.marker(marker["lon"], marker["lat"], {
popupTitle: marker["title"],
popupDesc: marker["description"],
}),
);
}
catalog.addSources(markers);
}

handleChangeFoV(msg) {
this.aladin.setFoV(msg["fov"]);
}
Expand Down
1 change: 1 addition & 0 deletions src/ipyaladin/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Top-level package for ipyaladin."""

from .widget import Aladin # noqa: F401
from .elements.marker import Marker # noqa: F401
from .__about__ import __version__, __aladin_lite_version__ # noqa: F401
File renamed without changes.
31 changes: 31 additions & 0 deletions src/ipyaladin/elements/marker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from dataclasses import dataclass
from typing import Tuple, Union

from astropy.coordinates import SkyCoord, Longitude, Latitude
from ipyaladin.utils._coordinate_parser import _parse_coordinate_string


@dataclass
class Marker:
"""A class representing a marker in Aladin Lite."""

def __init__(
self,
position: Union[str, SkyCoord, Tuple[Longitude, Latitude]],
title: str,
description: str,
) -> None:
self.title = title
self.description = description
if isinstance(position, SkyCoord):
self.lon = position.ra.deg
self.lat = position.dec.deg
elif isinstance(position, str):
self.lon, self.lat = _parse_coordinate_string(position)
elif (
isinstance(position, Tuple)
and isinstance(position[0], Longitude)
and isinstance(position[1], Latitude)
):
self.lon = position[0].deg
self.lat = position[1].deg
38 changes: 37 additions & 1 deletion src/ipyaladin/widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"""

from collections.abc import Callable
from dataclasses import asdict
import io
import pathlib
from json import JSONDecodeError
Expand All @@ -27,6 +28,7 @@

from .utils.exceptions import WidgetReducedError, WidgetNotReadyError
from .utils._coordinate_parser import _parse_coordinate_string
from .elements.marker import Marker

try:
from regions import (
Expand Down Expand Up @@ -281,7 +283,7 @@ class Aladin(anywidget.AnyWidget):
"is reduced in size when hidden.",
).tag(sync=True)

init_options = traitlets.List(trait=Any()).tag(sync=True)
init_options = traitlets.List(trait=traitlets.Any()).tag(sync=True)

@default("init_options")
def _init_options(self) -> List[str]:
Expand Down Expand Up @@ -507,6 +509,36 @@ def target(self, target: Union[str, SkyCoord, Tuple[float, float]]) -> None:
}
)

def add_markers(
self, markers: Union[Marker, List[Marker]], **catalog_options: any
) -> None:
"""Add markers to the Aladin Lite widget.
Markers have a popup window that appear when they're clicked on.
Parameters
----------
markers : Marker or list[Marker]
The marker(s) to add to the widget. It can be given as a single `Marker`
object or as a list of `Marker` objects.
catalog_options : any
The options for the catalog. See the `Aladin Lite catalog options
<https://cds-astro.github.io/aladin-lite/global.html#CatalogOptions>`_
See Also
--------
add_table: also adds points, but without popup window.
"""
if not isinstance(markers, list):
markers = [markers]
self.send(
{
"event_name": "add_marker",
"markers": [asdict(marker) for marker in markers],
"options": catalog_options,
}
)

def _save_file(self, path: str, buffer: bytes) -> None:
"""Save a file from a buffer.
Expand Down Expand Up @@ -776,6 +808,10 @@ def add_table(self, table: Union[QTable, Table], **table_options: any) -> None:
table options
<https://cds-astro.github.io/aladin-lite/global.html#CatalogOptions>`_
See Also
--------
add_markers: adds markers with a popup window when clicked
"""
table_bytes = io.BytesIO()
table.write(table_bytes, format="votable")
Expand Down
Empty file added src/tests/__init__.py
Empty file.
File renamed without changes.
File renamed without changes.

0 comments on commit d751864

Please sign in to comment.