Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Bokeh interactively changing the columns being plotted

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"
like image 866
Redlegjed Avatar asked Feb 06 '23 16:02

Redlegjed


1 Answers

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)

like image 143
MInvertedM Avatar answered Feb 11 '23 16:02

MInvertedM