{ "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "import panel as pn\n", "\n", "pn.extension()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "One of the main design goals for Panel was that it should make it possible to seamlessly transition back and forth between interactively prototyping a dashboard in the notebook or on the commandline to deploying it as a standalone server app. This section shows how to display panels interactively, embed static output, save a snapshot, and deploy as a separate web-server app. For more information about deploying Panel apps to various cloud providers see the [Server Deployment](Server_Deployment.ipynb) documentation.\n", "\n", "## Configuring output\n", "\n", "As you may have noticed, almost all the Panel documentation is written using notebooks. Panel objects display themselves automatically in a notebook and take advantage of Jupyter Comms to support communication between the rendered app and the Jupyter kernel that backs it on the Python end. To display a Panel object in the notebook is as simple as putting it on the end of a cell. Note, however, that the ``panel.extension`` first has to be loaded to initialize the required JavaScript in the notebook context. In recent versions of JupyterLab this works out of the box but for older versions (`<3.0`) the PyViz labextension has to be installed with:\n", "\n", " jupyter labextension install @pyviz/jupyterlab_pyviz\n", "\n", "### Optional dependencies\n", "\n", "Also remember that in order to use certain components such as Vega, LaTeX, and Plotly plots in a notebook, the models must be loaded using the extension. If you forget to load the extension, you should get a warning reminding you to do it. To load certain JS components, simply list them as part of the call to ``pn.extension``:\n", "\n", " pn.extension('vega', 'katex')\n", "\n", "Here we've ensured that the Vega and LaTeX JS dependencies will be loaded.\n", "\n", "### Initializing JS and CSS \n", "\n", "Additionally, any external ``css_files``, ``js_files`` and ``raw_css`` needed should be declared in the extension. The ``js_files`` should be declared as a dictionary mapping from the exported JS module name to the URL containing the JS components, while the ``css_files`` can be defined as a list:\n", "\n", " pn.extension(js_files={'deck': https://unpkg.com/deck.gl@~5.2.0/deckgl.min.js},\n", " css_files=['https://api.tiles.mapbox.com/mapbox-gl-js/v0.44.1/mapbox-gl.css'])\n", "\n", "The ``raw_css`` argument allows defining a list of strings containing CSS to publish as part of the notebook and app.\n", "\n", "Providing keyword arguments via the ``extension`` is the same as setting them on ``pn.config``, which is the preferred approach outside the notebook. ``js_files`` and ``css_files`` may be set to your chosen values as follows:\n", "\n", " pn.config.js_files = {'deck': 'https://unpkg.com/deck.gl@~5.2.0/deckgl.min.js'}\n", " pn.config.css_files = ['https://api.tiles.mapbox.com/mapbox-gl-js/v0.44.1/mapbox-gl.css']" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Display in the notebook\n", "\n", "#### The repr\n", " \n", "Once the extension is loaded, Panel objects will display themselves if placed at the end of cell in the notebook:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "pane = pn.panel('Here is some custom HTML')\n", "\n", "pane" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To instead see a textual representation of the component, you can use the ``pprint`` method on any Panel object:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "pane.pprint()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### The ``display`` function\n", "\n", "To avoid having to put a Panel on the last line of a notebook cell, e.g. to display it from inside a function call, you can use the IPython built-in ``display`` function:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def display_marquee(text):\n", " display(pn.panel('{text}'.format(text=text)))\n", " \n", "display_marquee('This Panel was displayed from within a function')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Inline apps\n", "\n", "Lastly it is also possible to display a Panel object as a Bokeh server app inside the notebook. To do so call the ``.app`` method on the Panel object and provide the URL of your notebook server:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "pane.app('localhost:8888')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The app will now run on a Bokeh server instance separate from the Jupyter notebook kernel, allowing you to quickly test that all the functionality of your app works both in a notebook and in a server context." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### ipywidgets\n", "\n", "If the `jupyter_bokeh` package is installed it is also possible to render Panel objects as an ipywidget rather than using Bokeh's internal communication mechanisms. You can enable ipywidgets support globally using:\n", "\n", "```python\n", "pn.extension(comms='ipywidgets')\n", "# or\n", "pn.config.comms = 'ipywidgets'\n", "```\n", "\n", "This global setting can be useful when trying to serve an entire notebook using [VoilĂ ](https://github.com/voila-dashboards/voila). Alternatively, we can convert individual objects to an ipywidget one at a time using the `pn.ipywidget()` function:\n", "\n", "```python\n", "ipywidget = pn.ipywidget(pane)\n", "ipywidget\n", "```\n", "\n", "This approach also allows combining a Panel object with any other Jupyter-widget--based model:\n", "\n", "```python\n", "from ipywidgets import Accordion\n", "Accordion(children=[pn.ipywidget(pane)])\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To use Panel's ipywidgets support in JupyterLab, the following extensions have to be installed:\n", " \n", "```\n", "jupyter labextension install @jupyter-widgets/jupyterlab-manager\n", "jupyter labextension install @bokeh/jupyter_bokeh\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Additionally the `jupyter_bokeh` package should be installed using either pip:\n", "\n", "```\n", "pip install jupyter_bokeh\n", "```\n", "\n", "or using conda:\n", "\n", "```\n", "conda install -c bokeh jupyter_bokeh\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Accessing the Bokeh model\n", "\n", "Since Panel is built on top of Bokeh, all Panel objects can easily be converted to a Bokeh model. The ``get_root`` method returns a model representing the contents of a Panel:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "model = pn.Column('# Some markdown').get_root()\n", "model" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "By default this model will be associated with Bokeh's ``curdoc()``, so if you want to associate the model with some other ``Document`` ensure you supply it explictly as the first argument. Once you have access to the underlying bokeh model you can use all the usual bokeh utilities such as ``components``, ``file_html``, or ``show``" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from bokeh.embed import components, file_html\n", "from bokeh.io import show\n", "\n", "script, html = components(model)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Embedding\n", "\n", "Panel generally relies on either the Jupyter kernel or a Bokeh Server to be running in the background to provide interactive behavior. However for simple apps with a limited amount of state it is also possible to `embed` all the widget state, allowing the app to be used entirely from within Javascript. To demonstrate this we will create a simple app which simply takes a slider value, multiplies it by 5 and then display the result." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "slider = pn.widgets.IntSlider(start=0, end=10)\n", "\n", "@pn.depends(slider.param.value)\n", "def callback(value):\n", " return '%d * 5 = %d' % (value, value*5)\n", "\n", "row = pn.Row(slider, callback)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If we displayed this the normal way it would call back into Python every time the value changed. However, the `.embed()` method will record the state of the app for the different widget configurations." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "row.embed()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If you try the widget above you will note that it only has 3 different states, 0, 5 and 10. This is because by default embed will try to limit the number of options of non-discrete or semi-discrete widgets to at most three values. This can be controlled using the `max_opts` argument to the embed method or you can provide an explicit list of `states` to embed for each widget:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "row.embed(states={slider: list(range(0, 12, 2))})" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " The full set of options for the embed method include:\n", "\n", "- **`max_states`**: The maximum number of states to embed\n", "\n", "- **`max_opts`**: The maximum number of states for a single widget\n", "\n", "- **`states`** (default={}): A dictionary specifying the widget values to embed for each widget\n", "\n", "- **`json`** (default=True): Whether to export the data to json files\n", "\n", "- **`save_path`** (default='./'): The path to save json files to\n", "\n", "- **`load_path`** (default=None): The path or URL the json files will be loaded from (same as ``save_path`` if not specified)\n", "\n", "* **`progress`** (default=False): Whether to report progress\n", "\n", "\n", "As you might imagine if there are multiple widgets there can quickly be a combinatorial explosion of states so by default the output is limited to about 1000 states. For larger apps the states can also be exported to json files, e.g. if you want to serve the app on a website specify the ``save_path`` to declare where it will be stored and the ``load_path`` to declare where the JS code running on the website will look for the files." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Saving \n", "\n", "In case you don't need an actual server or simply want to export a static snapshot of a panel app, you can use the ``save`` method, which allows exporting the app to a standalone HTML or PNG file.\n", "\n", "By default, the HTML file generated will depend on loading JavaScript code for BokehJS from the online ``CDN`` repository, to reduce the file size. If you need to work in an airgapped or no-network environment, you can declare that ``INLINE`` resources should be used instead of ``CDN``:\n", "\n", "```python\n", "from bokeh.resources import INLINE\n", "panel.save('test.html', resources=INLINE)\n", "```\n", "\n", "Additionally the save method also allows enabling the `embed` option, which, as explained above, will embed the apps state in the app or save the state to json files which you can ship alongside the exported HTML.\n", "\n", "Finally, if a 'png' file extension is specified, the exported plot will be rendered as a PNG, which currently requires Selenium and PhantomJS to be installed:\n", "\n", "```python\n", "pane.save('test.png')\n", "\n", "```" ] } ], "metadata": { "language_info": { "name": "python", "pygments_lexer": "ipython3" } }, "nbformat": 4, "nbformat_minor": 4 }