holoviz / holoviews

With Holoviews, your data visualizes itself.
https://holoviews.org
BSD 3-Clause "New" or "Revised" License
2.7k stars 403 forks source link

Widget rendering in Jupyter but not in Bokeh App #3814

Closed anderl80 closed 5 years ago

anderl80 commented 5 years ago

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()
anderl80 commented 5 years ago

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.

philippjfr commented 5 years ago

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.

anderl80 commented 5 years ago

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?

philippjfr commented 5 years ago

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

anderl80 commented 5 years ago

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

anderl80 commented 5 years ago
    plot_map = create_map_figure(df)
    plot_map_rendered = pn.panel(plot_map)._get_server()
    doc.add_root(plot_map_rendered)
philippjfr commented 5 years ago

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?

anderl80 commented 5 years ago

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.

anderl80 commented 5 years ago

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()
philippjfr commented 5 years ago

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)
anderl80 commented 5 years ago

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)
anderl80 commented 5 years ago

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.

philippjfr commented 5 years ago

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.

philippjfr commented 5 years ago

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

anderl80 commented 5 years ago

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

philippjfr commented 5 years ago

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

github-actions[bot] commented 2 weeks ago

This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.