Skip to content

Commit 97c7d0a

Browse files
committed
feat: Enhance map label functionality in GeoAnimatorManager with customizable options
1 parent f195939 commit 97c7d0a

2 files changed

Lines changed: 228 additions & 9 deletions

File tree

dvue/animator/multi_ui.py

Lines changed: 114 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
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
# ------------------------------------------------------------------

dvue/animator/ui.py

Lines changed: 114 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -625,8 +625,11 @@ def __init__(
625625
self._buffer_chunk_size = buffer_chunk_size
626626
if map_label_spec is None:
627627
_corner = map_label_corner if map_label_corner in _VALID_CORNERS else "bottom_left"
628-
map_label_spec = MapLabelSpec(corner=_corner)
628+
map_label_spec = MapLabelSpec(corner=_corner, visible=False)
629629
self._map_label_spec = map_label_spec
630+
# Keep map dimensions for corner repositioning from the UI panel.
631+
self._map_width = map_width
632+
self._map_height = map_height
630633
self._reader = self._setup_reader(initial_transform)
631634
self._geo_id_column = geo_id_column
632635
self._title = title
@@ -977,9 +980,37 @@ def __init__(
977980
value=_default_clip_km, step=0.5,
978981
sizing_mode="stretch_width", visible=False,
979982
)
980-
# Map corner label toggle — initial state from spec.
983+
# Map label panel — full controls in a dedicated sidebar card.
984+
# _map_label_check is the primary visible toggle (referenced by
985+
# _apply_config_to_manager); the remaining widgets configure the spec.
986+
_spec_now = self._map_label_spec
981987
self._map_label_check = pn.widgets.Checkbox(
982-
name="Show map label", value=self._map_label_spec.visible,
988+
name="Show label", value=_spec_now.visible,
989+
sizing_mode="stretch_width",
990+
)
991+
self._map_label_corner_select = pn.widgets.Select(
992+
name="Corner",
993+
options=list(_VALID_CORNERS),
994+
value=_spec_now.corner if _spec_now.corner in _VALID_CORNERS else "bottom_left",
995+
sizing_mode="stretch_width",
996+
)
997+
self._map_label_template_input = pn.widgets.TextInput(
998+
name="Template",
999+
value=_spec_now.template,
1000+
sizing_mode="stretch_width",
1001+
)
1002+
self._map_label_start_input = pn.widgets.TextInput(
1003+
name="Start time (YYYY-MM-DD)",
1004+
value=(
1005+
_spec_now.start_time.strftime("%Y-%m-%d %H:%M")
1006+
if _spec_now.start_time is not None else ""
1007+
),
1008+
placeholder="e.g. 2020-10-01",
1009+
sizing_mode="stretch_width",
1010+
)
1011+
self._map_label_font_input = pn.widgets.TextInput(
1012+
name="Font size",
1013+
value=_spec_now.font_size,
9831014
sizing_mode="stretch_width",
9841015
)
9851016
# Channel and basemap opacity sliders (0 = invisible, 100 = fully opaque)
@@ -1024,7 +1055,6 @@ def __init__(
10241055
*_size_widgets,
10251056
self._channels_alpha_slider,
10261057
self._basemap_alpha_slider,
1027-
self._map_label_check,
10281058
title="Appearance", collapsed=False,
10291059
sizing_mode="stretch_width",
10301060
)
@@ -1042,6 +1072,15 @@ def __init__(
10421072
sizing_mode="stretch_width",
10431073
)
10441074
self._contour_card = _contour_card
1075+
_map_label_card = pn.Card(
1076+
self._map_label_check,
1077+
self._map_label_corner_select,
1078+
self._map_label_template_input,
1079+
self._map_label_start_input,
1080+
self._map_label_font_input,
1081+
title="Map label", collapsed=True,
1082+
sizing_mode="stretch_width",
1083+
)
10451084
_optional_cards: list = []
10461085
if self._transform_options:
10471086
_optional_cards.append(pn.Card(
@@ -1090,6 +1129,7 @@ def __init__(
10901129
pn.layout.Divider(margin=(4, 0, 4, 0)),
10911130
_appearance_card,
10921131
_contour_card,
1132+
_map_label_card,
10931133
*_optional_cards,
10941134
_save_card,
10951135
sizing_mode="stretch_width",
@@ -1125,6 +1165,10 @@ def __init__(
11251165
self._channels_alpha_slider.param.watch(self._on_channels_alpha_change, "value")
11261166
self._basemap_alpha_slider.param.watch(self._on_basemap_alpha_change, "value")
11271167
self._map_label_check.param.watch(self._on_map_label_toggle, "value")
1168+
self._map_label_corner_select.param.watch(self._on_map_label_corner_change, "value")
1169+
self._map_label_template_input.param.watch(self._on_map_label_template_change, "value")
1170+
self._map_label_start_input.param.watch(self._on_map_label_start_time_change, "value")
1171+
self._map_label_font_input.param.watch(self._on_map_label_font_change, "value")
11281172
if self._transform_options:
11291173
self._transform_select.param.watch(self._on_transform_change, "value")
11301174
if x2_callback is not None:
@@ -1707,6 +1751,72 @@ def _on_map_label_toggle(self, event: param.parameterized.Event) -> None:
17071751
self._map_label_spec.visible = visible
17081752
self._corner_label.visible = visible
17091753

1754+
def _on_map_label_corner_change(self, event: param.parameterized.Event) -> None:
1755+
"""Reposition the corner label when the user picks a different corner."""
1756+
self._map_label_spec.corner = event.new
1757+
lx, ly, la, lb = _label_corner_pos(event.new, self._map_width, self._map_height)
1758+
1759+
def _apply() -> None:
1760+
self._corner_label.x = lx
1761+
self._corner_label.y = ly
1762+
self._corner_label.text_align = la
1763+
self._corner_label.text_baseline = lb
1764+
1765+
doc = self._bk_figure.document
1766+
if doc is not None:
1767+
doc.add_next_tick_callback(_apply)
1768+
else:
1769+
_apply()
1770+
1771+
def _on_map_label_template_change(self, event: param.parameterized.Event) -> None:
1772+
"""Apply an updated format template and re-render the current frame."""
1773+
self._map_label_spec.template = event.new
1774+
self._refresh_map_label()
1775+
1776+
def _on_map_label_start_time_change(self, event: param.parameterized.Event) -> None:
1777+
"""Parse the start-time text and re-render the current frame label."""
1778+
raw = (event.new or "").strip()
1779+
if raw:
1780+
try:
1781+
self._map_label_spec.start_time = pd.Timestamp(raw)
1782+
except Exception:
1783+
return # invalid input — leave spec unchanged
1784+
else:
1785+
self._map_label_spec.start_time = None
1786+
self._refresh_map_label()
1787+
1788+
def _on_map_label_font_change(self, event: param.parameterized.Event) -> None:
1789+
"""Apply an updated font-size string to the Bokeh label."""
1790+
font = event.new or "14px"
1791+
self._map_label_spec.font_size = font
1792+
1793+
def _apply() -> None:
1794+
self._corner_label.text_font_size = font
1795+
1796+
doc = self._bk_figure.document
1797+
if doc is not None:
1798+
doc.add_next_tick_callback(_apply)
1799+
else:
1800+
_apply()
1801+
1802+
def _refresh_map_label(self) -> None:
1803+
"""Re-evaluate the label template at the current playback position."""
1804+
idx = self._time_slider.value
1805+
ti = self._reader.time_index
1806+
if not (0 <= idx < len(ti)):
1807+
return
1808+
ts = ti[idx]
1809+
new_text = self._map_label_spec.format(ts, idx)
1810+
1811+
def _apply() -> None:
1812+
self._corner_label.text = new_text
1813+
1814+
doc = self._bk_figure.document
1815+
if doc is not None:
1816+
doc.add_next_tick_callback(_apply)
1817+
else:
1818+
_apply()
1819+
17101820
def _on_transform_change(self, event: param.parameterized.Event) -> None:
17111821
"""Apply a new time-domain transform; preserve current playback position.
17121822

0 commit comments

Comments
 (0)