首页 文章

Bokeh Multi-Select小部件回调无法正常工作

提问于
浏览
1

我是Bokeh的新手 . 最近试图实现一些小部件回调,很难在线找到任何资源 .

场景:我有一个multi_select bokeh小部件,其中包含公司列表 . 根据所选择的公司,必须更改Vbar中的数据 . 为此,我试图根据Multi select中的值更改Vbar中使用的数据源 . 我无法得到理想的结果 . 有人可以帮我解决这个问题 .

我在CustomJs上很穷,因此在Bokeh Server上为callabcks做这件事 . 如果CustomJs上也有解决方案,那对我有很大的帮助 .

非常感谢你提前的时间!

以下是我正在使用的代码

source =source1[source1['year']== dt.now().year]
sourcex = source[source['month'] ==1 & 
source['CompanyNo'].isin(['01','02','03','04','05','08']) ]

Overall= ColumnDataSource(source)
Curr= ColumnDataSource(sourcex)

boolinit = source['month']==1
view = CDSView(source=Overall, filters=[BooleanFilter(boolinit)])

hover3 = HoverTool(
            tooltips = [
                ('day', '@day'),
                ('ExtendedPrice','@{ExtendedPrice}{0,0}'),
                ],
            formatters = {
                'day': 'datetime',
                'ExtendedPrice': 'numeral'}
           )

p =  figure(
    title='YEARLY SALES',  
    plot_width=600, 
    plot_height=400, 
    min_border=3,

tools = [hover3,'box_zoom','wheel_zoom', 'pan','reset'],  
toolbar_location="above")
p.vbar(x='day', top='ExtendedPrice', width=0.2, color='#e8bc76', 
source=Curr)
p.xaxis.axis_label = 'Day'
p.xaxis.axis_label_text_font_style = 'normal'
p.xaxis.axis_label_text_font_size = '12pt'
p.yaxis[0].formatter = NumeralTickFormatter(format="0,0")



def Multi_Selectupdate(attrname, old, new):

    curr=sourcex[sourcex['CompanyNo'].isin(new)]
    source.data=curr.data




companies=['All']+sourcex['CompanyNo'].unique().tolist()
multi_select = MultiSelect(title="Select:", value=['01'], options=companies, 
height=200, width=100)

multi_select.on_change('value',Multi_Selectupdate )

layout = column(multi_select, p )

show(layout)

