Issue
EDIT: Re-written for clarity Sept. 8 2019 1330 UTC
In a Jupyter notebook, I want to explore dates chosen by a date picker widget. The way of doing this I already knew was to use @interact
and make the datepicker widget an argument to the interact function, like this:
def explore_dates(d1=widgets.DatePicker(value=pd.to_datetime('2018-07-10', format='%Y-%m-%d'), description='Date')):
But I need buttons "Next Holiday" and "Previous Holiday" to move from the selected date to the next holiday. I already have an array of holidays stored as datetime.datetime objects.
So I tried adding the button objects to the arguments to the @interact
function, like this:
@interact
def explore_dates(d1=widgets.DatePicker(value=pd.to_datetime('2018-07-10', format='%Y-%m-%d'), description='Date'),
prev_button = widgets.Button(description="Prev Holiday"),
next_button = widgets.Button(description="Next Holiday")):
That didn't work. Error says the line with the button widget "cannot be transformed into a widget". Hmm, I thought it already was a widget...
My next thought was to put the button code inside the function, rather than as an argument:
def explore_dates(d1=widgets.DatePicker(value=pd.to_datetime('2018-07-10', format='%Y-%m-%d'), description='Date')):
prev_button = widgets.Button(description="Prev Holiday")
next_button = widgets.Button(description="Next Holiday")
prev_button.on_click(prev_holiday_callback)
next_button.on_click(next_holiday_callback)
box=widgets.HBox([prev_button,next_button])
display(box)
That got the buttons on the screen and I can push them and my callback routines run. But when the datepicker is set up (per above) as an argument to @interact
, it appears that it is no longer possible to reset its date using its .value attribute. (See code above) trying to set d1.value results in an error.
I suppose I could get rid of @interact
entirely, and just put the datepicker and both buttons into the mainline code. But I don't know how to reproduce the function of @interact
at that point. Short of an infinite loop that waits for a click or observe event to come from one of the widgets, other than @interact
, I don't know how to tell Python/Jupyter "hey, just chill out until a widget event causes a callback to wake up the code".
In case it's helpful, here are my callback routines for the buttons.
def button_click_common():
global button_flag
global button_date
global holidays
for i in range(len(holidays)):
if(holidays[i]<button_date): # haven't passed the prior holiday yet
continue
# Now holidays[i] has to either be same as or after button_date
prior_holiday = i-1 # prior holiday is the one we just passed that was prior
next_holiday = i # assume at first we are between dates
if holidays[i]==button_date:
next_holiday +=1 # if we were already on a holiday date, next becomes the following one
return prior_holiday, next_holiday
def prev_holiday_callback(_):
global button_flag
global button_date
global holidays
prior_holiday,_ = button_click_common()
button_date = holidays[prior_holiday]
button_flag = True
def next_holiday_callback(_):
global button_flag
global button_date
global holidays
_,next_holiday = button_click_common()
button_date = holidays[next_holiday]
button_flag = True
The idea here is that the callback updates button_date to the date of the next holiday, and sets the flag button_flag. Then code (not shown) in the explore_dates function was going to test the flag. But I can't figure out how to update the datepicker widget with the new date when it is defined as an argument to the @interact
function.
Feels like I'm going about this wrong. Any advice or guidance welcome...
Thanks in advance!
Solution
I'm not sure I understand the exact aim you're trying to achieve, but interact works best with functions that take numeric inputs, rather than discrete inputs or python objects like dates.
I put together some code that would illustrate how I would use the forward and back buttons to update a datepicker, it's probably not exactly what you are looking for, but maybe you can describe the changes from there?
import ipywidgets as ipyw
from datetime import date
idx = 0
date_picker = ipyw.DatePicker()
date_picker.value = month_starts[idx]
start_button = ipyw.Button(description='Prev')
end_button = ipyw.Button(description='Next')
display(date_picker, start_button, end_button)
def change_date(button):
global idx
if button.description=='Prev':
if idx-1<0:
pass
else:
date_picker.value = month_starts[idx-1]
idx -= 1
elif button.description=='Next':
if idx+1>=len(month_starts):
pass
else:
date_picker.value = month_starts[idx+1]
idx += 1
start_button.on_click(change_date)
end_button.on_click(change_date)
Answered By - ac24
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.