diff --git a/notebooks/utils.py b/notebooks/utils.py index 2d9e491..0c78f5e 100644 --- a/notebooks/utils.py +++ b/notebooks/utils.py @@ -14,6 +14,7 @@ import pandas as pd import rasterio from catboost import CatBoostClassifier, Pool +from IPython.core.display import HTML as core_HTML from IPython.display import display from loguru import logger from matplotlib.patches import Rectangle @@ -30,6 +31,7 @@ logging.getLogger("rasterio").setLevel(logging.ERROR) +############# DEFINING TEMPORAL EXTENT ############# class date_slider: """Class that provides a slider for selecting a processing period. The processing period is fixed in length, amounting to one year. @@ -38,40 +40,121 @@ class date_slider: def __init__(self, start_date=datetime(2018, 1, 1), end_date=datetime(2023, 12, 1)): - self.start_date = start_date - self.end_date = end_date - + # Define the slider dates = pd.date_range(start_date, end_date, freq="MS") options = [(date.strftime("%b %Y"), date) for date in dates] self.interval_slider = widgets.SelectionRangeSlider( options=options, index=(0, 11), # Default to a 11-month interval orientation="horizontal", + description="", continuous_update=False, - readout=True, behaviour="drag", - layout={"width": "90%", "height": "100px", "margin": "0 auto 0 auto"}, style={ "handle_color": "dodgerblue", }, + layout=widgets.Layout( + width="600px", + margin="0 0 0 10px", + ), + readout=False, ) - self.selected_range = [ - pd.to_datetime(start_date), - pd.to_datetime(start_date) + pd.DateOffset(months=12) - timedelta(days=1), - ] - def on_slider_change(self, change): - start, end = change["new"] - # keep the interval fixed - expected_end = start + pd.DateOffset(months=11) - if end != expected_end: - end = start + pd.DateOffset(months=11) - self.interval_slider.value = (start, end) - self.selected_range = (start, end + pd.DateOffset(months=1) - timedelta(days=1)) + # Define the HTML text widget for the selected range and focus time + initial_range = [ + (pd.to_datetime(start_date)).strftime("%d %b %Y"), + ( + pd.to_datetime(start_date) + + pd.DateOffset(months=12) + - timedelta(days=1) + ).strftime("%d %b %Y"), + ] + initial_focus_time = ( + pd.to_datetime(start_date) + pd.DateOffset(months=6) + ).strftime("%b %Y") + self.html_text = widgets.HTML( + value=f"Selected range: {initial_range[0]} - {initial_range[1]}
Focus time: {initial_focus_time}", + placeholder="HTML placeholder", + description="", + layout=widgets.Layout(justify_content="center", display="flex"), + ) - def show_slider(self): + # Attach slider observer self.interval_slider.observe(self.on_slider_change, names="value") + # Add custom CSS for the ticks + custom_css = """ + + """ + + # # Generate ticks + tick_dates = pd.date_range( + start_date, pd.to_datetime(end_date) + pd.DateOffset(months=1), freq="4MS" + ) + tick_labels = [date.strftime("%b %Y") for date in tick_dates] + n_labels = len(tick_labels) + ticks_html = "" + for i, label in enumerate(tick_labels): + position = (i / (n_labels - 1)) * 100 # Position as a percentage + ticks_html += f""" +
|
+
{label.split()[0]}
{label.split()[1]}
+ """ + + # HTML container for tick marks and labels + tick_marks_and_labels = widgets.HTML( + value=f""" +
+
+
+ {ticks_html} +
+
+
+ """ + ) + + # Combine slider and ticks using VBox + slider_with_ticks = widgets.VBox( + [self.interval_slider, tick_marks_and_labels], + layout=widgets.Layout( + width="640px", align_items="center", justify_content="center" + ), + ) + # Add description widget descr_widget = widgets.HTML( value=""" @@ -83,26 +166,54 @@ def show_slider(self): """ ) - # Arrange the text widget, interval slider, and tick widget using VBox - vbox_with_ticks = widgets.VBox( + # Arrange the description widget, interval slider, ticks and text widget in a VBox + vbox = widgets.VBox( [ descr_widget, - self.interval_slider, + slider_with_ticks, + self.html_text, ], - layout={"height": "150px"}, + layout=widgets.Layout( + align_items="center", justify_content="center", width="650px" + ), ) - display(vbox_with_ticks) + display(core_HTML(custom_css)) + display(vbox) + + def on_slider_change(self, change): + + start, end = change["new"] + + # keep the interval fixed + expected_end = start + pd.DateOffset(months=11) + if end != expected_end: + end = start + pd.DateOffset(months=11) + self.interval_slider.value = (start, end) + + # update the HTML text underneath the slider + range = [ + (pd.to_datetime(start)).strftime("%d %b %Y"), + ( + pd.to_datetime(start) + pd.DateOffset(months=12) - timedelta(days=1) + ).strftime("%d %b %Y"), + ] + focus_time = (start + pd.DateOffset(months=6)).strftime("%b %Y") + self.html_text.value = f"Selected range: {range[0]} - {range[1]}
Focus time: {focus_time}" def get_processing_period(self): - start = self.selected_range[0].strftime("%Y-%m-%d") - end = self.selected_range[1].strftime("%Y-%m-%d") + start = pd.to_datetime(self.interval_slider.value[0]) + end = start + pd.DateOffset(months=12) - timedelta(days=1) + + start = start.strftime("%Y-%m-%d") + end = end.strftime("%Y-%m-%d") logger.info(f"Selected processing period: {start} to {end}") return TemporalContext(start, end) +########################## def get_input(label): while True: modelname = input(f"Enter a short name for your {label} (don't use spaces): ") diff --git a/notebooks/worldcereal_v1_demo_custom_cropland.ipynb b/notebooks/worldcereal_v1_demo_custom_cropland.ipynb index a658109..58dc88b 100644 --- a/notebooks/worldcereal_v1_demo_custom_cropland.ipynb +++ b/notebooks/worldcereal_v1_demo_custom_cropland.ipynb @@ -284,8 +284,7 @@ "source": [ "from utils import date_slider\n", "\n", - "slider = date_slider()\n", - "slider.show_slider()" + "slider = date_slider()" ] }, { diff --git a/notebooks/worldcereal_v1_demo_custom_croptype.ipynb b/notebooks/worldcereal_v1_demo_custom_croptype.ipynb index 25c8eb4..85ecc7f 100644 --- a/notebooks/worldcereal_v1_demo_custom_croptype.ipynb +++ b/notebooks/worldcereal_v1_demo_custom_croptype.ipynb @@ -115,8 +115,7 @@ "source": [ "from utils import date_slider\n", "\n", - "slider = date_slider()\n", - "slider.show_slider()" + "slider = date_slider()" ] }, { diff --git a/notebooks/worldcereal_v1_demo_custom_croptype_extended.ipynb b/notebooks/worldcereal_v1_demo_custom_croptype_extended.ipynb index 9b88256..89ac9d2 100644 --- a/notebooks/worldcereal_v1_demo_custom_croptype_extended.ipynb +++ b/notebooks/worldcereal_v1_demo_custom_croptype_extended.ipynb @@ -115,8 +115,7 @@ "source": [ "from utils import date_slider\n", "\n", - "slider = date_slider()\n", - "slider.show_slider()" + "slider = date_slider()" ] }, { diff --git a/notebooks/worldcereal_v1_demo_default_cropland.ipynb b/notebooks/worldcereal_v1_demo_default_cropland.ipynb index 801c2e1..174bb14 100644 --- a/notebooks/worldcereal_v1_demo_default_cropland.ipynb +++ b/notebooks/worldcereal_v1_demo_default_cropland.ipynb @@ -135,8 +135,7 @@ "source": [ "from utils import date_slider\n", "\n", - "slider = date_slider()\n", - "slider.show_slider()" + "slider = date_slider()" ] }, { diff --git a/notebooks/worldcereal_v1_demo_default_cropland_extended.ipynb b/notebooks/worldcereal_v1_demo_default_cropland_extended.ipynb index 37f0d25..859eb89 100644 --- a/notebooks/worldcereal_v1_demo_default_cropland_extended.ipynb +++ b/notebooks/worldcereal_v1_demo_default_cropland_extended.ipynb @@ -135,8 +135,7 @@ "source": [ "from utils import date_slider\n", "\n", - "slider = date_slider()\n", - "slider.show_slider()" + "slider = date_slider()" ] }, {