\n",
" This Bokeh app below served by a Bokeh server that has been embedded\n",
" in another web app framework. For more information see the section\n",
"
Embedding Bokeh Server as a Library\n",
" in the User's Guide.\n",
"
\n",
" {{ script|safe }}\n",
"\n",
"\n",
"```"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"If you wish to replicate navigate to the `examples/gallery/apps/flask` directory and follow the these steps:\n",
"\n",
"* Step One: call `python holoviews_app.py` in the terminal (this will start the Panel/Bokeh server)\n",
"* Step Two: open a new terminal and call `python flask_app.py` (this will start the Flask application)\n",
"* Step Three: go to web browser and type `localhost:5000` and the app will appear"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Combining HoloViews and Panel or Bokeh Plots/Widgets"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"While HoloViews provides very convenient ways of creating an app it is not as fully featured as Bokeh itself is. Therefore we often want to extend a HoloViews based app with Panel or Bokeh plots and widgets. Here we will discover to achieve this with both Panel and then the equivalent using pure Bokeh."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import holoviews as hv\n",
"import numpy as np\n",
"import panel as pn\n",
"\n",
"# Create the holoviews app again\n",
"def sine(phase):\n",
" xs = np.linspace(0, np.pi*4)\n",
" return hv.Curve((xs, np.sin(xs+phase))).opts(width=800)\n",
"\n",
"stream = hv.streams.Stream.define('Phase', phase=0.)()\n",
"dmap = hv.DynamicMap(sine, streams=[stream])\n",
"\n",
"start, end = 0, np.pi*2\n",
"slider = pn.widgets.FloatSlider(start=start, end=end, value=start, step=0.2, name=\"Phase\")\n",
"\n",
"# Create a slider and play buttons\n",
"def animate_update():\n",
" year = slider.value + 0.2\n",
" if year > end:\n",
" year = start\n",
" slider.value = year\n",
"\n",
"def slider_update(event):\n",
" # Notify the HoloViews stream of the slider update \n",
" stream.event(phase=event.new)\n",
"\n",
"slider.param.watch(slider_update, 'value')\n",
"\n",
"def animate(event):\n",
" if button.name == '► Play':\n",
" button.name = '❚❚ Pause'\n",
" callback.start()\n",
" else:\n",
" button.name = '► Play'\n",
" callback.stop()\n",
"\n",
"button = pn.widgets.Button(name='► Play', width=60, align='end')\n",
"button.on_click(animate)\n",
"callback = pn.state.add_periodic_callback(animate_update, 50, start=False)\n",
"\n",
"app = pn.Column(\n",
" dmap,\n",
" pn.Row(slider, button)\n",
")\n",
"\n",
"app"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"If instead we want to deploy this we could add `.servable` as discussed before or use `pn.serve`. Note however that when using `pn.serve` all sessions will share the same state therefore it is best to \n",
"wrap the creation of the app in a function which we can then provide to `pn.serve`. For more detail on deploying Panel applications also see the [Panel server deployment guide](https://panel.holoviz.org/user_guide/Server_Deployment.html).\n",
"\n",
"Now we can reimplement the same example using Bokeh allowing us to compare and contrast the approaches:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"import holoviews as hv\n",
"\n",
"from bokeh.io import show, curdoc\n",
"from bokeh.layouts import layout\n",
"from bokeh.models import Slider, Button\n",
"\n",
"renderer = hv.renderer('bokeh').instance(mode='server')\n",
"\n",
"# Create the holoviews app again\n",
"def sine(phase):\n",
" xs = np.linspace(0, np.pi*4)\n",
" return hv.Curve((xs, np.sin(xs+phase))).opts(width=800)\n",
"\n",
"stream = hv.streams.Stream.define('Phase', phase=0.)()\n",
"dmap = hv.DynamicMap(sine, streams=[stream])\n",
"\n",
"# Define valid function for FunctionHandler\n",
"# when deploying as script, simply attach to curdoc\n",
"def modify_doc(doc):\n",
" # Create HoloViews plot and attach the document\n",
" hvplot = renderer.get_plot(dmap, doc)\n",
"\n",
" # Create a slider and play buttons\n",
" def animate_update():\n",
" year = slider.value + 0.2\n",
" if year > end:\n",
" year = start\n",
" slider.value = year\n",
"\n",
" def slider_update(attrname, old, new):\n",
" # Notify the HoloViews stream of the slider update \n",
" stream.event(phase=new)\n",
" \n",
" start, end = 0, np.pi*2\n",
" slider = Slider(start=start, end=end, value=start, step=0.2, title=\"Phase\")\n",
" slider.on_change('value', slider_update)\n",
" \n",
" callback_id = None\n",
"\n",
" def animate():\n",
" global callback_id\n",
" if button.label == '► Play':\n",
" button.label = '❚❚ Pause'\n",
" callback_id = doc.add_periodic_callback(animate_update, 50)\n",
" else:\n",
" button.label = '► Play'\n",
" doc.remove_periodic_callback(callback_id)\n",
" button = Button(label='► Play', width=60)\n",
" button.on_click(animate)\n",
" \n",
" # Combine the holoviews plot and widgets in a layout\n",
" plot = layout([\n",
" [hvplot.state],\n",
" [slider, button]], sizing_mode='fixed')\n",
" \n",
" doc.add_root(plot)\n",
" return doc\n",
"\n",
"# To display in the notebook\n",
"show(modify_doc, notebook_url='localhost:8888')\n",
"\n",
"# To display in a script\n",
"# doc = modify_doc(curdoc()) "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"