I have a class Collection
that holds a bunch of other class objects Thing
that all have the same attributes with different values. The Collection.plot(x, y)
method makes a scatter plot of the x
values vs. the y
values of all the collected Thing
objects like so:
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
Then I would make a scatter plot of all four Thing
sources' 'foo' vs. 'baz' values with:
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')
What I would like to have happen here is have each point on the scatter plot able to be clicked. On click, it would run the plot
method for the given Thing
, making a separate plot of all its 'foo', 'bar', and 'baz' values.
Any ideas on how this can be accomplished?
I know I can just load ALL the data for all the objects into a ColumnDataSource
and make the plot using this toy example, but in my real use case the Thing.plot
method does a lot of complicated calculations and may be plotting thousands of points. I really need it to actually run the Thing.plot
method and draw the new plot. Is that feasible?
Alternatively, could I pass the Collection.plot
method a list of all the Thing.plot
pre-drawn figures to then display on click?
Using Python>=3.6 and bokeh>=2.3.0. Thank you very much!
I edited your code and sorry i returned too late.
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)
The problem is when you select something every time Thing class creates a new figure. It's not recommended. So, you could create all graphs and make them visible/invisible as your wishes OR you could change the source of the graph. You could find lots of examples about changing graph source and making them visible/invisible. I hope it works for you :)