简体   繁体   中英

Have Bokeh Callback update a list instead of a ColumnDataSource?

My question has to do with modifying this example from the Bokeh Gallery.

I have a matrix m or raw data, in which every row corresponds to a pair of x,y coordinates in a transformed space (see #MockData part of code).

The x,y coordinates are plotted on the left plot. I am trying to change the example so that, when I select some points in the left figure, the right figure will display line plots of the corresponding rows.

I've narrowed the problem down to the point, where the right figure will show the desired data as specified by the "indices" list. I cannot, however, figure out how to link the Callback function to the indices list. (Currently the Callback uselessly updates source s2 with the indices.)

Code should work when copied:

from bokeh.plotting import figure, output_file, show, ColumnDataSource, hplot
from bokeh.models import HoverTool, Callback, ColumnDataSource
import pandas as pd
output_file("Map.html")

# Mock data
m = np.zeros((6,11))
for i in range(6):
    for j in range(11):
        m[i,j] = i+j
x = [0,1,2,3,4,5]; y = [0,2,4,6,8,10]
m0 = m.transpose()
m1 = pd.DataFrame(m0, index=['0','1','2','3','4','5','6','7','8','9','10'], columns=[np.arange(0,len(m),1).astype(str)])

#First plot
s1 = ColumnDataSource(data=dict(x=x,y=y))
p1 = figure(tools=["lasso_select"], plot_width=600, plot_height=400)
p1.scatter('x', 'y', fill_color='black', line_color=None, size=10, source=s1)

#Second plot
s2 = ColumnDataSource(data=dict(z=[]))
p2 = figure(plot_width=400, plot_height=400)    
m1 = ColumnDataSource(m1)
indices = [1,3,4]
for i in indices:
    p2.line(np.arange(0,11,1), '%s'%i ,  source=m1)

s1.callback = Callback(args=dict(s2=s2), code="""
  var inds = cb_obj.get('selected')['1d'].indices;
  var d2 = s2.get('data');
  d2['z'] = []
  for (i = 0; i < inds.length; i++) {
      d2['z'].push(inds[i])}
  s2.trigger('change'); 
""")

layout = hplot(p1, p2)
show(layout)

Original question:

Working with the example in Bokeh documentation. I am trying to get indices from the selection in the left window and use them to get appropriate row from a matrix with original data and plot the row. In detail:

I start with a matrix of values, where each column is a year and each row a location. I run a sklearn Spectral Embedding on the matrix to characterize the data and get a matrix, where each column somehow describes the data. I plot the first 3 columns as x,y coordinates and a color. Next I am trying to modify the example such that, when I select some points, the second graph displays their original data (rows) as separate lines. The relevant code mostly taken from the example is below.

Let me know if I'm misunderstanding your question, but it sounds like you have a matrix (let's call it m1), that you're running a transform on and yielding the data in s1 and plotting in p1. You want be able to select some of the data in p1 and have the corresponding data in m1 be plotted in p2.

If that's right, you're going to need three ColumnDataSource objects. You're going to have to create a ColumnDataSource for m1 and use the s1 and s2 you already have. The Callback will need to be something like:

s1.callback = Callback(args=dict(m1=m1, s2=s2), code="""
  var inds = cb_obj.get('selected')['1d'].indices;
  var d1 = m1.get('data');
  var d2 = s2.get('data');
  d2['x'] = []
  d2['y'] = []
  for (i = 0; i < inds.length; i++) {
    d2['x'].push(d1['x'][inds[i]])
    d2['y'].push([inds[i]])
  }
  s2.trigger('change'); 
""")

This will take the indices of the selected data and find the x and y with the same indices m1 (the raw data) and add it to s2. Then the s2.trigger('change') call will update the data points in p2.

Let me know if I'm misunderstanding your question.

Not having any responses neither here nor in Bokeh mailing list, leads me to believe it's impossible to use callback in this manner, so I had to work around it and accept the current limitation, which is the inability to make the callback "size" dynamic.

For the purpose of data exploration this will do anyway.

from bokeh.plotting import figure, output_file, show, ColumnDataSource, hplot
from bokeh.models import HoverTool, Callback, ColumnDataSource
import pandas as pd
output_file("bla.html")

# Mock data
m = np.ones((6,11))
for i in range(2,6):
    for j in range(11):
        m[i,j] = i+j
x = [0,1,2,3,4,5]; y = [0,2,4,6,8,10]
m0 = m.transpose()
m1 = pd.DataFrame(m0, index=['0','1','2','3','4','5','6','7','8','9','10'], columns=[np.arange(0,len(m),1).astype(str)])

#First plot
s1 = ColumnDataSource(data=dict(x=x,y=y))
p1 = figure(tools=["lasso_select"], plot_width=600, plot_height=400)
p1.scatter('x', 'y', fill_color='black', line_color=None, size=10, source=s1)

#Second plot
s2 = ColumnDataSource(data=dict(x=[],y=[],y2=[]))
p2 = figure(plot_width=400, plot_height=400, tools =[])

m1 = ColumnDataSource(m1) #Actual Datasource for the second plot
p2.line(np.arange(0,100,1), 'y' , source=s2) # From original data - series 1
p2.line(np.arange(0,100,1), 'y2' , source=s2) # From original data - series 2

s1.callback = Callback(args=dict(s2=s2, m1=m1), code="""
  var inds = cb_obj.get('selected')['1d'].indices;
  var d1 = m1.get('data'); 
  var d2 = s2.get('data');
  d2['y'] = []
  d2['y2'] = []
  for (i = 0; i < 11; i++) {
    d2['y'].push(d1[inds['0']][i]),
    d2['y2'].push(d1[inds['1']][i])
  }
  s2.trigger('change'); 
""")

layout = hplot(p1, p2)
show(layout)

The code now plots the original data for the first two indices in the selected data. The limitation is that the number of series of original data has to be predefined, so the code will only ever plot two lines. Furthermore it also needs at least two lines to execute. It won't work for only one point selected. Therefore if I predefine more lines to be graphed I will always have to select that number of points.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM