0
votes

I am following the example code I've attached below. It currently displays the plot and associated slider embedded in the doc returned by the "modify_doc" function in the notebook. However, I'd like to deploy it into it's own server to make a GUI, while still maintaining it's ability to run the callback when the slider is changed and update the plot. However when I try to use Panel Pyviz to deploy it, it just displays the message "< bokeh.document.document.Document object at 0x00000193EC5FFE80 >" on the server that pops up. How do I deploy the doc in a way that will display the image?

import numpy as np
import holoviews as hv

from bokeh.io import show, curdoc
from bokeh.layouts import layout
from bokeh.models import Slider, Button

renderer = hv.renderer('bokeh').instance(mode='server')

# Create the holoviews app again
def sine(phase):
    xs = np.linspace(0, np.pi*4)
    return hv.Curve((xs, np.sin(xs+phase))).opts(width=800)

stream = hv.streams.Stream.define('Phase', phase=0.)()
dmap = hv.DynamicMap(sine, streams=[stream])

# Define valid function for FunctionHandler
# when deploying as script, simply attach to curdoc
def modify_doc(doc):
    # Create HoloViews plot and attach the document
    hvplot = renderer.get_plot(dmap, doc)

    # Create a slider

    def slider_update(attrname, old, new):
        # Notify the HoloViews stream of the slider update 
        stream.event(phase=new)

    start, end = 0, np.pi*2
    slider = Slider(start=start, end=end, value=start, step=0.2, title="Phase")
    slider.on_change('value', slider_update)

    # Combine the holoviews plot and widgets in a layout
    plot = layout([
    [hvplot.state],
    [slider]], sizing_mode='fixed')

    doc.add_root(plot)
    return doc

# To display in the notebook
show(modify_doc, notebook_url='localhost:8888')

# To display in a script
doc = modify_doc(curdoc()) 

# To deploy to separate server using Panel (attempt, doesn't work. Just displays #"<bokeh.document.document.Document object at 0x00000193EC5FFE80>":

graph = pn.Row (doc)
graph.show()
1

1 Answers

0
votes

There is some mixing of very different APIs going on in your example. Panel is meant to simplify some of Bokeh's APIs so a lot of what you're doing here isn't necessary. That said I'll provide a few versions which are either using only Panel components or combining Panel and bokeh components.

To start with this is how I might write this example using just Panel:

import numpy as np
import panel as pn
import holoviews as hv

pn.extension()

start, end = 0, np.pi*2
slider = pn.widgets.FloatSlider(start=start, end=end, value=start, step=0.2, name="Phase")

@pn.depends(phase=slider.param.value)
def sine(phase):
    xs = np.linspace(0, np.pi*4)
    return hv.Curve((xs, np.sin(xs+phase))).opts(width=800)

dmap = hv.DynamicMap(sine)

row = pn.Row(dmap, slider)

# Show in notebook
row.app('localhost:8888')

# Open a server
row.show()

# To deploy this using `panel serve` or `bokeh serve`
row.servable()

In your example you instead use various bokeh components, this is also possible and might be desirable if you've already got bokeh code.

import numpy as np
import holoviews as hv
import panel as pn

from bokeh.models import Slider, Button

# Create the holoviews app again
def sine(phase):
    xs = np.linspace(0, np.pi*4)
    return hv.Curve((xs, np.sin(xs+phase))).opts(width=800)

stream = hv.streams.Stream.define('Phase', phase=0.)()
dmap = hv.DynamicMap(sine, streams=[stream])

def slider_update(attrname, old, new):
    # Notify the HoloViews stream of the slider update 
    stream.event(phase=new)

start, end = 0, np.pi*2
slider = Slider(start=start, end=end, value=start, step=0.2, title="Phase")
slider.on_change('value', slider_update)

graph = pn.Row(dmap, slider)

# Show in notebook
row.app('localhost:8888')

# Open a server
row.show()

# To deploy this using `panel serve` or `bokeh serve`
row.servable()

If you want to serve these apps to multiple people I'd definitely recommend using panel serve but if you really want to make a script you can run with python script.py you should do this:

def app():
    start, end = 0, np.pi*2
    slider = pn.widgets.FloatSlider(start=start, end=end, value=start, step=0.2, name="Phase")

    @pn.depends(phase=slider.param.value)
    def sine(phase):
        xs = np.linspace(0, np.pi*4)
        return hv.Curve((xs, np.sin(xs+phase))).opts(width=800)

    dmap = hv.DynamicMap(sine)
    return pn.Row(dmap, slider)

pn.serve({'/': app})

This requires latest Panel, but ensures that even if you run the app as a script that each user gets a new instance of the app which does not share state with all others.