I have a large dataset that I want to explore. However I don't want to create multiple plots. I just want a single plot where I can interactively change the columns used for the x and y axis and the plot will update itself.
I'm trying to do this with Python/Bokeh using Bokeh to serve my script to a browser. However I'm not clear how to get the plot to update. I see a lot of examples where the underlying datasource is changed, but I don't want to do this I just want to change which columns are being plotted.
I've made a simplified example of what I want to do below. It uses two 'Select' widgets for choosing the x and y columns of the datasource. These have callbacks that attempt to change the column that the 'Line' glyph is referring to. However this does not seem to work. Any advice would be welcome.
import numpy as np
from bokeh.models import ColumnDataSource
from bokeh.plotting import Figure
from bokeh.models.widgets import Select,TextInput
from bokeh.models.glyphs import Line
from bokeh.models.layouts import HBox, VBox
from bokeh.io import curdoc
#==============================================================================
#%% Define some Data
#==============================================================================
N = 200
# Define the data to be used
x = np.linspace(0,4.*np.pi,N)
y = 3*np.cos(2*np.pi*x + np.pi*0.2)
z = 0.5*np.sin(2*np.pi*0.8*x + np.pi*0.4)
source = ColumnDataSource({'x':x,'cos':y,'sin':z})
#==============================================================================
#%% Layout
#==============================================================================
TOOLS = "box_select,lasso_select,help"
# create a new plot
plot = Figure(tools=TOOLS, title=None)
# Make a line and connect to data source
glyph = Line(x="x", y="cos", line_color="#F46D43", line_width=6, line_alpha=0.6)
plot.add_glyph(source, glyph)
# Add list boxes for selecting which columns to plot on the x and y axis
yaxis_select = Select(title="Y axis:", value="cos",
options=['x','cos','sin'])
xaxis_select = Select(title="X axis:", value="x",
options=['x','cos','sin'])
# Text input as a title
text = TextInput(title="title", value='my sine wave plotter')
# Layout widgets next to the plot
controls = VBox(text,yaxis_select,xaxis_select)
layout = HBox(controls,plot,width=800)
#==============================================================================
#%% Callbacks
#==============================================================================
# Put callbacks on the list boxes so that when they are changed the columns being
# plotted get changed.
def update_x_axis(attr, old, new):
# Change the column used for the x axis
glyph.x = xaxis_select.value
def update_y_axis(attr, old, new):
# Change the column used for the y axis
glyph.y = yaxis_select.value
yaxis_select.on_change('value', update_y_axis)
xaxis_select.on_change('value', update_x_axis)
#==============================================================================
#%% Add to document root
#==============================================================================
curdoc().add_root(layout)
curdoc().title = "Plotting app"
To edit the actual source field that the glyphs take their co-ordinates from, you need a variation of the code found in this question. A revision of ImportanceOfBeingErnest's code yields the correct result with no need to change the original ColumnDataSource or hide any keys from the Select widgets:
import numpy as np
from bokeh.models import ColumnDataSource
from bokeh.plotting import Figure
from bokeh.models.widgets import Select, TextInput
from bokeh.models.layouts import HBox, VBox
import bokeh.io
from bokeh.io import curdoc
from bokeh.models import CustomJS
N = 200
# Define the data to be used
x = np.linspace(0, 4. * np.pi, N)
y = 3 * np.cos(2 * np.pi * x + np.pi * 0.2)
z = 0.5 * np.sin(2 * np.pi * 0.8 * x + np.pi * 0.4)
data = {'x': x, 'cos': y, 'sin': z}
source = ColumnDataSource(data=data)
codex = """
var column = cb_obj.value;
line1.glyph.x.field = column;
source.trigger('change')
"""
codey = """
var column = cb_obj.value;
line1.glyph.y.field = column;
source.trigger('change')
"""
# create a new plot
plot = Figure(title=None)
# Make a line and connect to data source
line1 = plot.line(x="x", y="cos", line_color="#F46D43", line_width=6, line_alpha=0.6, source=source)
callbackx = CustomJS(args=dict(line1=line1, source=source), code=codex)
callbacky = CustomJS(args=dict(line1=line1, source=source), code=codey)
# Add list boxes for selecting which columns to plot on the x and y axis
yaxis_select = Select(title="Y axis:", value="cos",
options=data.keys(),
callback=callbacky
)
xaxis_select = Select(title="X axis:", value="x",
options=data.keys(),
callback=callbackx
)
# Text input as a title
text = TextInput(title="title", value='my sine wave plotter')
# Layout widgets next to the plot
controls = VBox(text, yaxis_select, xaxis_select)
layout = HBox(controls, plot, width=800)
# bokeh.io.show(layout)
curdoc().add_root(layout)
curdoc().title = "Sliders"
I separated the code strings for clarity, but ImportanceOfBeingErnest's string.format() use was quite neat.
Unfortunately I couldn't get a fully Python solution for editing these particular fields, although ImportanceOfBeingErnest's solution doesn't actually need JavaScript at all (but does change the datasource)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With