Python 基于列而不是行索引/索引的Bokeh链接/刷

Python 基于列而不是行索引/索引的Bokeh链接/刷,python,plot,bokeh,Python,Plot,Bokeh,我有一个与此类似的数据帧: person activities hours foodeaten 0 1 a 3 12 1 1 b 4 14 2 1 c 6 34 3 2 a 2 45 4 2 b 7 67 5

我有一个与此类似的数据帧:

   person activities  hours  foodeaten
0       1          a      3         12
1       1          b      4         14
2       1          c      6         34
3       2          a      2         45
4       2          b      7         67
5       3          a      5          5
6       3          b      3         -1
7       3          c      2          3
8       3          d     12          5
我想画出一个人在每项活动上花费的时间。 因此,图1:活动a,对于活动a,x=人y=小时 图2:活动b,x=人员y=活动b的小时数 等等

我想链接的人的基础上,而不是索引的情节。因此,当我在图1中为人物1选择圆圈时,该人物在其他图中的小时数也应高亮显示,以供我参考

MWE:

本例显示了行链接行为,但我想通过在图中选择此人的属性来突出显示此人的所有内容。
Bokeh没有任何内置的自动链接功能。但是,可以使用
CustomJS
callbacks基于另一个glyph的选择更新一个glyph的选择:

from bokeh.io import show
from bokeh.layouts import row
from bokeh.models import CustomJS
from bokeh.plotting import figure

p1 = figure(plot_width=300, plot_height=300, tools="tap")
r1 = p1.circle(x=[1, 2], y=1, color=["red", "blue"], size=20)

p2 = figure(plot_width=300, plot_height=300, tools="")
r2 = p2.circle(x=[1, 1, 2], y=[1, 2, 1.5], color=["red", "red", "blue"], size=20)

callback = CustomJS(args=dict(s2=r2.data_source), code="""
const s2_inds = []
if (cb_obj.indices.indexOf(0) >= 0) {
  s2_inds.push(0)
  s2_inds.push(1)
}
if (cb_obj.indices.indexOf(1) >= 0) {
  s2_inds.push(2)
}
s2.selected.indices = s2_inds
"""))

show(row(p1, p2))
使用此代码,选择左侧红色圆圈将选择所有右侧红色圆圈,蓝色圆圈也是如此:


请注意,上面的代码显式地手动编码索引之间的关系,以说明一般技术(即手动指定一个表)。您可能希望使用有关特定数据的知识和假设来构建一个更通用的
CustomJS
回调,该回调可以计算自动设置哪些索引

以下是任意数量绘图的工作示例:

from bokeh.plotting import figure, show
from bokeh.layouts import gridplot
from bokeh.models import ColumnDataSource, CDSView, BooleanFilter, CustomJS
import pandas as pd

data = {'person': [1, 1, 1, 2, 2, 3, 3, 3, 3], 'activities':['a', 'b', 'c', 'a', 'b', 'a', 'b', 'c', 'd'], 'hours':[3, 4, 6, 2, 7, 5, 3, 2, 12], 'foodeaten':[12, 14, 34, 45, 67, 5, -1, 3, 5]}
df = pd.DataFrame(data = data)
source = ColumnDataSource(data = df)
views = [(df.activities == l) for l in ['a', 'b', 'c', 'd']]
filtered_views = [CDSView(source = source, filters = [BooleanFilter(view.values.tolist())]) for view in views]
plot_options = dict(plot_width = 250, plot_height = 250, tools = "tap,pan,wheel_zoom,reset,save", tooltips = [("Person", "@person"), ("hours", "@hours")])
plots = [figure(title = 'activity {l}'.format(l = l), **plot_options) for l in ['a', 'b', 'c', 'd', 'a', 'b', 'c', 'd']]
[plot.circle('person', y = name, size = 15, view = view, source = source) for plot, view, name in zip(plots, 2 * filtered_views, 4 * ['hours'] + 4 * ['foodeaten'])]
callback = CustomJS(args = dict(source = source, plots = plots), code = """
const selected_index = source.selected.indices[0]
const person = source.data['person'][selected_index]
var all_selected = [];
for (index in source.data['index']){
    if (source.data['person'][index] == person)
    all_selected.push(index) 
}
source.selected.indices = all_selected; """)

[plot.js_on_event('tap', callback) for plot in plots]
show(gridplot(children = [plot for plot in plots], ncols = 2))

@D.Thomas,因为您不应该对其他人的代码进行重大更改。我鼓励你发布一个你自己的答案,列出你改变了什么,为什么改变,以及这个答案的链接。
from bokeh.plotting import figure, show
from bokeh.layouts import gridplot
from bokeh.models import ColumnDataSource, CDSView, BooleanFilter, CustomJS
import pandas as pd

data = {'person': [1, 1, 1, 2, 2, 3, 3, 3, 3], 'activities':['a', 'b', 'c', 'a', 'b', 'a', 'b', 'c', 'd'], 'hours':[3, 4, 6, 2, 7, 5, 3, 2, 12], 'foodeaten':[12, 14, 34, 45, 67, 5, -1, 3, 5]}
df = pd.DataFrame(data = data)
source = ColumnDataSource(data = df)
views = [(df.activities == l) for l in ['a', 'b', 'c', 'd']]
filtered_views = [CDSView(source = source, filters = [BooleanFilter(view.values.tolist())]) for view in views]
plot_options = dict(plot_width = 250, plot_height = 250, tools = "tap,pan,wheel_zoom,reset,save", tooltips = [("Person", "@person"), ("hours", "@hours")])
plots = [figure(title = 'activity {l}'.format(l = l), **plot_options) for l in ['a', 'b', 'c', 'd', 'a', 'b', 'c', 'd']]
[plot.circle('person', y = name, size = 15, view = view, source = source) for plot, view, name in zip(plots, 2 * filtered_views, 4 * ['hours'] + 4 * ['foodeaten'])]
callback = CustomJS(args = dict(source = source, plots = plots), code = """
const selected_index = source.selected.indices[0]
const person = source.data['person'][selected_index]
var all_selected = [];
for (index in source.data['index']){
    if (source.data['person'][index] == person)
    all_selected.push(index) 
}
source.selected.indices = all_selected; """)

[plot.js_on_event('tap', callback) for plot in plots]
show(gridplot(children = [plot for plot in plots], ncols = 2))