使用 MultiChoice 和 CustomJS 过滤散景图的数据源

Filtering data source for Bokeh plot using MultiChoice and CustomJS

前言,我没用过JavaScript。经过几个小时的搜索,我仍然不太明白如何解决我的特定问题。

我一直在利用散景制作交互式绘图,并使用 pandas 过滤数据,如下面的代码所示。

import pandas as pd
import numpy as np

from bokeh.plotting import figure, show
from bokeh.models import ColumnDataSource

df = pd.DataFrame()
df['name'] = ['a','a','a','b','b','b','c','c','c']
df['sample'] =np.random.randint(1,3,9)
df['x'] = np.random.randint(0,10,9)
df['y'] = np.random.randint(0,10,9)

plot = figure(width=400, height=400)

for name in df.name.unique():
    
    source = ColumnDataSource(df[df['name']==name].groupby(['sample']).median())
    plot.circle('x', 'y', source=source, legend_label=name)

plot.legend.click_policy="hide"
plot.legend.background_fill_alpha = 0.25

show(plot)

我遇到的问题是,如果我的名字太多,那么图例就会变长,情节就会变得混乱。我试图通过添加一个 MultiChoice 小部件来解决这个问题,用户可以在其中选择要根据哪些名称来过滤数据和绘制数据,同时使用 CustomJS 回调来调整数据源。我 运行 遇到了一个问题,因为我需要按 'name' 过滤数据,然后按 'sample' 分组并找到这些点的中值。

我想我缺乏 JS 知识让我在这里有点迷茫。谁能指出我正确的方向?

编辑(更新和澄清):这是我还没有开始工作的另一种尝试。通过控制台日志,当我在 MultiChoice 小部件上进行选择时,源似乎发生了变化。然而,我的情节没有改变。我还需要它与 html 一起使用,在这种情况下我不能使用散景服务器。

import pandas as pd
import numpy as np

from bokeh.plotting import figure, show
from bokeh.models import ColumnDataSource, CustomJS, MultiChoice
from bokeh.layouts import row

df = pd.DataFrame()
df['name'] = ['a','a','a','b','b','b','c','c','c']
df['sample'] =np.random.randint(1,3,9)
df['x'] = np.random.randint(0,10,9)
df['y'] = np.random.randint(0,10,9)

plot = figure(width=400, height=400)

source = ColumnDataSource(df)

initial_value = [df.name[0]]
options = list(df.name.unique())
multi_choice = MultiChoice(value=initial_value, options=options, max_items=3, title='Selection:')

temp_choice_dict = {}
for name in df.name.unique():
    if name == 'a':
        choice_dict = {'name': name}
    else:
        temp_choice_dict = {'name': name}
    for key, value in temp_choice_dict.items():
            choice_dict=set_key(choice_dict,key,value)
            
source_name = ColumnDataSource(name_dict)

if source_name.data['name']==[]:
    source_empty = ColumnDataSource({'x':[],'y':[]})
    plot.circle('x', 'y', source=source_empty)
else:
    for i in range(len(source_name.data['name'])):

        source = ColumnDataSource(df[df['name']==source_name.data['name'][i]].groupby(['sample']).median())
        plot.circle('x', 'y', source=source, legend_label=source_name.data['name'][i])

plot.circle('x', 'y', source=source)

callback = CustomJS(args={'source_name':source_name},code="""  
        console.log(' changed selected option', cb_obj.value);
        source_name.data['name'] = cb_obj.value;
        console.log(' source change', source_name.data['name']);
        source_name.change.emit();
""")


multi_choice.js_on_change('value', callback)

plot.legend.background_fill_alpha = 0.25

show(row(plot,multi_choice))

我在 bokeh 论坛上找到了一些帮助。解决方案是调整所选字形和图例标签的可见性。下面是完整的解决方案。

import pandas as pd
import numpy as np

from bokeh.plotting import figure, show, row
from bokeh.models import ColumnDataSource, CustomJS, MultiChoice

df = pd.DataFrame()
df['name'] = ['a','a','a','b','b','b','c','c','c']
df['sample'] =np.random.randint(1,3,9)
df['x'] = np.random.randint(0,10,9)
df['y'] = np.random.randint(0,10,9)

plot = figure(width=400, height=400)


name_dict={'name':[],'legend':[],'label':[]}
for name in df.name.unique():  
    source = ColumnDataSource(df[df['name']==name].groupby(['sample']).median())
    name_glyph = plot.circle('x', 'y', source=source, legend_label=name)
    name_dict['name'].append(name_glyph)
    name_dict['label'].append(name)

name_legend_dict={'name':[]}
for label in range(len(df.name.unique())):
    name_dict['legend'].append(plot.legend.items[label])
    
# Set up MultiChoice widget
initial_value = [df.name[0]]
options = list(df.name.unique())
multi_choice = MultiChoice(value=initial_value, options=options, max_items=3, title='Selection:')

for i in range(len(options)):
    if name_dict['label'][i] in initial_value:
        name_dict['name'][i].visible = True;
        name_dict['legend'][i].visible = True;
    else:
        name_dict['name'][i].visible = False;
        name_dict['legend'][i].visible = False;   
    

callback = CustomJS(args=dict(name_dict=name_dict, multi_choice=multi_choice), code="""
var selected_vals = multi_choice.value;
var index_check = [];

for (var i = 0; i < name_dict['name'].length; i++) {
    index_check[i]=selected_vals.indexOf(name_dict['label'][i]);
        if ((index_check[i])>= 0) {
            name_dict['name'][i].visible = true;
            name_dict['legend'][i].visible = true;
            }
        else {
            name_dict['name'][i].visible = false;
            name_dict['legend'][i].visible = false;
        }
    }
""")

multi_choice.js_on_change('value', callback)

plot.legend.background_fill_alpha = 0.25

show(row(plot,multi_choice))