Deploy and export#

One of the main design goals for Panel was to enable a seamless transition between interactively prototyping a dashboard and deploying it as a standalone server app. This notebook shows how to interactively display panels, embed static output, save a snapshot, and serve it as a separate web server app.

Configure output#

Panel objects are automatically displayed in a notebook and use Jupyter Comms to support communication between the rendered app and the Jupyter kernel. The display of a panel object in the notebook is simple: it only has to load the panel.extension first in order to initialise the required JavaScript in the notebook context.

[1]:
import panel as pn


pn.extension()

Optional dependencies#

In order to be able to use certain components such as Vega, LaTeX and Plotly-Plots, the corresponding Javascript components must also be loaded. To do this, you can simply include them as part of the call to pn.extension:

[2]:
pn.extension("vega", "katex")

Initialise JS and CSS#

Additional CSS and Javascript can also be specified with css_files, js_files and raw_css. js_files should be specified as a dictionary mapping from the exported JS module name to the URL with the JS components, while css_files can be defined as a list:

[3]:
pn.extension(
    js_files={"deck": "https://unpkg.com/deck.gl@~5.2.0/deckgl.min.js"},
    css_files=[
        "https://api.tiles.mapbox.com/mapbox-gl-js/v0.44.1/mapbox-gl.css"
    ],
)

With this raw_css argument you can define a list of strings with CSS that should be published as part of the notebook and the app.

Providing keyword arguments with extension is equivalent to specifying with pn.config. pn.config is the preferred approach to add Javascript and CSS files outside of a notebook:

[4]:
pn.config.js_files = {"deck": "https://unpkg.com/deck.gl@~5.2.0/deckgl.min.js"}
pn.config.css_files = [
    "https://api.tiles.mapbox.com/mapbox-gl-js/v0.44.1/mapbox-gl.css"
]

Display in the notebook#

Once extension is loaded, panel objects that are placed at the end of a cell are displayed:

[5]:
pane = pn.panel("<marquee>Here is some custom HTML</marquee>")

pane
[5]:

The display function#

To avoid having to put a panel in the last row of a notebook cell, you can use the IPython display function:

[6]:
def display_marquee(text):
    display(pn.panel("<marquee>{text}</marquee>".format(text=text)))


display_marquee("This Panel was displayed from within a function")

Inline apps#

Finally, it is also possible to display a panel object as a bokeh server app in the notebook. To do this, call the .app method in the panel object and enter the URL of your notebook server:

[7]:
pn.io.notebook.show_server(panel=pane, notebook_url="localhost:8888")
[7]:
<panel.io.server.Server at 0x1095da090>

The app is now executed in an instance of the Bokeh server that is separate from the Jupyter notebook kernel, so that you can quickly test whether the entire functionality of your app works both in the notebook and in the server context.

Display in an interactive Python window (REPL)#

If you work via the command line, extensive displays are not automatically displayed inline, as is the case in a notebook. However, you can interact with your panel components if you start a Bokeh server instance and use the show method to open a separate browser window. The method has the following arguments:

  • port: int, (optional): allows a specific port to be specified (default=0 select any open port)

  • websocket_origin: str or list(str) (optional): A list of hosts that can connect to the websocket. This is necessary when a server app is embedded in an external website. If not specified, localhost is used.

  • threaded: boolean (optional, default=False): True starts the server in a separate thread and allows you to interact with the app.

The show call returns either a Bokeh server instance (threaded=False) or an StoppableThread instance (threaded=True), both provide a stop method for stopping the server instance.

Starting a server from the command line#

Panel (and Bokeh) provide a CLI command to deploy a Python script, app directory, or Jupyter notebook with a Bokeh or Panel app. To start a server using the CLI, simply do the following:

$ pipenv run panel serve app.ipynb

To turn a notebook into a deployable app, simply attach to one or more panel objects .servable(), which adds the app to bokehs curdoc. This makes it easy to create dashboards interactively in a notebook and then seamlessly provide them to the Bokeh server.

Session status#

  • panel.state exposes some of the internal Bokeh server components to users.

  • panel.state.curdoc allows access to the current bokeh.document.

Embed#

Panel generally needs either the Jupyter kernel or a Bokeh server running in the background to enable interactive behavior. However, for simple apps it is also possible to capture the entire widget status so that the app can be used entirely from Javascript. To demonstrate this, let’s create a simple app that simply takes a slider value, multiplies that by 5, and then displays the result:

[8]:
slider = pn.widgets.IntSlider(
    name="Integer to Scientific Notation Converter", start=0, end=10
)


@pn.depends(slider.param.value)
def callback(value):
    return "%d = %e" % (value, value)


row = pn.Row(slider, callback)
[9]:
row.embed()
[9]:

If you try the above widget, you will find that it only has three different status 0, 5 and 10. This is because embedding attempts to limit the number of options for non-discrete or semi-discrete widgets to a maximum of three values by default. This can be changed with the max_opts argument of the embed method. The full options for the embed method are:

  • max_states: Maximum number of states to be embedded

  • max_opts: Maximum number of states for a single widget

  • json: Specifies whether the data should be exported to json files

  • save_path: Path to save JSON files (default='./')

  • load_path: Path or URL from which the JSON files are loaded (as save_path unless otherwise specified)

As you can easily imagine, a combinatorial explosion of the statuses can quickly occur with several widgets, so that the output is limited to around 1000 statuses by default. For larger apps, the status can also be exported to JSON files. For example, if you want to make the app available on a website, specify save_path where the JSON file should be saved and load_path where the JS code should search for the files.

Save#

If you don’t need an actual server or just want to export a static snapshot of a panel app, you can use the save method which can be used to export the app to a standalone HTML or PNG file.

By default, the generated HTML file depends on loading the JavaScript code for BokehJS from the online CDN repository to reduce the file size. If you need to work in a networked or non-networked environment, you can choose to use INLINE resources instead of CDN:

[10]:
from bokeh.resources import INLINE


pane.save("deploy-panel.html", resources=INLINE)
pane.save("test.png")

To export the png file you also need Selenium and PhantomJS:

$ pipenv install selenium
Installing selenium…
…
$ npm install -g phantomjs-prebuilt
…
Done. Phantomjs binary available at /usr/local/lib/node_modules/phantomjs-prebuilt/lib/phantom/bin/phantomjs
+ phantomjs-prebuilt@2.1.16
added 81 packages from 76 contributors in 31.121s

In addition, you can use the save method together with the embed option to embed the app status in the app or to save it in JSON files, which can be deployed together with the exported HTML code. You have the following options:

  • resources: bokeh.resources, e.g. CDN or INLINE

  • embed: Boolean value, whether the status should be saved in the file or not.

  • max_states: The maximum number of states to be embedded

  • max_opts: The maximum number of states for a single widget

  • embed_json: Boolean value as to whether the data should be exported as a JSON file (default=True).