我有一个类
Collection
,它包含一堆其他类对象Thing
,它们都具有相同的属性和不同的值。 Collection.plot(x, y)
方法绘制所有收集的 x
对象的 y
值与 Thing
值的散点图,如下所示:
from bokeh.plotting import figure, show
from bokeh.models import TapTool
class Thing:
def __init__(self, foo, bar, baz):
self.foo = foo
self.bar = bar
self.baz = baz
def plot(self):
# Plot all data for thing
fig = figure()
fig.circle([1,2,3], [self.foo, self.bar, self.baz])
return fig
class Collection:
def __init__(self, things):
self.things = things
def plot(self, x, y):
# Configure plot
title = '{} v {}'.format(x, y)
fig = figure(title=title, tools=['pan', 'tap'])
taptool = fig.select(type=TapTool)
taptool.callback = RUN_THING_PLOT_ON_CLICK()
# Plot data
xdata = [getattr(th, x) for th in self.things]
ydata = [getattr(th, y) for th in self.things]
fig.circle(xdata, ydata)
return fig
然后我将制作所有四个
Thing
源''foo'与'baz'值的散点图:
A = Thing(2, 4, 6)
B = Thing(3, 6, 9)
C = Thing(7, 2, 5)
D = Thing(9, 2, 1)
X = Collection([A, B, C, D])
X.plot('foo', 'baz')
我希望在这里发生的是可以单击散点图上的每个点。单击时,它将针对给定的
plot
运行 Thing
方法,为其所有“foo”、“bar”和“baz”值绘制单独的图。
关于如何实现这一点有什么想法吗?
我知道我可以将所有对象的所有数据加载到
ColumnDataSource
中,并使用这个玩具示例进行绘图,但在我的实际用例中,Thing.plot
方法会执行大量复杂的计算,并且可能会绘制数千个点。我真的需要它来实际运行 Thing.plot
方法并绘制新的绘图。这样可行吗?
或者,我可以向
Collection.plot
方法传递所有 Thing.plot
预先绘制的图形的列表,然后在单击时显示吗?
使用 Python>=3.6 和 bokeh>=2.3.0。非常感谢!
我编辑了你的代码,抱歉我回来得太晚了。
from bokeh.plotting import figure, show
from bokeh.models import TapTool, ColumnDataSource
from bokeh.events import Tap
from bokeh.io import curdoc
from bokeh.layouts import Row
class Thing:
def __init__(self, foo, bar, baz):
self.foo = foo
self.bar = bar
self.baz = baz
def plot(self):
# Plot all data for thing
t_fig = figure(width=300, height=300)
t_fig.circle([1, 2, 3], [self.foo, self.bar, self.baz])
return t_fig
def tapfunc(self):
selected_=[]
'''
here we get selected data. I select by name (foo, bar etc.) but also x/y works. There is a loop because taptool
has a multiselect option. All selected names adds to selected_
'''
for i in range(len(Collection.source.selected.indices)):
selected_.append(Collection.source.data['name'][Collection.source.selected.indices[i]])
print(selected_) # your selected data
# now create a graph according to selected_. I use only first item of list. But you can use differently.
if Collection.source.selected.indices:
if selected_[0] == "foo":
A = Thing(2, 4, 6).plot()
layout.children = [main, A]
elif selected_[0] == "bar":
B = Thing(3, 6, 9).plot()
layout.children = [main, B]
elif selected_[0] == 'baz':
C = Thing(7, 2, 5).plot()
layout.children = [main, C]
class Collection:
# Columndata source. Also could be added in __init__
source = ColumnDataSource(data={
'x': [1, 2, 3, 4, 5],
'y': [6, 7, 8, 9, 10],
'name': ['foo', 'bar', 'baz', None, None]
})
def __init__(self):
pass
def plot(self):
# Configure plot
TOOLTIPS = [
("(x,y)", "(@x, @y)"),
("name", "@name"),
]
fig = figure(width=300, height=300, tooltips=TOOLTIPS)
# Plot data
circles = fig.circle(x='x', y='y', source=self.source, size=10)
fig.add_tools(TapTool())
fig.on_event(Tap, tapfunc)
return fig
main = Collection().plot()
layout = Row(children=[main])
curdoc().add_root(layout)
问题是当你每次 Thing 类创建一个新图形时都选择一些东西。不推荐。因此,您可以创建所有图表并根据您的意愿使它们可见/不可见,或者您可以更改图表的来源。您可以找到很多有关更改图形源并使它们可见/不可见的示例。我希望它对你有用:)
首先,你不需要把所有东西都变成一个类。如果您正在创建一个类来调用其中的单个方法,例如plot(),那么请重新考虑它是否真的需要成为一个类。
我正在使用更短的方法来获取所需的回调,将观察器添加到数据源选择中。找到你的数据源,我的是网络图:
network_graph.node_renderer.data_source.selected.on_change("indices", self.plot_callback)
self.current_data_src = pd.DataFrame(network_graph.node_renderer.data_source.data)
def plot_callback(self, _attr, _old, new):
"""
Receives the indices in the data source of the node the
user has clicked on.
"""
selected = next(iter(new), None)
if selected is None or self.current_data_src.empty:
return
print(self.current_data_src.iloc[selected])
我的 ColumnDataSource 对象是
network_graph.node_renderer.data_source
。回调中的新参数给出了我的图中所选节点的索引。我可以通过阅读数据源来访问所有信息。
有两种方法可以做到这一点。这是基本示例。首先,您可以使用 Tap 事件来执行此操作并创建一个函数来从字形获取信息。其次,您可以直接将源连接到函数。
from bokeh.io import curdoc
from bokeh.plotting import figure
from bokeh.events import Tap
from bokeh.models import TapTool, ColumnDataSource
def tapfunc():
print(source.selected.indices)
def sourcefunc(attr, old, new):
print(source.selected)
source = ColumnDataSource(data={
'x': [1,2,3,4,5],
'y': [6,7,8,9,10]
})
p = figure(width=400, height=400)
circles = p.circle(x='x', y='y', source=source, size=20, color="navy", alpha=0.5)
p.add_tools(TapTool())
p.on_event(Tap, tapfunc)
source.selected.on_change('indices', sourcefunc)
curdoc().add_root(p)
selected 返回选定值索引的列表。因此,您应该将索引添加到源中。您可以使用 pandas 作为索引。有关选择的更多信息,请在此处检查。因此,在函数中,您可以创建一个新的图形和字形(线条等)并更新它。 这里,非常好的例子。您可以从您的电脑上拉取并运行它。