1 回答

  • 1

    由于我没有数据,因此我使用以下脚本生成它 . 我假设您的数据有3列 - DateExtendedPriceCompanyNo . 我首先生成了10K行的随机数据,然后在 CompanyNodaymonthyear 级别进行汇总 .

    import pandas as pd
    import random
    CopmanyList = ['00', '01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12', '13', '14', '15']
    
    df = pd.DataFrame({'base' : ["2017-01-01" for t in range(10000)],
        'Date' : [random.randint(0, 1035) for t in range(10000)], 
        'ExtendedPrice' : [random.random() for t in range(10000)],
        'CompanyNo' : [CopmanyList[random.randint(0, 15)] for t in range(10000)]})
    
    df['base'] = pd.to_datetime(df['base'])
    df["Date2"] = df.apply(lambda x: x["base"] + timedelta(days=x['Date']), axis=1)
    df.drop(['base', 'Date'], axis=1, inplace=True)
    df.set_index('Date2', inplace=True)
    df['month'] = df.index.month
    df['year'] = df.index.year
    df['day'] = df.index.day
    source1=df.groupby(['year','month','day', 'CompanyNo'], as_index = False)['ExtendedPrice'].sum()
    source =source1[source1['year']== dt.now().year]
    sourcex = source[source['month'] ==1]
    sourcex = sourcex[sourcex['CompanyNo'].isin(['01'])]
    sourcex.sort_values(by='day', inplace=True)
    

    现在,您想要实现的是,给定一天和一组公司名称,您需要发布某种类型的 ExtendedPrice 聚合的条形图,即公司'01'和'02'(已选中)有2行,对于当前年份的第2个月(也由滑块选择),这两个的ExtendedPrice值为100和200.如果我们要显示总和,则需要在条形图中显示300 . 这个总结,需要动态计算 .

    有两种方法可以实现它 .

    1. Bokeh Callbacks

    这将使用本机python函数,但您需要使用 bokeh serve 运行它,例如 bokeh serve --show <filename.py> . 请参阅下面的代码,本机python函数 compsel 正在过滤pandas数据帧并对其进行汇总并用新创建的数据替换图表后端数据 -

    from datetime import timedelta
    from datetime import datetime as dt
    from bokeh.models.widgets import MultiSelect, Slider
    from bokeh.layouts import widgetbox, column
    from bokeh.models.ranges import FactorRange
    from bokeh.plotting import figure, curdoc
    from bokeh.models import ColumnDataSource, HoverTool, CustomJS
    
    Overall= ColumnDataSource(source)
    Curr= ColumnDataSource(sourcex[['day', 'ExtendedPrice']])
    Curr.remove('index')
    
    hover3 = HoverTool(tooltips = [('day', '@day'),('Sales','@{ExtendedPrice}{0,0.00}')],
                       formatters = {'day': 'datetime','Sales': 'numeral'})
    
    p =  figure(title='YEARLY SALES',  plot_width=600, plot_height=400, min_border=3,
    tools = [hover3,'box_zoom','wheel_zoom', 'pan','reset'],  
    toolbar_location="above")
    
    r = p.vbar(x='day', top='ExtendedPrice', width=0.2, color='#e8bc76', source=Curr)
    p.xaxis.axis_label = 'Day'
    p.xaxis.axis_label_text_font_style = 'normal'
    p.xaxis.axis_label_text_font_size = '12pt'
    
    def compsel(attr, old, new):
        vcomp = multi_select.value
        vmonth = slider.value
        sourcex = source[source['month'] ==vmonth]
        sourcex = sourcex[sourcex['CompanyNo'].isin(vcomp)]
        sourcex = sourcex.groupby(['day'], as_index = False)['ExtendedPrice'].sum()
        Currnew= ColumnDataSource(sourcex[['day', 'ExtendedPrice']])
        Currnew.remove('index')
        r.data_source.data=Currnew.data
    
    
    companies=source['CompanyNo'].unique().tolist()
    companies.sort()
    multi_select = MultiSelect(title="Select:", value=['01'], options=companies, height=200, width=100)
    slider = Slider(start=1, end=12, value=1, step=1, title="Month")
    slider.on_change("value", compsel)
    multi_select.on_change("value", compsel)
    
    layout = column(multi_select, slider, p)
    
    curdoc().add_root(layout)
    

    还有其他选项来托管此应用程序 . 通过在终端中键入 bokeh serve --help 来查看其他选项

    2. JavaScript Callbacks

    这将生成一个javascript函数来与图表交互,这不需要散景服务器并直接在浏览器上工作 . 这将需要javascript中非常简单的代码 . 在这里解决的例子中,我总结 ExtendedPrice 作为总和,但代码将需要小的调整来计算其他统计数据,例如意思 . 这里的js回调函数与散景回调函数类似,它会读取较大的数据集并根据选择和滑块值对其进行过滤 -

    source =source1[source1['year']== dt.now().year]
    sourcex = source[source['month'] ==1]
    sourcex = sourcex[sourcex['CompanyNo'].isin(['01'])]
    sourcex.sort_values(by='day', inplace=True)
    
    Overall= ColumnDataSource(source)
    Curr= ColumnDataSource(sourcex[['day', 'ExtendedPrice']])
    Curr.remove('index')
    
    hover3 = HoverTool(tooltips = [('day', '@day'),('Sales','@{ExtendedPrice}{0,0.00}')],
                       formatters = {'day': 'datetime','Sales': 'numeral'})
    
    p =  figure(title='YEARLY SALES',  plot_width=600, plot_height=400, min_border=3,
    tools = [hover3,'box_zoom','wheel_zoom', 'pan','reset'],  
    toolbar_location="above")
    
    r = p.vbar(x='day', top='ExtendedPrice', width=0.2, color='#e8bc76', source=Curr)
    p.xaxis.axis_label = 'Day'
    p.xaxis.axis_label_text_font_style = 'normal'
    p.xaxis.axis_label_text_font_size = '12pt'
    
    callms = CustomJS(args=dict(source=Overall, sc=Curr), code="""  
            var comp=msel.attributes.value;
            var f = slider.value;
            sc.data['ExtendedPrice'] = [];
            sc.data['day'] = [];
            for (var i = 0; i <= source.get_length(); i++){
              if (source.data['month'][i] == f){
                if (comp.indexOf(source.data['CompanyNo'][i]) >=0){
                  var d1 = source.data['day'][i]
                  if(typeof sc.data['day'][d1-1]=="undefined"){
                    sc.data['day'][d1-1] = d1
                    sc.data['ExtendedPrice'][d1-1] = source.data['ExtendedPrice'][i]
                  }
                  else{
                    sc.data['ExtendedPrice'][d1-1] = sc.data['ExtendedPrice'][d1-1] + source.data['ExtendedPrice'][i]  
                  }
                }
              }
            }
            sc.change.emit();
        """)
    
    companies=source['CompanyNo'].unique().tolist()
    companies.sort()
    multi_select = MultiSelect(title="Select:", value=['01'], options=companies, height=200, width=100, callback=callms)
    slider = Slider(start=1, end=12, value=1, step=1, title="Month", callback=callms)
    callms.args["msel"] = multi_select
    callms.args["slider"] = slider
    
    layout = column(multi_select, slider, p)
    
    output_file("Filterdata.html")
    show(layout)
    

相关问题