Holoviews: Widget rendering in Jupyter but not in Bokeh App

Created on 10 Jul 2019  路  16Comments  路  Source: holoviz/holoviews

I wrote a small application that renders beautifully in Juypter and shows the dropdown menu with the additional but unused dimension "date".

But when I deploy it to a bokeh app, the widget is not shown.

# Create the main plot
def create_map_figure(data):
    import geoviews as gv
    import holoviews as hv
    import geoviews.tile_sources as gts
    from holoviews import dim, opts
    from colorcet import rainbow
    gv.extension("bokeh")

    plot_width = 800
    plot_height = 400
    tile_opts = dict(height=plot_height, xaxis=None, yaxis=None, show_grid=False)
    map_tiles = gts.CartoLight.opts(style=dict(alpha=1), plot=tile_opts)

    points_ds = gv.Dataset(data[["lon", "lat", "val", "date"]])

    points = (points_ds.to(gv.Points, kdims=["lon", "lat"], vdims=["val"])
              .options(active_tools=['wheel_zoom'], tools=["hover"])
              .opts(colorbar=True, cmap=rainbow)).opts(opts.Points(color=dim('val'), size=5))

    return map_tiles * points.redim.range(change=(0, 10))


def modify_doc(doc):
    import holoviews as hv
    from bokeh.layouts import column, row
    from bokeh.models import Div

    conn = get_db_connection()
    df = get_data(conn)
    plot_map = create_map_figure(df)
    renderer = hv.renderer('bokeh').instance(mode='server')
    plot_map_rendered = renderer.app(plot_map, doc)

    doc.add_root(plot_map_rendered.state)

bokeh_app = Application(FunctionHandler(modify_doc))

server = Server(
        {'/': bokeh_app},
        io_loop=io_loop,
        allow_websocket_origin=ALLOW_WEBSOCKET_ORIGIN,
        debug=True,
        **{'port': PORT, 'address': HOST}
        )
server.start()

if __name__ == '__main__':
    io_loop.add_callback(server.show, "/")
    io_loop.start()

Most helpful comment

Here you go:

import geoviews as gv
import holoviews as hv
import numpy as np
import geoviews.tile_sources as gts
from holoviews import dim, opts
from colorcet import rainbow
gv.extension("bokeh")

plot_width = 800
plot_height = 400
tile_opts = dict(height=plot_height, xaxis=None, yaxis=None, show_grid=False)
map_tiles = gts.CartoLight.opts(style=dict(alpha=1), plot=tile_opts)

point_ds = gv.Dataset(
    (np.random.rand(100), np.random.rand(100), np.random.randint(0, 3, 100), np.random.randn(100)),
    ['lon', 'lat', 'group'], 'val')

points = point_ds.to(gv.Points, ['lon', 'lat']).opts(
    active_tools=['wheel_zoom'], tools=["hover"],
    colorbar=True, cmap=rainbow, color=dim('val'), size=5)

plot = map_tiles * points.redim.range(change=(0, 10))

server = pn.panel(plot)._get_server(
    show=True, port=PORT, websocket_origin=ALLOW_WEBSOCKET_ORIGIN,
    address=HOST, debug=True)

server.start()

How can I embed a panel app using params API in that surrounding above?

Not sure I follow, could you expand on this?

All 16 comments

I'm sorry, after reading http://holoviews.org/user_guide/Plots_and_Renderers.html I realised I have to extract the widget myself.. I tried static_html with no effort. Still got no idea how to do it.

At this point I'd strongly recommend using Panel to deploy the HoloViews app. It also has a more robust implementation of the HoloViews widgets and provides a lot more flexibility in laying things out or even replacing widgets.

No other chance to get this working as this is a simple app? Is it easy to implement panel in this "bokeh server started using the library in tornado" setup?

Yes, pn.panel(hv_obj)._get_server(...). I think I'll make that method public.

Do you have a minimal example in that bokeh server setup?

  • Could you please provide a short code snipped how to deploy the above app with your suggested function? I just can't make it work.
    plot_map = create_map_figure(df)
    plot_map_rendered = pn.panel(plot_map)._get_server()
    doc.add_root(plot_map_rendered)
  • How can I embed a panel app using params API in that surrounding above?

Here you go:

import geoviews as gv
import holoviews as hv
import numpy as np
import geoviews.tile_sources as gts
from holoviews import dim, opts
from colorcet import rainbow
gv.extension("bokeh")

plot_width = 800
plot_height = 400
tile_opts = dict(height=plot_height, xaxis=None, yaxis=None, show_grid=False)
map_tiles = gts.CartoLight.opts(style=dict(alpha=1), plot=tile_opts)

point_ds = gv.Dataset(
    (np.random.rand(100), np.random.rand(100), np.random.randint(0, 3, 100), np.random.randn(100)),
    ['lon', 'lat', 'group'], 'val')

points = point_ds.to(gv.Points, ['lon', 'lat']).opts(
    active_tools=['wheel_zoom'], tools=["hover"],
    colorbar=True, cmap=rainbow, color=dim('val'), size=5)

plot = map_tiles * points.redim.range(change=(0, 10))

