Bokeh: Open a new data table with each time a point on plot is clicked

250 Views Asked by At

I have two data sources with the same keys. Source 1 has key/value pairs with unique keys to be plotted. Source 2 has the same keys as source 1, but each with potentially multiple values.

Is it possible for clicking on a point on the plot (source1) to open a new data table showing all corresponding rows in source2? ie if 3 different points are selected on the plot, then 3 different data tables would be visible.

So there are two pieces I'm hoping for help with:

  1. Only show values for one key in a data table.
  2. Make a new data table appear each time a point is selected on the plot, and disappear when it's unselected.

I figured out how to link the plot to a data table with a different source & the same keys, and have the corresponding rows highlight when a point on the plot is clicked.

import numpy as np
import pandas as pd
from bokeh.models.callbacks import CustomJS
from bokeh.plotting import figure
from bokeh.io import show
from bokeh.layouts import row
from bokeh.models.widgets import DataTable, TableColumn
from bokeh.models import ColumnDataSource, CustomJSFilter, TapTool, HoverTool


source = ColumnDataSource(pd.DataFrame(
    np.array([[i, i] for i in range(0, 10)]), columns=['x', 'y']))

data = [[i, "%s.%d" % ("data", i)] for i in range(0, 10)]
data.extend([[i, "%s.%d" % ("data", i)] for i in range(0, 10)])
source2 = ColumnDataSource(pd.DataFrame(data, columns = ['x', 'data']))

columns = [
        TableColumn(field='x', title='x'),
        TableColumn(field='data', title='data'),
    ]
data_table = DataTable(source=source2, columns=columns, width=600, height=400)

selection_callback = CustomJS(args=dict(source=source, source2=source2, p2=data_table), code="""
    var indices = []

    var source_x = source.data.x[source.selected.indices[0]]
    source2.data.x.forEach((source2_x, i) => {
        if (source2_x == source_x) {
          indices.push(i)
        }
    });

    source2.selected.indices = indices
    p2.change.emit()
""")

plot = figure(title="Plot1", tools="tap,pan,box_zoom,wheel_zoom,save,reset")
plot.select(TapTool).callback = selection_callback
source.selected.js_on_change('indices', selection_callback)
plot.circle('x', 'y', source=source, line_color=None, color='red', size=6)

show(row(plot, data_table))

I tried replacing "source2.selected.indices" with "source2.indices" to try to hide unselected indices, but that didn't seem to do anything.

And I haven't been able to find any information about making plots or tables appear/disappear with interactions.

1

There are 1 best solutions below

0
On

Played around with this some more and I was able to figure it out.

Specifically:

Created a master ColumnDataSource for the data table. Set the data in the data table to just the relevant subset. set the data table visibility to false to hide it, unless a specific value is selected. Here's the relevant CustomJS code snippet:

callback = CustomJS(args=dict(source=source, source2_master=source2_master, source2=source2, p2=data_table), code="""
    var x_vals = []
    var y_vals = []

    var source_x = source.data.x[source.selected.indices[0]]
    source2_master.data.x.forEach((source2_x, i) => {
        if (source2_x == source_x) {
          x_vals.push(source2_master.data.x[i])
          y_vals.push(source2_master.data.y[i])
        }
    });

    source2.data = {'x': [x_vals], 'y': [y_vals]}
    p2.visible = x_vals.length != 0;
    p2.change.emit()
""")