【问题标题】:How to realize 1 month `step` in Bokeh DateRangeSlider?如何在 Bokeh DateRangeSlider 中实现 1 个月的“步骤”?
【发布时间】:2020-06-05 01:03:00
【问题描述】:

Bokeh DateRangeSlider 小部件的 step 属性需要 int 值,该值必须是以毫秒为单位的时间值。当步长设置为secondsminuteshoursdaysyears 时效果很好。但是,我需要滑块上的分辨率。

当步骤设置为 31 天时,它适用于开始日期,直到 3 月,而不是 1 March 我得到 4 March。然后显示值从每月 1 日开始的偏移越来越大。

我希望能够将两侧的滑块范围设置并显示为始终为每月的第一天,例如1 March1 April1 May1 June 等...就像在 DataFrame 中一样。

考虑以下代码,实现它的最佳方式是什么(可能使用 JS 回调)?

import pandas as pd
from bokeh.plotting import show
from bokeh.models import DateRangeSlider


data = {'date_start': ['201812', '201901', '201902', '201903', '201904', '201905', '201906', '201907', '201908', '201909', '201910', '201911'],  
        'date_end': [ '201901', '201902', '201903', '201904', '201905', '201906', '201907', '201908', '201909', '201910', '201911', '201912'], 
        'values' : [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]}
df = pd.DataFrame(data)

df['Start'] = pd.to_datetime(df['date_start'], format='%Y%m')
df['End'] = pd.to_datetime(df['date_end'], format='%Y%m')

start_date = df['Start'].min()
end_date = df['End'].max()

range_slider = DateRangeSlider(start=start_date, end=end_date, value=(start_date, end_date), step=31*24*60*60*1000, title="Date Range", callback_policy = 'mouseup', tooltips = False, width=600)

show(range_slider)import pandas as pd
from bokeh.plotting import show
from bokeh.models import DateRangeSlider


data = {'date_start': ['201812', '201901', '201902', '201903', '201904', '201905', '201906', '201907', '201908', '201909', '201910', '201911'],  
        'date_end': [ '201901', '201902', '201903', '201904', '201905', '201906', '201907', '201908', '201909', '201910', '201911', '201912'], 
        'values' : [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]}
df = pd.DataFrame(data)

df['Start'] = pd.to_datetime(df['date_start'], format='%Y%m')
df['End'] = pd.to_datetime(df['date_end'], format='%Y%m')

start_date = df['Start'].min()
end_date = df['End'].max()

range_slider = DateRangeSlider(start=start_date, end=end_date, value=(start_date, end_date), step=31*24*60*60*1000, title="Date Range", callback_policy = 'mouseup', tooltips = False, width=600)

show(range_slider)