server = pn.panel(plot)._get_server(
    show=True, port=PORT, websocket_origin=ALLOW_WEBSOCKET_ORIGIN,
    address=HOST, debug=True)

server.start()

How can I embed a panel app using params API in that surrounding above?

Not sure I follow, could you expand on this?

Many thanks! I definately have to check this. In the meantime I found that I can serve a panel (took the MPGExplorer() example as it is) row using:

def modify_doc(doc):
    row = pn.Row(explorer.param, explorer.plot)
    doc.add_root(plot_map_rendered)

complementary to the rest of my app above. Worked beautifully locally, now I try to deploy to Cloud Foundry.

I did this to make your example work, but using python app.py it loads endlessly in the browser and shows nothing.

import os
from tornado.ioloop import IOLoop

# if running locally, listen on port 5000
PORT = int(os.getenv('PORT', '5000'))
HOST = "0.0.0.0"

print("HOST", HOST)
print("PORT", PORT)

# this is set in the manifest
try:
    ALLOW_WEBSOCKET_ORIGIN = os.getenv("ALLOW_WEBSOCKET_ORIGIN").split(',')
except:
    ALLOW_WEBSOCKET_ORIGIN = ['localhost:{0}'.format(PORT)]

print("ALLOW_WEBSOCKET_ORIGIN:", ALLOW_WEBSOCKET_ORIGIN)

io_loop = IOLoop.current()

import geoviews as gv
import numpy as np
import geoviews.tile_sources as gts
from holoviews import dim, opts
from colorcet import rainbow
import panel as pn
gv.extension("bokeh")

plot_width = 800
plot_height = 400
tile_opts = dict(height=plot_height, xaxis=None, yaxis=None, show_grid=False)
map_tiles = gts.CartoLight.opts(style=dict(alpha=1), plot=tile_opts)

point_ds = gv.Dataset(
    (np.random.rand(100), np.random.rand(100), np.random.randint(0, 3, 100), np.random.randn(100)),
    ['lon', 'lat', 'group'], 'val')

points = point_ds.to(gv.Points, ['lon', 'lat']).opts(
    active_tools=['wheel_zoom'], tools=["hover"],
    colorbar=True, cmap=rainbow, color=dim('val'), size=5)

plot = map_tiles * points.redim.range(change=(0, 10))

server = pn.panel(plot)._get_server(
    show=True, port=PORT, websocket_origin=ALLOW_WEBSOCKET_ORIGIN,
    address=HOST, debug=True)

server.start()

if __name__ == '__main__':
    io_loop.add_callback(server.show, "/")
    io_loop.start()

Ah, yes I tested in a notebook where the IOLoop is already running. I'd probably just change it to:

if __name__ == '__main__':
    pn.panel(plot)._get_server(
        show=True, port=PORT, websocket_origin=ALLOW_WEBSOCKET_ORIGIN,
        address=HOST, debug=True, start=True)

You made someone happy, works like a charm.

import os
from tornado.ioloop import IOLoop

# if running locally, listen on port 5000
PORT = int(os.getenv('PORT', '5000'))
HOST = "0.0.0.0"

print("HOST", HOST)
print("PORT", PORT)

# this is set in the manifest
try:
    ALLOW_WEBSOCKET_ORIGIN = os.getenv("ALLOW_WEBSOCKET_ORIGIN").split(',')
except:
    ALLOW_WEBSOCKET_ORIGIN = ['localhost:{0}'.format(PORT)]

print("ALLOW_WEBSOCKET_ORIGIN:", ALLOW_WEBSOCKET_ORIGIN)

io_loop = IOLoop.current()

import geoviews as gv
import numpy as np
import geoviews.tile_sources as gts
from holoviews import dim, opts
from colorcet import rainbow
import panel as pn
gv.extension("bokeh")

plot_width = 800
plot_height = 400
tile_opts = dict(height=plot_height, xaxis=None, yaxis=None, show_grid=False)
map_tiles = gts.CartoLight.opts(style=dict(alpha=1), plot=tile_opts)

point_ds = gv.Dataset(
    (np.random.rand(100), np.random.rand(100), np.random.randint(0, 3, 100), np.random.randn(100)),
    ['lon', 'lat', 'group'], 'val')

points = point_ds.to(gv.Points, ['lon', 'lat']).opts(
    active_tools=['wheel_zoom'], tools=["hover"],
    colorbar=True, cmap=rainbow, color=dim('val'), size=5)

plot = map_tiles * points.redim.range(change=(0, 10))

if __name__ == '__main__':
    pn.panel(plot)._get_server(
        show=True, port=PORT, websocket_origin=ALLOW_WEBSOCKET_ORIGIN,
        address=HOST, debug=True, start=True)

The way you showed me to start the application starts only one server. So if another user connects to the server, they see the same applications and the user 2 sees the changes user 1 makes in the widgets.

Wasn't that true for your previous approach as well? If that's not the case we should open a panel issue.

The way servers are usually launched is by using:

pn.panel(plot).servable()

and then launching the server with panel serve script.py.

Oh, I realize what the issue is. Let me think about it.

Do you have an idea here or did you find a solution?

Closing in favor of https://github.com/pyviz/panel/issues/644. This is something we'll have to solve at the Panel level.

Was this page helpful?
0 / 5 - 0 ratings