Skip to content
Advertisement

Reverse a Bokeh Button operation by clicking the button again

I have a vision for a dashboard where you can click on one of several Buttons to perform additive operations, and undo the operation by clicking on the button again. Actually undo is the wrong word, as I do not want the second click to restore it to an original state as that could interfere with the results of another button, but I would like the second click to take out what it put in. As an example I have the following problem where pushing a button labeled Apple, adds a value of 3.0 to every data point, which updates the plot of circle glyphs. My question is this, “is there a way I can set up the code where pressing the button again will subtract a value of 3.0?”

from bokeh.models import ColumnDataSource, CustomJS, Button
from bokeh.plotting import Figure, output_file, show
from bokeh.layouts import column

output_file("js_on_change.html")

x = [1.2, 2.4, 0.8, 5.6, 12.1, 8.8]
y = [3.4, 1.1, 4.2, 6.6, 1.8, 12.1]
z = ['Apple', 'Apple', 'Orange', 'Orange', 'Orange', 'Orange']

data = dict(x=x, y=y, z=z)
source = ColumnDataSource(data)

plot = Figure(plot_width=400, plot_height=400)
plot.circle('x', 'y', source=source, size=8, line_alpha=0.6)

callback1 = CustomJS(args=dict(source=source), code="""
    var data = source.data;
    var f = 3.0
    var x = data['x']
    var y = data['y']
    for (var i = 0; i < x.length; i++) {
        y[i] = y[i] + f
    }
    source.change.emit();
""")

button = Button(label="Apple", button_type="success")
button.js_on_click(callback1)

layout = column(button, plot)

show(layout)

Advertisement

Answer

One possible solution is to add a second CDS to your custom JavaScript to count the clicks.

CustomJS(args=dict(source=source, counter=counter), code="...")

In you JavaScript you have to add a few lines. If the number of clicks is even, you add f, else you substract it.

var count = counter.data;
var c = count['counter']
if (c[0]%2==1) f = -1*f;
c[0]++
counter.change.emit();

The created figure will work like this:

toggle

Complete code

from bokeh.models import ColumnDataSource, CustomJS, Button, Range1d
from bokeh.plotting import Figure, output_notebook, show
from bokeh.layouts import column
output_notebook()

x = [1]
y = [3]
z = ['Apple']

data = dict(x=x, y=y, z=z)
source = ColumnDataSource(data)

counter = [0]
counter = dict(counter=counter)
counter = ColumnDataSource(counter)

plot = Figure(plot_width=400, plot_height=400)
plot.circle('x', 'y', source=source, size=8, line_alpha=0.6)
plot.y_range = Range1d(0,7)

callback1 = CustomJS(args=dict(source=source, counter=counter), code="""
    var f = 3.0
    
    var count = counter.data;
    var c = count['counter']
    if (c[0]%2==1) f = -1*f;
    c[0]++
    counter.change.emit();
    
    var data = source.data;
    var x = data['x']
    var y = data['y']
    for (var i = 0; i < x.length; i++) {
        y[i] = y[i] + f
    }
    source.change.emit();
""")

button = Button(label="Apple", button_type="success")
button.js_on_click(callback1)

layout = column(button, plot)

show(layout)
User contributions licensed under: CC BY-SA
2 People found this is helpful
Advertisement