Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Bokeh: implementing custom javascript in an image plot

I am trying to combine these two examples in Bokeh:

http://docs.bokeh.org/en/latest/docs/gallery/image.html http://docs.bokeh.org/en/latest/docs/user_guide/interaction/callbacks.html#customjs-for-widgets

The idea seems simple. I want to plot the image shown in the first link and then vary the frequency of the sine function using an interactive slider:

import numpy as np

from bokeh.plotting import figure, show, output_file
from bokeh.models import CustomJS, ColumnDataSource, Slider
from bokeh.io import vform


N = 10

x = np.linspace(0, 10, N)
y = np.linspace(0, 10, N)
xx, yy = np.meshgrid(x, y)
d = np.sin(xx)*np.cos(yy)  

output_file("image.html", title="image.py example")

source = ColumnDataSource(data={'d': d, 'x': x, 'y': y})

p = figure(x_range=[0, 10], y_range=[0, 10])
p.image([source.data['d']], x=[0], y=[0], dw=[10], dh=[10], palette="Spectral11")

callback = CustomJS(args=dict(source=source), code="""
        var data = source.get('data');
        var f = cb_obj.get('value')
        x = data['x']
        y = data['y']
        d = data['d']
        for (i = 0; i < x.length; i++) {
            for (i = 0; i < x.length; i++){
                d[i][j] = Math.sin(f*x[i])*Math.cos(y[j])
        }
        source.trigger('change');
    """)

slider = Slider(start=0.1, end=4, value=1, step=.1, title="angular frequency", callback=callback)

layout = vform(slider, p)

show(layout)

The chart plots right, but the image never updates. The problem almost certainly exists how I am plotting the image:

p.image([source.data['d']], x=[0], y=[0], dw=[10], dh=[10], palette="Spectral11")

I don't think that's how you properly attach a plot to a source object. I am just passing in an array, which explains why the plot isn't updating when source changes, but I am not sure what the correct method is for the image function. If I change the statement to:

p.image(['d'], x=[0], y=[0], dw=[10], dh=[10], source=source, palette="Spectral11")

It won't plot correctly. I am not sure if this is just a syntax problem or a deeper issue. Any pointers would be appreciated. Thanks in advance.

like image 329
Casey P Avatar asked Nov 18 '15 19:11

Casey P


1 Answers

I was dealing with a similar problem for a couple of days. Finally I made it work. First, notice the brackets [] in the ColumnDataSource function. The data allows for multiple images. So, inside the callback function you should use [0] to get the data for one image. Also the use of 'x' and 'y' in the source for the image conflicts with the location of the image x=[0], and y[0], so I used xx and yy. I want to mention that I borrowed code from an example by tobyhodges color_sliders.py : a way to push slider information to a callback function that has been already defined. Here the code:

import numpy as np
from bokeh.plotting import figure, show, output_file
from bokeh.models import CustomJS, ColumnDataSource, Slider
from bokeh.io import vform

N = 100

x = np.linspace(0, 10, N)
y = np.linspace(0, 10, N)
xx, yy = np.meshgrid(x, y)
d = np.sin(xx)*np.cos(yy)  

output_file("image.html", title="image.py example")

source = ColumnDataSource(data={'d': [d], 'xx': [x], 'yy': [y]})

p = figure(x_range=[0, 10], y_range=[0, 10])
p.image(image="d", x=[0], y=[0], dw=[10], dh=[10], palette="Spectral11",source=source)

callback = CustomJS(args=dict(source=source), code="""
        var xx = source.get('data')['xx'][0];
        var yy = source.get('data')['yy'][0];
        var d = source.get('data')['d'][0];
        var f = slider.get('value');
        for (var i = 0; i < xx.length; i++) {
            for (var j = 0; j < yy.length; j++){
                d[i][j] = Math.sin(f * xx[i]) * Math.cos(yy[j]);
            }
        }
        source.trigger('change');
    """)

slider = Slider(start=0.1, end=4, value=1, step=.1, title="angular frequency", callback=callback)
callback.args['slider'] = slider
layout = vform(slider, p)
show(layout)

To avoid deprecation warnings in the latest version of bokeh (the version that I have just installed is 0.12.3) I have modified this code as follows. In this code, I don't use data source for the image. Inside CustomJS I pass the image handle "im" and get the data source as im.data_source.

import numpy as np
import bokeh
import bokeh.plotting

N = 100

x = np.linspace(0, 10, N)
y = np.linspace(0, 10, N)
xx, yy = np.meshgrid(x, y)
d = np.sin(xx)*np.cos(yy)  

source = bokeh.models.ColumnDataSource(data={'x': [x], 'y': [y]})

p = bokeh.plotting.figure(x_range=[0, 10], y_range=[0, 10])
im = p.image(image=[d], x=[0], y=[0], dw=[10], dh=[10], palette="Spectral11")

callback = bokeh.models.CustomJS(args=dict(source=source,im=im), code="""
        var x = source.data['x'][0];
        var y = source.data['y'][0];
        var image_source = im.data_source;
        var d = image_source.data['image'][0];
        var f = slider.value;
        for (var i = 0; i < x.length; i++) {
            for (var j = 0; j < y.length; j++){
                d[i][j] = Math.sin(f * x[i]) * Math.cos(f * y[j]);
            }
        }
        image_source.trigger('change');
    """)

