Bokeh standalone embed serve folder for content (v 3.3.0 2023)

96 Views Asked by At

I'm trying to run a server on a remote location to render a plot with circles so that the tooltip includes an image of the item hovered on.

I'm using Bokeh 3.3.0 and tornado. This is my code:

from bokeh.layouts import column
from bokeh.models import ColumnDataSource,HoverTool
from bokeh.plotting import figure
from bokeh.server.server import Server

def bkapp(doc):
    
    #get necessary data...
    dfsource=ColumnDataSource(df)

    hover = HoverTool(tooltips ="""
        <div>
            <div>
                <img
                    src="@_id.png"
                    style="float: left; margin: 0px 5px 5px 0px;"
                    border="2"
                ></img>
            </div>
            <div>
                <span style="font-size: 15px;">@_id</span>
                <span style="font-size: 10px; color: #696;">(@ucx, @ucy)</span>
            </div>
        """)

    # create a plot
    p = figure(sizing_mode="stretch_width", max_width=2000, height=800,
              tools=["pan", 'wheel_zoom',"tap","reset"])
    p.add_tools(hover)

    circle = p.circle(source=dfsource,x="ucx",y="ucy")

    doc.add_root(p)

server = Server({'/': bkapp}, num_procs=4)
server.start()

if __name__ == '__main__':
    print('Opening Bokeh application on http://localhost:5006/')

    server.io_loop.add_callback(server.show, "/")
    server.io_loop.start()

The plot works as intended but the images are not accessible.

The folder structure is simple:

-cwd
--myapp.py
--imagefolder/

I have tried setting src as:

  • src="imagefolder/@_id.png"
  • src="file://@_id.png"
  • src="http://localhost:5006/@_id.png"

and other combinations but I think the problem is that the folder is not being served. Can someone tell me how can I tell tornado, through the Bokeh API to please serve this folder of images so that I can do something like http://localhost:5006/imagefolder/_id.png and be able to see the image?

1

There are 1 best solutions below

0
lesolorzanov On

So I basically endedup embedding a bokeh app in a flask app, which is not elegant at all and speaks for the poor idea if mixing server programming and visualization both in bokeh. But any ways this is the idea:

from threading import Thread

from flask import Flask, render_template,request, send_from_directory
from tornado.ioloop import IOLoop

from bokeh.embed import server_document

#from bokeh. more bokeh imports...

app = Flask(__name__, static_url_path='')


def display_event():
    """
    Function to build a suitable CustomJS to display the current event
    in the div model.
    """
    return CustomJS(args={}, code=f"""
        #more custom js
    """
    )



def bkapp(doc):
    
    
    print(os.getcwd())
    dfsource=ColumnDataSource(datacontainer.df)
    dfsignssource=ColumnDataSource(datacontainer.dfsigns)
    doc.theme = "dark_minimal"

    hover = HoverTool(tooltips ="""
        <div>
            <div>
                <img
                    src="http://localhost:8000/img/@_id.png" width="500" 
                    style="float: left; margin: 0px 5px 5px 0px;"
                    border="2"
                ></img>
            </div>
            <div>
                <span style="font-size: 15px;">@_id</span>
                <span style="font-size: 10px; color: #696;">(@ucx, @ucy)</span>
            </div>
        """)

    # create a plot
    p = figure(sizing_mode="stretch_width", max_width=2000, height=800,
              tools=["pan","box_select", "wheel_zoom","tap","reset"])
    p.add_tools(hover)

    circle = p.circle(source=dfsource,x="ucx",y="ucy",
                          fill_color="color", 
                          size=7,line_width=0.3,line_color="white",name="circles")

    
    p.js_on_event(events.SelectionGeometry, display_event())

    # show the results
    doc.add_root(p)

#bokeh app
@app.route('/', methods=['GET'])
def bkapp_page():
    script = server_document('http://localhost:5006/bkapp')
    return render_template("embed.html", script=script, template="Flask")

#serving images with flask to be consumed by bokeh
@app.route('/img/<path:path>')
def send_js(path):
    return send_from_directory('images', path)

def bk_worker():
    # Can't pass num_procs > 1 in this configuration. If you need to run multiple
    # processes, see e.g. flask_gunicorn_embed.py
    server = Server({'/bkapp': bkapp}, io_loop=IOLoop(), allow_websocket_origin=["localhost:8000"])
    server.start()
    server.io_loop.start()

Thread(target=bk_worker).start()

if __name__ == '__main__':
    print('Opening single process Flask app with embedded Bokeh application on http://localhost:8000/')
    print()
    print('Multiple connections may block the Bokeh app in this configuration!')
    print('See "flask_gunicorn_embed.py" for one way to run multi-process')
    app.run(port=8000)
    

there is more info here github.com/realpython/flask-bokeh-example and here bokeh user guide