From 4881fc0961e2286e29dce0fc79227e4220d77c07 Mon Sep 17 00:00:00 2001 From: Eric Neilsen Date: Wed, 1 Apr 2026 09:48:12 -0700 Subject: [PATCH 1/6] Initial implementation of refined controls for nightsum uranography map --- schedview/plot/visit_skymaps.py | 273 +++++++++++++++++++++++++++++++- 1 file changed, 268 insertions(+), 5 deletions(-) diff --git a/schedview/plot/visit_skymaps.py b/schedview/plot/visit_skymaps.py index b1aaebd5..892bf70d 100644 --- a/schedview/plot/visit_skymaps.py +++ b/schedview/plot/visit_skymaps.py @@ -10,7 +10,7 @@ import warnings from types import MethodType -from typing import Any, Callable, Dict, List, Optional, Self, SupportsFloat, Tuple +from typing import Any, Callable, Dict, List, Optional, Self, SupportsFloat, Tuple, cast import bokeh import bokeh.layouts @@ -23,6 +23,7 @@ import pandas as pd from astropy.coordinates import SkyCoord, get_body from astropy.time import Time +from bokeh.models import UIElement from uranography.api import ( ArmillarySphere, Planisphere, @@ -1252,6 +1253,169 @@ def add_alt_visits_selector( return self + def add_play_controls(self, speed: int = 100) -> Self: + """Add Play/Stop control. + + Parameters + ---------- + speed : `int`, optional + The speed of the playback in milliseconds between updates. + Default is 100. + + Returns + ------- + self : `VisitMapBuilder` + Returns self to enable method chaining. + + Notes + ----- + * The toggle button switches between "Play" and "Stop" states. + When in the "Play" (active) state, the MJD (or datetime) slider(s) + advance automatically at a rate set by ``speed``, and the button + reads "Stop." + * While playing, the RA and decl sliders are disabled to prevent + conflicts with the automatic MJD updates. + """ + play_toggle = bokeh.models.Toggle(label="\u23f5 Play", button_type="primary", active=True) + + play_callback_code = r""" + const key = 'bokeh_play_timer_' + play.id; + function tick() { + const step = (mjd.step != null) ? mjd.step : 0.0004; + let next = mjd.value + step; + if (next > mjd.end) next = mjd.start; + mjd.value = next; + } + + // If toggled on, start or reset the interval at current speed + if (play.active) { + + // Disable the RA and decl sliders when playing because the + // callbacks stomp on each other causing problems. + if (ra_slider != null) { ra_slider.disabled = true } + if (decl_slider != null) { decl_slider.disabled = true } + + play.label = "\u23F8 Stop"; + if (window[key]) { + clearInterval(window[key]); // reset if already running + } + window[key] = setInterval(tick, speed); + } else { + if (ra_slider != null) { ra_slider.disabled = false } + if (decl_slider != null) { decl_slider.disabled = false } + + play.label = "\u23F5 Play"; + if (window[key]) { + // actually stops the updates + clearInterval(window[key]); + window[key] = null; + } + } + """ + play_toggle_args = { + "speed": speed, + "play": play_toggle, + "mjd": self.mjd_slider, + "ra_slider": self.ref_map.controls.get("ra", None), + "decl_slider": self.ref_map.controls.get("decl", None), + } + play_toggle.js_on_change( + "active", bokeh.models.CustomJS(args=play_toggle_args, code=play_callback_code) + ) + self.ref_map.controls["play"] = play_toggle + + return self + + def add_zenith_button(self) -> Self: + """Add a button to center the map on the zenith. + + Returns + ------- + self : `VisitMapBuilder` + Returns self to enable method chaining. + """ + button = bokeh.models.Button(label="Center zenith") + + code = """ + if (alt != null) { + alt.value = 89.99999 + } + if (az != null) { + az.value = 180.0 + } + """ + + args = {"alt": self.ref_map.controls.get("alt", None), "az": self.ref_map.controls.get("az", None)} + button.js_on_click(bokeh.models.CustomJS(args=args, code=code)) + self.ref_map.controls["zenith"] = button + return self + + def add_coord_sys_selector(self) -> Self: + """Add a coordinate system selector to toggle between equatorial + (R.A, decl) and horizon (alt, az) coordinates. + + Returns + ------- + self : `VisitMapBuilder` + Returns self to enable method chaining. + + Notes + ----- + * The coordinate system selector toggles visibility of the appropriate + sliders: + - Equatorial mode: Shows R.A. and declination sliders, hides alt/az + sliders + - Horizontal mode: Shows alt and az sliders, hides R.A./decl sliders + * The orientation is adjusted according to the coordinate system, such + that the north equatorial pole is at x=0, y>0 in equatorial + coordinates, and the zenith is at x=0, y>0 in horizon coordinates. + * This method requires that the "ra", "decl", "alt", "az", and "up" + controls have already been added to the reference map. + """ + needed_controls = ["ra", "decl", "alt", "az", "up"] + for control_name in needed_controls: + if control_name not in self.ref_map.controls: + raise ValueError(f"{control_name} control must be added before coord sys selector") + + self.ref_map.controls["ra"].visible = True + self.ref_map.controls["decl"].visible = True + self.ref_map.controls["up"].visible = False + self.ref_map.controls["up"].value = "north is up" + self.ref_map.controls["alt"].visible = False + self.ref_map.controls["az"].visible = False + + labels = [ + "sliders in R.A., decl; north equatorial pole directly above center", + "sliders in alt, az; zenith directly above center", + ] + args = { + "alt": self.ref_map.controls["alt"], + "az": self.ref_map.controls["az"], + "up": self.ref_map.controls["up"], + "ra": self.ref_map.controls["ra"], + "decl": self.ref_map.controls["decl"], + "labels": labels, + } + code = """ + if (labels[this.active].toLowerCase().includes("alt")) { + alt.visible = true + az.visible = true + up.value = "zenith is up" + ra.visible = false + decl.visible = false + } else { + alt.visible = false + az.visible = false + up.value = "north is up" + ra.visible = true + decl.visible = true + } + """ + coordsys = bokeh.models.RadioButtonGroup(name="Coordinate system", labels=labels, active=0) + coordsys.js_on_change("active", bokeh.models.CustomJS(args=args, code=code)) + self.ref_map.controls["coordsys"] = coordsys + return self + def _connect_controls(self): # Must be called after all data sources and contros have been added. @@ -1262,12 +1426,111 @@ def _connect_controls(self): for data_source in dynamic_data_sources: spheremap.connect_controls(data_source) + def _nightsum_layout(self) -> UIElement: + """Create a night summary layout for the visit map. + + Returns + ------- + figure : `bokeh.models.UIElement` + A Bokeh UIElement containing the complete night summary layout + with maps, time controls, and coordinate system controls. + + Notes + ----- + The layout is designed for night summary and related schedview + reports. + + The layout includes: + - Map visualization rows with time update indicators + - Time control row with play button and datetime slider + - Button row with zenith centering and coordinate system selector + - Control rows for RA, decl, alt, and az sliders + """ + map_row_contents = [] + for sphere_map in self.spheremaps: + map_row_contents.append(sphere_map.force_update_time) + map_row_contents.append(sphere_map.plot) + + map_row = bokeh.layouts.row(*map_row_contents) + + self.ref_map.controls["datetime"].sizing_mode = "stretch_width" + + time_row = bokeh.layouts.row( + self.ref_map.controls["play"], + self.ref_map.controls["datetime"], + sizing_mode="stretch_width", + ) + + button_row = bokeh.layouts.row( + bokeh.models.Spacer(sizing_mode="stretch_width"), + self.ref_map.controls["zenith"], + bokeh.models.Spacer(sizing_mode="stretch_width"), + self.ref_map.controls["coordsys"], + bokeh.models.Spacer(sizing_mode="stretch_width"), + sizing_mode="stretch_width", + ) + + column_contents = [map_row, time_row, button_row] + + for control_key in ["ra", "decl", "alt", "az"]: + column_contents.append(self.ref_map.controls[control_key]) + + figure = bokeh.layouts.column(*column_contents) + + return figure + def build( - self, layout: Callable[[List[bokeh.models.UIElement]], bokeh.models.UIElement] = bokeh.layouts.row - ) -> bokeh.models.UIElement: + self, + layout: Callable[[List[UIElement]], UIElement] | str = bokeh.layouts.row, + ) -> UIElement: + """Build the visit sky-map visualization. + + Parameters + ---------- + layout : `callable` or `str`, optional + Layout function for combining the spheremap figures. If a callable + is provided, it will be used to arrange the individual map figures. + If the string "nightsum" is provided, a night summary layout is + used. Default is `bokeh.layouts.row`. + + Returns + ------- + figure : `bokeh.models.UIElement` + A Bokeh UIElement containing the complete visualization that can be + embedded in reports, dashboards, or saved as HTML. + + Raises + ------ + `ValueError` + If an invalid layout is provided. + + Notes + ----- + * This method finalizes the visualization by connecting all controls + and assembling the figure according to the specified layout. It + should be called after all configuration methods have been chained. + * This method should be called after all configuration methods have + been chained. + * The returned UIElement can be saved as an HTML file using + `bokeh.io.save(viewable, filename="visit_skymap.html")`. + * The visualization includes all configured elements such as visit + patches, graticules, horizon, celestial bodies, and time controls. + + """ self._connect_controls() - map_figures = list(s.figure for s in self.spheremaps) - combined_figure = layout(map_figures) + combined_figure: UIElement | None = None + match layout: + case "nightsum": + combined_figure = self._nightsum_layout() + case f if callable(f): + map_figures = list(s.figure for s in self.spheremaps) + layout = cast(Callable, layout) + combined_figure = layout(map_figures) + case _: + raise ValueError("Invalid layout supplied") + + assert isinstance(combined_figure, UIElement) + return combined_figure From ab5fbfbec70148607a95c3e110e143ae48659b11 Mon Sep 17 00:00:00 2001 From: "Eric H. Neilsen, Jr." Date: Thu, 2 Apr 2026 15:44:47 -0500 Subject: [PATCH 2/6] Add tests for play, zenith, coordsys, and nightsum layout --- schedview/plot/visit_skymaps.py | 29 ++++++++-- tests/test_plot_visit_skymaps.py | 95 ++++++++++++++++++++++++++++++++ 2 files changed, 118 insertions(+), 6 deletions(-) diff --git a/schedview/plot/visit_skymaps.py b/schedview/plot/visit_skymaps.py index 892bf70d..4bf3f592 100644 --- a/schedview/plot/visit_skymaps.py +++ b/schedview/plot/visit_skymaps.py @@ -1253,7 +1253,7 @@ def add_alt_visits_selector( return self - def add_play_controls(self, speed: int = 100) -> Self: + def add_play_controls(self, speed: int = 100, **kwargs) -> Self: """Add Play/Stop control. Parameters @@ -1261,6 +1261,9 @@ def add_play_controls(self, speed: int = 100) -> Self: speed : `int`, optional The speed of the playback in milliseconds between updates. Default is 100. + **kwargs + Additional keyword arguments passed to the underlying + `bokeh.models.Toggle` constructor. Returns ------- @@ -1276,7 +1279,7 @@ def add_play_controls(self, speed: int = 100) -> Self: * While playing, the RA and decl sliders are disabled to prevent conflicts with the automatic MJD updates. """ - play_toggle = bokeh.models.Toggle(label="\u23f5 Play", button_type="primary", active=True) + play_toggle = bokeh.models.Toggle(label="\u23f5 Play", button_type="primary", active=True, **kwargs) play_callback_code = r""" const key = 'bokeh_play_timer_' + play.id; @@ -1326,15 +1329,21 @@ def add_play_controls(self, speed: int = 100) -> Self: return self - def add_zenith_button(self) -> Self: + def add_zenith_button(self, **kwargs: Any) -> Self: """Add a button to center the map on the zenith. + Parameters + ---------- + **kwargs + Additional keyword arguments passed to the underlying + `bokeh.models.Button` constructor. + Returns ------- self : `VisitMapBuilder` Returns self to enable method chaining. """ - button = bokeh.models.Button(label="Center zenith") + button = bokeh.models.Button(label="Center zenith", **kwargs) code = """ if (alt != null) { @@ -1350,10 +1359,16 @@ def add_zenith_button(self) -> Self: self.ref_map.controls["zenith"] = button return self - def add_coord_sys_selector(self) -> Self: + def add_coord_sys_selector(self, **kwargs: Any) -> Self: """Add a coordinate system selector to toggle between equatorial (R.A, decl) and horizon (alt, az) coordinates. + Parameters + ---------- + **kwargs + Additional keyword arguments passed to the underlying + `bokeh.models.RadioButtonGroup` constructor. + Returns ------- self : `VisitMapBuilder` @@ -1411,7 +1426,9 @@ def add_coord_sys_selector(self) -> Self: decl.visible = true } """ - coordsys = bokeh.models.RadioButtonGroup(name="Coordinate system", labels=labels, active=0) + rb_kwargs = {"name": "Coordinate system", "labels": labels, "active": 0} + rb_kwargs.update(kwargs) + coordsys = bokeh.models.RadioButtonGroup(**rb_kwargs) coordsys.js_on_change("active", bokeh.models.CustomJS(args=args, code=code)) self.ref_map.controls["coordsys"] = coordsys return self diff --git a/tests/test_plot_visit_skymaps.py b/tests/test_plot_visit_skymaps.py index f2c47c30..6218ca1f 100644 --- a/tests/test_plot_visit_skymaps.py +++ b/tests/test_plot_visit_skymaps.py @@ -302,3 +302,98 @@ def test_add_alt_visits_selector(): assert selector.options == expected_options _save_and_check_viewable_html(viewable) + + +def test_add_play_controls(): + """Test that add_play_controls adds a play/stop toggle button.""" + builder = VisitMapBuilder(TEST_VISITS) + builder.add_visit_patches() + builder.add_play_controls(name="playbutton") + viewable = builder.build() + + # Check that the play control was added to the reference map controls + assert "play" in builder.ref_map.controls + play_control = builder.ref_map.controls["play"] + assert isinstance(play_control, bokeh.models.Toggle) + + # Check that it appears in the viewable + playbuttons = list(viewable.select({"name": "playbutton"})) + assert len(playbuttons) > 0 + + # Check that the label contains the play symbol + assert "\u23f5 Play" in play_control.label + + # Verify the viewable can be saved to HTML + _save_and_check_viewable_html(viewable) + + +def test_add_zenith_button(): + """Test that add_zenith_button adds the center zenith button.""" + builder = VisitMapBuilder(TEST_VISITS) + builder.add_visit_patches() + builder.add_zenith_button(name="zenithbutton") + viewable = builder.build() + + # Check that the play control was added to the reference map controls + assert "zenith" in builder.ref_map.controls + play_control = builder.ref_map.controls["zenith"] + assert isinstance(play_control, bokeh.models.Button) + + # Check that it appears in the viewable + zenith_buttons = list(viewable.select({"name": "zenithbutton"})) + assert len(zenith_buttons) > 0 + + # Verify the viewable can be saved to HTML + _save_and_check_viewable_html(viewable) + + +def test_add_coord_sys_selector(): + """Test that add_coord_sys_selector adds a coordinate system selector.""" + # Prepare a builder with the controls that add_coord_sys_selector expects + builder = VisitMapBuilder(TEST_VISITS).add_visit_patches().add_eq_sliders() + + # Add the coordinate system selector + builder.add_coord_sys_selector() + viewable = builder.build() + + # Check that the selector was added to the reference map controls + assert "coordsys" in builder.ref_map.controls + coordsys_selector = builder.ref_map.controls["coordsys"] + assert isinstance(coordsys_selector, bokeh.models.RadioButtonGroup) + assert coordsys_selector.name == "Coordinate system" + + # Check that the selector appears in the viewable + coordsys_selectors = list(viewable.select({"name": "Coordinate system"})) + assert len(coordsys_selectors) > 0 + + # Verify the viewable can be saved to HTML + _save_and_check_viewable_html(viewable) + + +def test_build_with_nightsum_layout(): + """Test a build with layout='nightsum'.""" + + builder = ( + VisitMapBuilder(TEST_VISITS) + .add_visit_patches() + .add_datetime_slider(name="datetime") + .add_eq_sliders() + .add_play_controls(name="play") + .add_zenith_button(name="zenith") + .add_coord_sys_selector() + ) + viewable = builder.build(layout="nightsum") + + play_controls = list(viewable.select({"name": "play"})) + assert len(play_controls) > 0 + + datetime_sliders = list(viewable.select({"name": "datetime"})) + assert len(datetime_sliders) > 0 + + zenith_button = list(viewable.select({"name": "zenith"})) + assert len(zenith_button) > 0 + + coordsys_selectors = list(viewable.select({"name": "Coordinate system"})) + assert len(coordsys_selectors) > 0 + + _save_and_check_viewable_html(viewable) From 5cdebf1caa6fb979f6c0f246deb50c6fe597f980 Mon Sep 17 00:00:00 2001 From: "Eric H. Neilsen, Jr." Date: Thu, 2 Apr 2026 15:52:22 -0500 Subject: [PATCH 3/6] Change button label from stop to pause --- schedview/plot/visit_skymaps.py | 8 ++++---- tests/test_plot_visit_skymaps.py | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/schedview/plot/visit_skymaps.py b/schedview/plot/visit_skymaps.py index 4bf3f592..37b8e939 100644 --- a/schedview/plot/visit_skymaps.py +++ b/schedview/plot/visit_skymaps.py @@ -1254,7 +1254,7 @@ def add_alt_visits_selector( return self def add_play_controls(self, speed: int = 100, **kwargs) -> Self: - """Add Play/Stop control. + """Add Play/Pause control. Parameters ---------- @@ -1272,10 +1272,10 @@ def add_play_controls(self, speed: int = 100, **kwargs) -> Self: Notes ----- - * The toggle button switches between "Play" and "Stop" states. + * The toggle button switches between "Play" and "Pause" states. When in the "Play" (active) state, the MJD (or datetime) slider(s) advance automatically at a rate set by ``speed``, and the button - reads "Stop." + reads "Pause." * While playing, the RA and decl sliders are disabled to prevent conflicts with the automatic MJD updates. """ @@ -1298,7 +1298,7 @@ def add_play_controls(self, speed: int = 100, **kwargs) -> Self: if (ra_slider != null) { ra_slider.disabled = true } if (decl_slider != null) { decl_slider.disabled = true } - play.label = "\u23F8 Stop"; + play.label = "\u23F8 Pause"; if (window[key]) { clearInterval(window[key]); // reset if already running } diff --git a/tests/test_plot_visit_skymaps.py b/tests/test_plot_visit_skymaps.py index 6218ca1f..5edb5674 100644 --- a/tests/test_plot_visit_skymaps.py +++ b/tests/test_plot_visit_skymaps.py @@ -305,7 +305,7 @@ def test_add_alt_visits_selector(): def test_add_play_controls(): - """Test that add_play_controls adds a play/stop toggle button.""" + """Test that add_play_controls adds a play/pause toggle button.""" builder = VisitMapBuilder(TEST_VISITS) builder.add_visit_patches() builder.add_play_controls(name="playbutton") From 06b6b1fa47b632e23c3acc7cdd355ffbf72869c7 Mon Sep 17 00:00:00 2001 From: Eric Neilsen Date: Thu, 2 Apr 2026 14:30:33 -0700 Subject: [PATCH 4/6] mollify type checker --- schedview/plot/visit_skymaps.py | 36 ++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/schedview/plot/visit_skymaps.py b/schedview/plot/visit_skymaps.py index 37b8e939..79c6e280 100644 --- a/schedview/plot/visit_skymaps.py +++ b/schedview/plot/visit_skymaps.py @@ -10,7 +10,7 @@ import warnings from types import MethodType -from typing import Any, Callable, Dict, List, Optional, Self, SupportsFloat, Tuple, cast +from typing import Any, Callable, Dict, List, Optional, Self, Sequence, SupportsFloat, Tuple, cast import bokeh import bokeh.layouts @@ -23,7 +23,11 @@ import pandas as pd from astropy.coordinates import SkyCoord, get_body from astropy.time import Time -from bokeh.models import UIElement +from bokeh.models.sources import DataSource +from bokeh.models.ui.ui_element import UIElement +from bokeh.models.widgets.buttons import Button, Toggle +from bokeh.models.widgets.groups import RadioButtonGroup +from bokeh.models.widgets.inputs import Select from uranography.api import ( ArmillarySphere, Planisphere, @@ -215,7 +219,7 @@ def __init__( camera_perimeter if camera_perimeter is not None else LsstCameraFootprintPerimeter() ) - self.visits_ds = {} + self.visits_ds: dict[str, DataSource] = {} self.visit_patches_added = False self._add_mjd_slider(**mjd_slider_kwargs) self.body_ds: Dict[str, bokeh.models.ColumnDataSource] = {} @@ -320,7 +324,7 @@ def add_visit_patches(self, visits: pd.DataFrame | None = None, **kwargs: Any) - mjd=band_visits[self.mjd_column].values, ) - patches_kwargs = {"fill_color": self.visit_fill_colors[band]} + patches_kwargs: dict = {"fill_color": self.visit_fill_colors[band]} patches_kwargs.update(DEFAULT_VISIT_PATCHES_KWARGS) patches_kwargs.update(kwargs) @@ -713,6 +717,7 @@ def add_body(self, body: str, size: float, color: str, alpha: float, time_step: return result """ + mjds: Sequence if time_step > self.mjd_slider.end - self.mjd_slider.start: mjds = [(self.mjd_slider.end + self.mjd_slider.start) / 2] else: @@ -720,7 +725,7 @@ def add_body(self, body: str, size: float, color: str, alpha: float, time_step: end_mjd: float = self.mjd_slider.end + time_step first_mjd: float = start_mjd + time_step / 2 last_mjd: float = end_mjd + time_step / 2 - mjds = np.arange(first_mjd, last_mjd, time_step) + mjds = np.arange(first_mjd, last_mjd, time_step).tolist() ap_times = Time(mjds, format="mjd", scale="utc") body_coords_all_times = get_body(body, ap_times) @@ -972,14 +977,15 @@ def add_footprint_outlines( footprint_polygons = footprint_polygons.reorder_levels(["region", "loop"]).copy() outside = "" + palette: tuple[str, ...] if colormap is None: regions = [ r for r in footprint_polygons.index.get_level_values("region").unique() if r != outside ] if len(regions) == 1: - palette = ["black"] + palette = ("black",) elif len(regions) == 2: - palette = ["black", "darkgray"] + palette = ("black", "darkgray") else: # Try palettes from the "colorblind" section in the bokeh docs try: @@ -989,7 +995,7 @@ def add_footprint_outlines( colormap = {r: c for r, c in zip(regions, palette)} - footprint_regions = {} + footprint_regions: dict[str, dict] = {} for region_index in set(footprint_polygons.index.values.tolist()): assert isinstance(region_index, tuple) region_name, loop_id = region_index @@ -1201,9 +1207,7 @@ def add_alt_visits_selector( (str(i), str(label)) for i, label in visit_set_labels.items() ] default_value = alt_options[0][0] - alt_visits_selector = bokeh.models.Select( - value=default_value, options=alt_options, name="alt_visits_selector" - ) + alt_visits_selector = Select(value=default_value, options=alt_options, name="alt_visits_selector") self.ref_map.controls["alt_visits_selector"] = alt_visits_selector transform_args = { @@ -1263,7 +1267,7 @@ def add_play_controls(self, speed: int = 100, **kwargs) -> Self: Default is 100. **kwargs Additional keyword arguments passed to the underlying - `bokeh.models.Toggle` constructor. + `Toggle` constructor. Returns ------- @@ -1279,7 +1283,7 @@ def add_play_controls(self, speed: int = 100, **kwargs) -> Self: * While playing, the RA and decl sliders are disabled to prevent conflicts with the automatic MJD updates. """ - play_toggle = bokeh.models.Toggle(label="\u23f5 Play", button_type="primary", active=True, **kwargs) + play_toggle = Toggle(label="\u23f5 Play", button_type="primary", active=True, **kwargs) play_callback_code = r""" const key = 'bokeh_play_timer_' + play.id; @@ -1343,7 +1347,7 @@ def add_zenith_button(self, **kwargs: Any) -> Self: self : `VisitMapBuilder` Returns self to enable method chaining. """ - button = bokeh.models.Button(label="Center zenith", **kwargs) + button = Button(label="Center zenith", **kwargs) code = """ if (alt != null) { @@ -1426,9 +1430,9 @@ def add_coord_sys_selector(self, **kwargs: Any) -> Self: decl.visible = true } """ - rb_kwargs = {"name": "Coordinate system", "labels": labels, "active": 0} + rb_kwargs: dict[str, Any] = {"name": "Coordinate system", "labels": labels, "active": 0} rb_kwargs.update(kwargs) - coordsys = bokeh.models.RadioButtonGroup(**rb_kwargs) + coordsys = RadioButtonGroup(**rb_kwargs) coordsys.js_on_change("active", bokeh.models.CustomJS(args=args, code=code)) self.ref_map.controls["coordsys"] = coordsys return self From 0849c81d360b3811a881b76b2beca339e9041a87 Mon Sep 17 00:00:00 2001 From: Eric Neilsen Date: Thu, 2 Apr 2026 14:31:35 -0700 Subject: [PATCH 5/6] shorten coord sys labels --- schedview/plot/visit_skymaps.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/schedview/plot/visit_skymaps.py b/schedview/plot/visit_skymaps.py index 79c6e280..e153639f 100644 --- a/schedview/plot/visit_skymaps.py +++ b/schedview/plot/visit_skymaps.py @@ -1404,8 +1404,8 @@ def add_coord_sys_selector(self, **kwargs: Any) -> Self: self.ref_map.controls["az"].visible = False labels = [ - "sliders in R.A., decl; north equatorial pole directly above center", - "sliders in alt, az; zenith directly above center", + "sliders in R.A., decl", + "sliders in alt, az", ] args = { "alt": self.ref_map.controls["alt"], From 8e5b960f76184fc63af8cb34aa9abab21e1d04f7 Mon Sep 17 00:00:00 2001 From: Eric Neilsen Date: Thu, 2 Apr 2026 14:59:45 -0700 Subject: [PATCH 6/6] Update visitmaps example notebook --- notebooks/visit_skymaps.ipynb | 26 +++++++++----------------- 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/notebooks/visit_skymaps.ipynb b/notebooks/visit_skymaps.ipynb index ed76c134..2fee5721 100644 --- a/notebooks/visit_skymaps.ipynb +++ b/notebooks/visit_skymaps.ipynb @@ -221,9 +221,7 @@ "id": "18", "metadata": {}, "source": [ - "## Example with many elements\n", - "\n", - "Methods can be called to provide additional data, features and controls:" + "## Example with all the bells and whistles and \"nightsum\" controls layout" ] }, { @@ -239,11 +237,10 @@ " mjd=visits[\"observationStartMJD\"].max(),\n", " map_classes=[uranography.armillary.ArmillarySphere, uranography.planisphere.Planisphere],\n", " figure_kwargs={\"match_aspect\": True},\n", + " mjd_slider_kwargs={'start': visits.observationStartMJD.min(), 'end': visits.observationStartMJD.max()}\n", " )\n", " .add_visit_patches()\n", " .add_footprint_outlines(footprint_regions)\n", - " .hide_horizon_sliders()\n", - " .make_up_north()\n", " .add_eq_sliders()\n", " .add_graticules()\n", " .add_ecliptic()\n", @@ -257,9 +254,12 @@ " .add_horizon()\n", " .add_horizon(zd=70, color=\"red\")\n", " .add_hovertext()\n", + " .add_play_controls()\n", + " .add_zenith_button()\n", + " .add_coord_sys_selector()\n", ")\n", - "viewable = builder.build()\n", - "bokeh.io.show(viewable)" + "vmap = builder.build(layout=\"nightsum\")\n", + "bokeh.io.show(vmap)" ] }, { @@ -543,21 +543,13 @@ "viewable = builder.build()\n", "bokeh.io.show(viewable)" ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "26", - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { "kernelspec": { - "display_name": "ehn_devel", + "display_name": "LSST", "language": "python", - "name": "ehn_devel" + "name": "lsst" }, "language_info": { "codemirror_mode": {