slider = bokeh.models.Slider(start=0.1, end=4, value=1, step=.1, 
                             title="angular frequency", callback=callback)
callback.args['slider'] = slider
layout = bokeh.models.layouts.Column(slider, p)

bokeh.io.output_file("image.html", title="image.py example")
bokeh.io.save(layout)

Update for bokeh version 0.12.4:

Changes: the output now is for a Jupyter notebook. To obtain the html page, just follow previous versions. New in this bokeh version: The array on JavaScript is now a 1D array.

import numpy as np
import bokeh
import bokeh.plotting

N = 100

x = np.linspace(0, 10, N)
y = np.linspace(0, 10, N)
xx, yy = np.meshgrid(x, y)
d = np.sin(xx)*np.cos(yy)  

source = bokeh.models.ColumnDataSource(data={'x': [x], 'y': [y]})

p = bokeh.plotting.figure(plot_width=300, plot_height=300,x_range=[0, 10], y_range=[0, 10])
im = p.image(image=[d], x=[0], y=[0], dw=[10], dh=[10], palette="Spectral11")

callback = bokeh.models.CustomJS(args=dict(source=source,im=im), code="""
        var x = source.data['x'][0];
        var y = source.data['y'][0];
        var image_source = im.data_source;
        var d = image_source.data['image'][0];
        var f = slider.value;
        for (var j = 0; j < y.length; j++){
            for (var i = 0; i < x.length; i++) {
                d[j*y.length + i] = Math.sin(f * x[i]) * Math.cos(f * y[j]);
            }
        }
        image_source.trigger('change');
    """)

slider = bokeh.models.Slider(start=0.1, end=4, value=1, step=.1,
                             title="angular frequency",callback=callback)
callback.args['slider'] = slider
layout = bokeh.models.layouts.Row(p,slider)

bokeh.io.output_notebook()
bokeh.io.show(layout)

enter image description here

Update for bokeh version 0.12.15:

import numpy as np
import bokeh
import bokeh.plotting

N = 100

x = np.linspace(0, 10, N)
y = np.linspace(0, 10, N)
xx, yy = np.meshgrid(x, y)
d = np.sin(xx)*np.cos(yy)  

source = bokeh.models.ColumnDataSource(
    data=dict(values=[d],x_vals=[x],y_vals=[y]))

p = bokeh.plotting.figure(plot_width=300, plot_height=300,
    x_range=[0, 10], y_range=[0, 10])
p.image(image='values', x=0, y=0, dw=10, dh=10, 
    palette="Spectral11", source=source)

callback = bokeh.models.CustomJS(args=dict(source=source), code="""
    var f = slider.value;
    var x = source.data['x_vals'][0];
    var y = source.data['y_vals'][0];
    var d = source.data['values'][0];
    for (var j = 0; j < y.length; j++){
        for (var i = 0; i < x.length; i++) {
            d[j*y.length + i] = Math.sin(f*x[i]) * Math.cos(f*y[j]);
        }
    }
    source.change.emit();
    """)

slider = bokeh.models.Slider(start=0.1, end=4, value=1, step=.1,
    title="angular frequency",callback=callback)
callback.args['slider'] = slider
layout = bokeh.models.layouts.Row(p,slider)

bokeh.io.output_notebook()
bokeh.io.show(layout)

enter image description here

Update for bokeh version 2.4.2:

Things have change a little on recent versions. Code that works on version 2.4.2 (latest by February 2022) follows.

import numpy as np
import bokeh
import bokeh.plotting

N = 100

x = np.linspace(0, 10, N)
y = np.linspace(0, 10, N)
xx, yy = np.meshgrid(x, y)
d = np.sin(xx)*np.cos(yy)  

source = bokeh.models.ColumnDataSource(data=dict(
             x_values=x,  # needed in CustomJS for calculating d
             y_values=y,  # needed in CustomJS for calculating d
            ))
source_im = bokeh.models.ColumnDataSource(data=dict(
             image=[d],  # needed for p.image
             x=[0],  # needed for p.image
             y=[0],  # needed for p.image
             dw = [10],  # needed for p.image
             dh=[10]  # needed for p.image
            ))

p = bokeh.plotting.figure(plot_width=300, plot_height=300,
                          x_range=[0, 10], y_range=[0, 10])
im = p.image(source=source_im, palette="Spectral11")

slider_callback = bokeh.models.CustomJS(args=dict(
                source=source,source_im=source_im), code="""
        var x = source.data['x_values'];
        var y = source.data['y_values'];
        var d = source_im.data['image'][0];
        var f = slider.value;
        for (var j = 0; j < y.length; j++){
            for (var i = 0; i < x.length; i++) {
                d[j*y.length + i] = Math.sin(f * x[i]) * Math.cos(f * y[j]);
            }
        }
        source_im.change.emit();
    """)

slider = bokeh.models.Slider(start=0.1, end=4, value=1, step=.1,
                             title="angular frequency")
slider.js_on_change('value', slider_callback)
slider_callback.args['slider'] = slider
layout = bokeh.models.layouts.Row(p,slider)

bokeh.io.output_notebook()
bokeh.io.show(layout)
like image 154
Pablo Reyes Avatar answered Sep 23 '22 12:09

Pablo Reyes