使用“bokeh”TapTool打开新图

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

我有一个类

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。非常感谢!

python bokeh
3个回答
1
投票

我编辑了你的代码,抱歉我回来得太晚了。

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 类创建一个新图形时都选择一些东西。不推荐。因此,您可以创建所有图表并根据您的意愿使它们可见/不可见,或者您可以更改图表的来源。您可以找到很多有关更改图形源并使它们可见/不可见的示例。我希望它对你有用:)


0
投票

首先,你不需要把所有东西都变成一个类。如果您正在创建一个类来调用其中的单个方法,例如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
。回调中的新参数给出了我的图中所选节点的索引。我可以通过阅读数据源来访问所有信息。


-1
投票

有两种方法可以做到这一点。这是基本示例。首先,您可以使用 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 作为索引。有关选择的更多信息,请在此处检查。因此,在函数中,您可以创建一个新的图形和字形(线条等)并更新它。 这里,非常好的例子。您可以从您的电脑上拉取并运行它。

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