Mathusalem
Mathusalem

Reputation: 877

Sliding through images with Bokeh Slider

I am trying to convey a large amount of scientific data seamlessly with the help of sliders.

I am beginning with Bokeh and have close to no knowledge in javascript. I tried to setup a first approach to be able to slide through two images, but I cannot get the image to refresh.

Suppose I have 1.png and 2.png in my folder.

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

output_file('image.html')

source = ColumnDataSource(data=dict(url=['1.png']))

p = Figure(x_range=(0,1), y_range=(0,1))

callbackimg = CustomJS(args=dict(source=source), code="""
    var data = source.get('data');
    var f = cb_obj.get('value')
    old = data['url'][0]
    data['url'][0]= old.replace("1","f")
    source.trigger('change');
""")

p.image_url('url',source=source, x=0, y=1,w=1,h=1)
slider = Slider(start=1, end=2, value=1, step=1, title="image number",     callback=callbackimg)

layout = vform(slider, p)
show(layout)

I adapted examples from Bokeh Widget Doc for the slider and working with images in bokeh.

My idea is that the slider, through the callbackimg snippet, will modify the source which contains the url, i.e the name of the image to load. I thought, for now, that a simple access to the string in the source, and a replacement through the current value of the slider (so it should jump from 1.png to 2.png as the slider goes from 1 to 2) should do the trick.

However, nothing is changing. I suspect I am doing something wrong in the Javascript snippet.

Thanks for any feedback

EDIT: I edited the code according to the suggestions of @bigreddot, but now the slider shows simply an empty figure when sliding in position '2'. EDIT2: Solved the issue, in my answer below

Upvotes: 3

Views: 2184

Answers (4)

Michele
Michele

Reputation: 3061

Another option is to create in advance a data source with all the image urls and use the slider to select the correct image.

Given the image names:

import pandas as pd
imgs = ['1.png', '2.png', '3.png']
df = pd.DataFrame(imgs, columns=['imgs'])
print(df.head())

     imgs
0   1.png
1   2.png
2   3.png

You can do:

from bokeh.layouts import layout
from bokeh.plotting import figure, show
from bokeh.models import CustomJS, Slider, ColumnDataSource

source = ColumnDataSource(df)
p = figure(x_range=(0,1), y_range=(0,1))
im = p.image_url(url=[source.data['imgs'][0]], x=0, y=1, w=1, h=1)

cb = CustomJS(args=dict(im=im, source=source), code="""
    im.data_source.data['url'] = [source.data['imgs'][cb_obj.value]];
    im.data_source.change.emit();
""")
slider = Slider(start=0, end=2, value=0, step=1, title="image number", callback=cb)
show(layout([slider, p]))

Upvotes: 0

suraj deshmukh
suraj deshmukh

Reputation: 188

I have created a sample example where image changes as per the slider value. If slider value is 1 then app will display map-marker.png image and if value is 2 then app will display google_logo.png image.

With the help of Div you can do this task very easily.

map-marker.png map-marker.png

google_logo.png google_logo.png

Steps:

  1. Create a folder named 'image' in /usr/local/lib/python2.7/dist-packages/bokeh/server/static this path
  2. After creating folder in above mentioned location copy google_logo.png and map-marker.png images to /usr/local/lib/python2.7/dist-packages/bokeh/server/static/image this location. And save the following code as app.py and run it using bokeh serve app.py --show command.

    from bokeh.io import curdoc
    from bokeh.layouts import row
    from bokeh.models.widgets import Slider, Div
    
    def change_img(attr, old, new):
        if slider.value == 1:
            logo.text = """<img src="static/image/map-marker.png"  alt="logo">"""
        else:
            logo.text = """<img src="static/image/google_logo.png"  alt="logo">"""
    
    slider = Slider(start=1, end=2, value=1, title="image number")
    slider.on_change('value', change_img)
    logo = Div(text="""<img src="static/image/logo.png"  alt="logo">""")
    
    curdoc().add_root(row(slider,logo))
    curdoc().title = "Image Demo"
    

working app.py working

tested on bokeh version 0.12.13

Upvotes: 1

Mathusalem
Mathusalem

Reputation: 877

@bigreddot answer is correct, but var f is a number, so in the replace, I need to write f.toString(10). Code that does what I want:

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

output_file('image.html')

source = ColumnDataSource(data=dict(url=['1.png']))

p = Figure(x_range=(0,1), y_range=(0,1))

callbackimg = CustomJS(args=dict(source=source), code="""
    var data = source.get('data');
    var f = cb_obj.get('value')
    old = data['url'][0]
    data['url'][0]= old.replace("1",f.toString(10))
    source.trigger('change');
""")

p.image_url('url',source=source, x=0, y=1,w=1,h=1)
slider = Slider(start=1, end=2, value=1, step=1, title="image number",     callback=callbackimg)

layout = vform(slider, p)
show(layout)

Upvotes: 2

bigreddot
bigreddot

Reputation: 34568

The problem is this:

url = data['url'][0]
url.replace("1","f")

The replace method returns a new string (which you immediately discard), so you are not actually changing anything in the column data source. You need something like:

old = data['url'][0]
data['url'] = old.replace("1","f")

Upvotes: 1

Related Questions