如何在Bokeh DateRangeSlider中实现1个月的步骤?

问题描述 投票:0回答:1

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

当步长设置为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)

enter image description here

python date slider bokeh
1个回答
0
投票

经过一些努力之后,我想到了这个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)

enter image description here

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

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))

enter image description here

© www.soinside.com 2019 - 2024. All rights reserved.