【问题讨论】:

    标签: python date slider bokeh


    【解决方案1】:

    经过一番挣扎后,我想出了这个 JS 回调,它临时将步骤更改为 1 天,以便能够更正日期。它还临时更改范围,以便在恢复步骤时滑块手柄保持在其位置。远非完美但有效:

    import pandas as pd
    
    from bokeh.plotting import show
    from bokeh.models import CustomJS, DateRangeSlider
    
    data = {'date_start': ['201812', '201901', '201902', '201903', '201904', '201905', '201906', '201907', '201908', '201909', '201910', '201911'],  
            'date_end': [ '201901', '201902', '201903', '201904', '201905', '201906', '201907', '201908', '201909', '201910', '201911', '201912'], 
            'values' : [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]}
    df = pd.DataFrame(data)
    
    df['Start'] = pd.to_datetime(df['date_start'], format='%Y%m')
    df['End'] = pd.to_datetime(df['date_end'], format='%Y%m')
    
    start_date = df['Start'].min()
    end_date = df['End'].max()
    
    range_slider = DateRangeSlider(start=start_date, end=end_date, value=(start_date, end_date), step=31*24*60*60*1000, title="Date Range", callback_policy = 'mouseup', tooltips = False, width=600)
    
    code = '''
    
    
    console.log('start, end', cb_obj.start, cb_obj.end)
    
    for (i in cb_obj.value) {
        if (getDay(cb_obj.value[i]) != 1) {
            correctDate(day, i)
        }
    }
    
    function getDay(value) {
        date = new Date(value)
        str_date = date.toString()
        day = str_date.split(' ')[2]
    
        return Number(day)
    }
    
    function correctDate(day, side) {
        if (day < 15) {
            console.log('day < 15')
            difference = day - 1
            difference_milliseconds = -1 * difference*24*60*60*1000
        }
        else {  
            console.log('day >= 15')
            difference = 0
            new_day = -1
            while(new_day != 1) {
                difference_milliseconds = difference*24*60*60*1000
                new_date = new Date(cb_obj.value[0] + difference_milliseconds)
                new_day = Number(new_date.getDate())
    
                difference += 1
            }   
        }
    
        cb_obj.step = 1*24*60*60*1000 // set slider step to 1 day to be able to correct
    
        if (side == 0) {
            cb_obj.start = cb_obj.start + difference_milliseconds
            cb_obj.value = [cb_obj.value[0] + difference_milliseconds, cb_obj.value[1]]
        }
        else if (side == 1) {
            cb_obj.end = cb_obj.end + difference_milliseconds + 4*24*60*60*1000 
            cb_obj.value = [cb_obj.value[0], cb_obj.value[1] + difference_milliseconds]    
        }
    
        setTimeout(resetStep, 50, cb_obj) // reset step to 31 days
    }
    
    function resetStep(cb_obj) {
        cb_obj.step = 31*24*60*60*1000
    }       
    '''
    range_slider.js_on_change('value_throttled', CustomJS(args = {'end_date': end_date}, code=code))
    
    show(range_slider)
    

    或者也许最好的选择是在月份步骤中根本不使用DateRangeSlider。下面的解决方案使用RangeSlider 结合Div 来实现看起来更好的相同功能:

    import pandas as pd
    from bokeh.plotting import show
    from bokeh.models import RangeSlider, Div, Column, CustomJS
    
    data = {'date_start': ['2018-12', '2019-01', '2019-02', '2019-03', '2019-04', '2019-05', '2019-06', '2019-07', '2019-08', '2019-09', '2019-10', '2019-11'],  
            'date_end': [ '2019-01', '2019-02', '2019-03', '2019-04', '2019-05', '2019-06', '2019-07', '2019-08', '2019-09', '2019-10', '2019-11', '2019-12'], 
            'values' : [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]}
    df = pd.DataFrame(data)
    
    df['Start'] = pd.to_datetime(df['date_start'], format='%Y-%m')
    df['End'] = pd.to_datetime(df['date_end'], format='%Y-%m')
    
    number_dates = len(list(df.date_start.unique()))
    
    start_dates = df.date_start.to_list()
    end_dates = df.date_end.to_list()
    
    range_slider = RangeSlider(start=0, end=number_dates, value=(0, number_dates), step=1, title="", callback_policy = 'mouseup', tooltips = False, width=600, show_value = False)
    div = Div(text = "Date Range: <b>" + str(start_dates[range_slider.value[0]]) + ' . . . ' + str(end_dates[range_slider.value[1]-1]) + '</b>', render_as_text = False, width = 575)
    
    code = '''
    range = Math.round(Number(cb_obj.value[1] - cb_obj.value[0]), 10)
    range = range < 10 ? '0' + range : range
    div.text = "Date Range: <b>" + start_dates[Math.round(cb_obj.value[0], 10)] + '&nbsp;.&nbsp;.&nbsp;.&nbsp;' + end_dates[Math.round(cb_obj.value[1], 10) + -1] + '</b>'
    '''
    
    range_slider.js_on_change('value_throttled', CustomJS(args = {'div': div, 'start_dates': start_dates, 'end_dates': end_dates}, code=code))
    
    show(Column(div, range_slider))
    

    【讨论】:

      猜你喜欢
      • 2018-03-28
      • 1970-01-01
      • 1970-01-01
      • 2011-03-20
      • 2017-07-23
      • 2023-01-20
      • 1970-01-01
      • 2011-09-11
      • 2010-10-07
      相关资源
      最近更新 更多