atkat12
atkat12

Reputation: 4160

iPyWidget with date slider?

I am wondering if there's an easy way to build an iPyWidget with a datetime slider. Right now it is easy to slide over integer or floating point ranges (e.g. numbers 1-10, decimals 0.01, 0.02, ...).

I imagine you could convert dates to floats or integers, build some sort of slider using these, and then convert back to dates for the display labels on the slider. However, this seems clunky. Does anyone have a smoother solution?

Upvotes: 17

Views: 21590

Answers (3)

sweetdream
sweetdream

Reputation: 1419

Based on the code from fdorssers, I converted it into a class and share it in case it can be useful for someone.

Enjoy :-)


import pandas as pd
import ipywidgets as widgets
from IPython.display import display
from datetime import datetime

class DateRangeSelector(object):
    def __init__(self, start, end, freq="D", fmt="%d %b %Y"):
        # Store the format string
        self.fmt = fmt
        
        # Create date range
        self.dates = pd.date_range(start, end, freq=freq)
        
        # First and last dates are selected by default
        initial_selection = (0, len(self.dates) - 1)
        
        # Define the date range slider
        self.date_range_selector = widgets.SelectionRangeSlider(
            options=self.dates,
            index=initial_selection,
            continuous_update=False,
            readout=False
        )
    
        # Define the display with formatted dates
        initial_text = (
            f"<b>{self.dates[initial_selection[0]].strftime(self.fmt)} - "
            f"{self.dates[initial_selection[1]].strftime(self.fmt)}</b>"
        )
        self.w_readout = widgets.HTML(value=initial_text)
    
        # Create the HBox layout
        self.date_range_widget = widgets.HBox([
            self.date_range_selector, 
            self.w_readout
        ])
    
        # Set up the interactive output
        widgets.interactive_output(
            self.callback, 
            {"dts": self.date_range_selector}
        )
        
    def callback(self, dts):
        formatted_start = dts[0].strftime(self.fmt)
        formatted_end = dts[1].strftime(self.fmt)
        self.w_readout.value = f"<b>{formatted_start} - {formatted_end}</b>"
        
    def _ipython_display_(self):
        display(self.date_range_widget)
        
    @property
    def value(self):
        """Return the currently selected dates"""
        return (
            self.date_range_selector.value[0],
            self.date_range_selector.value[1]
        )

Upvotes: 0

fdorssers
fdorssers

Reputation: 719

A general purpose range slider widget called SelectionRangeSlider has been added to ipywidgets in May 2017. This slider can be used for all sorts of data, including dates.

The code sample below creates a range slider allowing the user to select a date range between April 24 and May 24.

import ipywidgets as widgets
import pandas as pd
from datetime import datetime

start_date = datetime(2018, 4, 24)
end_date = datetime(2018, 5, 24)

dates = pd.date_range(start_date, end_date, freq='D')

options = [(date.strftime(' %d %b %Y '), date) for date in dates]
index = (0, len(options)-1)

selection_range_slider = widgets.SelectionRangeSlider(
    options=options,
    index=index,
    description='Dates',
    orientation='horizontal',
    layout={'width': '500px'}
)

selection_range_slider

Date range slider with full month selection

Date range slider with partial month selection

This slider can easily be combined with the interact functionality from ipywidgets. In that case the widget returns a tuple consisting of the selected upper and lower range.

def print_date_range(date_range):
    print(date_range)

widgets.interact(
    print_date_range,
    date_range=selection_range_slider
);

The two outputs for the previously selected ranges:

(Timestamp('2018-04-24 00:00:00', freq='D'), Timestamp('2018-05-24 00:00:00', freq='D'))
(Timestamp('2018-04-30 00:00:00', freq='D'), Timestamp('2018-05-18 00:00:00', freq='D'))

Upvotes: 28

sweetdream
sweetdream

Reputation: 1419

I had the same issue recently. I had to write my own class to do a daterange picker. Here is my code:

import pandas as pd
import ipywidgets as widgets
from IPython.display import display

class DateRangePicker(object):
    def __init__(self,start,end,freq='D',fmt='%Y-%m-%d'):
        """
        Parameters
        ----------
        start : string or datetime-like
            Left bound of the period
        end : string or datetime-like
            Left bound of the period
        freq : string or pandas.DateOffset, default='D'
            Frequency strings can have multiples, e.g. '5H' 
        fmt : string, defauly = '%Y-%m-%d'
            Format to use to display the selected period

        """
        self.date_range=pd.date_range(start=start,end=end,freq=freq)
        options = [(item.strftime(fmt),item) for item in self.date_range]
        self.slider_start = widgets.SelectionSlider(
            description='start',
            options=options,
            continuous_update=False
        )
        self.slider_end = widgets.SelectionSlider(
            description='end',
            options=options,
            continuous_update=False,
            value=options[-1][1]
        )

        self.slider_start.on_trait_change(self.slider_start_changed, 'value')
        self.slider_end.on_trait_change(self.slider_end_changed, 'value')

        self.widget = widgets.Box(children=[self.slider_start,self.slider_end])

    def slider_start_changed(self,key,value):
        self.slider_end.value=max(self.slider_start.value,self.slider_end.value)
        self._observe(start=self.slider_start.value,end=self.slider_end.value)

    def slider_end_changed(self,key,value):
        self.slider_start.value=min(self.slider_start.value,self.slider_end.value)
        self._observe(start=self.slider_start.value,end=self.slider_end.value)

    def display(self):
        display(self.slider_start,self.slider_end)

    def _observe(self,**kwargs):
        if hasattr(self,'observe'):
            self.observe(**kwargs)

def fct(start,end):
    print start,end

Using it is relatively straightforward:

w=DateRangePicker(start='2016-08-02',end="2016-09-02",freq='D',fmt='%Y-%m-%d')
w.observe=fct
w.display()

Enjoy ;-)

Upvotes: 11

Related Questions