5555 _level_colors ,
5656 _nice_decimal_places ,
5757 _format_level ,
58+ _label_corner_pos ,
5859 TransformSpec ,
5960 StreamingTransformedSlicingReader ,
6061 MapLabelSpec ,
@@ -241,7 +242,6 @@ def _build_bokeh_map(
241242 # container, not attached to map data coordinates.
242243 _spec = map_label_spec if map_label_spec is not None else MapLabelSpec ()
243244 _corner = _spec .corner if _spec .corner in _VALID_CORNERS else "bottom_left"
244- from .ui import _label_corner_pos
245245 _lx , _ly , _la , _lb = _label_corner_pos (_corner , map_width , map_height )
246246 corner_label = Label (
247247 x = _lx , y = _ly ,
@@ -355,7 +355,7 @@ def __init__(
355355 # ----------------------------------------------------------------
356356 if map_label_spec is None :
357357 _corner = map_label_corner if map_label_corner in _VALID_CORNERS else "bottom_left"
358- map_label_spec = MapLabelSpec (corner = _corner )
358+ map_label_spec = MapLabelSpec (corner = _corner , visible = False )
359359 self ._map_label_spec = map_label_spec
360360 self ._base_reader_a = reader_a
361361 self ._base_reader_b = reader_b
@@ -638,9 +638,35 @@ def __init__(
638638 sizing_mode = "stretch_width" , visible = False ,
639639 )
640640 # Show / hide toggles for channels and basemap (all three figures).
641- # Map corner label toggle — initial state from spec.
641+ # Map label panel — full controls in a dedicated sidebar card.
642+ _spec_now = self ._map_label_spec
642643 self ._map_label_check = pn .widgets .Checkbox (
643- name = "Show map label" , value = self ._map_label_spec .visible ,
644+ name = "Show label" , value = _spec_now .visible ,
645+ sizing_mode = "stretch_width" ,
646+ )
647+ self ._map_label_corner_select = pn .widgets .Select (
648+ name = "Corner" ,
649+ options = list (_VALID_CORNERS ),
650+ value = _spec_now .corner if _spec_now .corner in _VALID_CORNERS else "bottom_left" ,
651+ sizing_mode = "stretch_width" ,
652+ )
653+ self ._map_label_template_input = pn .widgets .TextInput (
654+ name = "Template" ,
655+ value = _spec_now .template ,
656+ sizing_mode = "stretch_width" ,
657+ )
658+ self ._map_label_start_input = pn .widgets .TextInput (
659+ name = "Start time (YYYY-MM-DD)" ,
660+ value = (
661+ _spec_now .start_time .strftime ("%Y-%m-%d %H:%M" )
662+ if _spec_now .start_time is not None else ""
663+ ),
664+ placeholder = "e.g. 2020-10-01" ,
665+ sizing_mode = "stretch_width" ,
666+ )
667+ self ._map_label_font_input = pn .widgets .TextInput (
668+ name = "Font size" ,
669+ value = _spec_now .font_size ,
644670 sizing_mode = "stretch_width" ,
645671 )
646672 # Channel and basemap opacity sliders (0 = invisible, 100 = fully opaque)
@@ -662,7 +688,6 @@ def __init__(
662688 self ._size_slider ,
663689 self ._channels_alpha_slider ,
664690 self ._basemap_alpha_slider ,
665- self ._map_label_check ,
666691 title = "Appearance" , collapsed = False ,
667692 sizing_mode = "stretch_width" ,
668693 )
@@ -687,6 +712,15 @@ def __init__(
687712 sizing_mode = "stretch_width" ,
688713 )
689714 self ._contour_card = _contour_card
715+ _map_label_card = pn .Card (
716+ self ._map_label_check ,
717+ self ._map_label_corner_select ,
718+ self ._map_label_template_input ,
719+ self ._map_label_start_input ,
720+ self ._map_label_font_input ,
721+ title = "Map label" , collapsed = True ,
722+ sizing_mode = "stretch_width" ,
723+ )
690724 _optional_cards : list = []
691725 if self ._transform_options :
692726 _optional_cards .append (pn .Card (
@@ -729,6 +763,7 @@ def __init__(
729763 _appearance_card ,
730764 _diff_card ,
731765 _contour_card ,
766+ _map_label_card ,
732767 * _optional_cards ,
733768 _save_card ,
734769 sizing_mode = "stretch_width" ,
@@ -781,6 +816,10 @@ def __init__(
781816 self ._channels_alpha_slider .param .watch (self ._on_channels_alpha_change , "value" )
782817 self ._basemap_alpha_slider .param .watch (self ._on_basemap_alpha_change , "value" )
783818 self ._map_label_check .param .watch (self ._on_map_label_toggle , "value" )
819+ self ._map_label_corner_select .param .watch (self ._on_map_label_corner_change , "value" )
820+ self ._map_label_template_input .param .watch (self ._on_map_label_template_change , "value" )
821+ self ._map_label_start_input .param .watch (self ._on_map_label_start_time_change , "value" )
822+ self ._map_label_font_input .param .watch (self ._on_map_label_font_change , "value" )
784823 if self ._transform_options :
785824 self ._transform_select .param .watch (self ._on_transform_change , "value" )
786825 self ._save_config_btn .on_click (self ._on_save_config )
@@ -1420,6 +1459,76 @@ def _apply():
14201459 else :
14211460 _apply ()
14221461
1462+ def _on_map_label_corner_change (self , event : param .parameterized .Event ) -> None :
1463+ """Reposition corner labels on all three maps when the corner changes."""
1464+ self ._map_label_spec .corner = event .new
1465+ lx , ly , la , lb = _label_corner_pos (event .new , 500 , self ._map_height )
1466+
1467+ def _apply () -> None :
1468+ for lbl in (self ._corner_label_a , self ._corner_label_b , self ._corner_label_diff ):
1469+ lbl .x = lx
1470+ lbl .y = ly
1471+ lbl .text_align = la
1472+ lbl .text_baseline = lb
1473+
1474+ doc = self ._active_doc ()
1475+ if doc is not None :
1476+ doc .add_next_tick_callback (_apply )
1477+ else :
1478+ _apply ()
1479+
1480+ def _on_map_label_template_change (self , event : param .parameterized .Event ) -> None :
1481+ """Apply an updated format template and re-render the current frame label."""
1482+ self ._map_label_spec .template = event .new
1483+ self ._refresh_map_label ()
1484+
1485+ def _on_map_label_start_time_change (self , event : param .parameterized .Event ) -> None :
1486+ """Parse the start-time text and re-render the current frame labels."""
1487+ raw = (event .new or "" ).strip ()
1488+ if raw :
1489+ try :
1490+ self ._map_label_spec .start_time = pd .Timestamp (raw )
1491+ except Exception :
1492+ return
1493+ else :
1494+ self ._map_label_spec .start_time = None
1495+ self ._refresh_map_label ()
1496+
1497+ def _on_map_label_font_change (self , event : param .parameterized .Event ) -> None :
1498+ """Apply an updated font-size string to all three corner labels."""
1499+ font = event .new or "14px"
1500+ self ._map_label_spec .font_size = font
1501+
1502+ def _apply () -> None :
1503+ for lbl in (self ._corner_label_a , self ._corner_label_b , self ._corner_label_diff ):
1504+ lbl .text_font_size = font
1505+
1506+ doc = self ._active_doc ()
1507+ if doc is not None :
1508+ doc .add_next_tick_callback (_apply )
1509+ else :
1510+ _apply ()
1511+
1512+ def _refresh_map_label (self ) -> None :
1513+ """Re-evaluate the label template at the current playback position."""
1514+ idx = self ._time_slider .value
1515+ ti = self ._reader_a .time_index
1516+ if not (0 <= idx < len (ti )):
1517+ return
1518+ ts = ti [idx ]
1519+ new_text = self ._map_label_spec .format (ts , idx )
1520+
1521+ def _apply () -> None :
1522+ self ._corner_label_a .text = new_text
1523+ self ._corner_label_b .text = new_text
1524+ self ._corner_label_diff .text = new_text
1525+
1526+ doc = self ._active_doc ()
1527+ if doc is not None :
1528+ doc .add_next_tick_callback (_apply )
1529+ else :
1530+ _apply ()
1531+
14231532 # ------------------------------------------------------------------
14241533 # Callbacks — diff toggle
14251534 # ------------------------------------------------------------------
0 commit comments