Skip to content

Commit

Permalink
Add default templates (#1277)
Browse files Browse the repository at this point in the history
  • Loading branch information
philippjfr authored May 13, 2020
1 parent 3379f8e commit b73ec77
Show file tree
Hide file tree
Showing 19 changed files with 910 additions and 117 deletions.
Binary file added examples/assets/dark_theme.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added examples/assets/template.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
207 changes: 201 additions & 6 deletions examples/user_guide/Templates.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,51 @@
"As we can see the template defines a number of custom blocks, which can be overridden by extending this default template."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import panel as pn\n",
"import numpy as np\n",
"import holoviews as hv\n",
"\n",
"pn.extension()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Using custom templates"
"## Using default templates\n",
"\n",
"For a large variety of use cases we do not need complete control over the exact layout of each individual component on the page we just want to achieve a more polished look and feel. For these cases Panel ships with a number of default templates, which are defined by declaring three main content areas on the page, which can be populated as desired:\n",
"\n",
"* **`header`**: The header area of the HTML page\n",
"* **`sidebar`**: A collapsible sidebar\n",
"* **`main`**: The main area of the application\n",
"\n",
"These three areas behave very similarly to other Panel layout components and have list-like semantics. This means we can easily append new components into these areas. Unlike other layout components however, the contents of the areas is fixed once rendered. If you need a dynamic layout you should therefore insert a regular Panel layout component (e.g. a `Column` or `Row`) and modify it in place once added to one of the content areas. \n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Supported themes\n",
"\n",
"Panel ships with a number of these default themes built on different CSS frameworks:\n",
" \n",
"* `MaterialTemplate`: Built on [Material Components for the web](https://material.io/develop/web/)\n",
"* `BootstrapTemplate`: Built on [Bootstrap v4](https://getbootstrap.com/docs/4.0/getting-started/introduction/)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Let us construct a very simple app containing two plots in the `main` area and two widgets in the sidebar based on the `BootstrapTemplate` class:"
]
},
{
Expand All @@ -77,10 +117,145 @@
"metadata": {},
"outputs": [],
"source": [
"import panel as pn\n",
"import holoviews as hv\n",
"bootstrap = pn.template.BootstrapTemplate(title='Bootstrap Template')\n",
"\n",
"pn.extension()"
"pn.config.sizing_mode = 'stretch_width'\n",
"\n",
"xs = np.linspace(0, np.pi)\n",
"freq = pn.widgets.FloatSlider(name=\"Frequency\", start=0, end=10, value=2)\n",
"phase = pn.widgets.FloatSlider(name=\"Phase\", start=0, end=np.pi)\n",
"\n",
"@pn.depends(freq=freq, phase=phase)\n",
"def sine(freq, phase):\n",
" return hv.Curve((xs, np.sin(xs*freq+phase))).opts(\n",
" responsive=True, min_height=400)\n",
"\n",
"@pn.depends(freq=freq, phase=phase)\n",
"def cosine(freq, phase):\n",
" return hv.Curve((xs, np.cos(xs*freq+phase))).opts(\n",
" responsive=True, min_height=400)\n",
"\n",
"bootstrap.sidebar.append(freq)\n",
"bootstrap.sidebar.append(phase)\n",
"\n",
"bootstrap.main.append(\n",
" pn.Row(\n",
" pn.Card(hv.DynamicMap(sine), title='Sine'),\n",
" pn.Card(hv.DynamicMap(cosine), title='Cosine')\n",
" )\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<div style=\"margin: auto 0;\">\n",
"<figure>\n",
"<img src=\"../assets/template.png\"></img>\n",
" <caption><b>Bootstrap Template</b>: A simple example demonstrating the Bootstrap template</caption>\n",
"</figure>\n",
"\n",
"A `Template` can be served or displayed just like any other Panel component, i.e. using `.servable()` or `.show()`."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Theming\n",
"\n",
"Default template classes provide a unified approach to theming, which currently allow specifying custom CSS and the Bokeh `Theme` to apply to the `Template`. The way it is implemented a user declares a generic `Theme` class and the `Template` loads the specific implementation for a particular `Template`. To make this more concrete, by default a Template uses the `DefaultTheme`, but then uses the `find_theme` method to look up the implementation of that theme for the Template being used:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from panel.template import DefaultTheme\n",
"\n",
"DefaultTheme.find_theme(pn.template.MaterialTemplate)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"To implement your own theme you should therefore declare a generic class for use by the enduser and a specific implementation for all the themes that should be supported, e.g. here is an example of what the definition of a dark theme might look like:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import param\n",
"\n",
"from panel.template.theme import Theme\n",
"from bokeh.themes import DARK_MINIMAL\n",
"\n",
"class DarkTheme(Theme):\n",
" \"\"\"\n",
" The DarkTheme provides a dark color palette\n",
" \"\"\"\n",
"\n",
" bokeh_theme = param.ClassSelector(class_=(Theme, str), default=DARK_MINIMAL)\n",
"\n",
"class MaterialDarkTheme(DarkTheme):\n",
"\n",
" # css = param.Filename() Here we could declare some custom CSS to apply\n",
" \n",
" # This tells Panel to use this implementation\n",
" _template = pn.template.MaterialTemplate"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"To apply the theme we now merely have to provide the generic `DarkTheme` class to the Template (we will import the `DarkTheme` that ships with panel here:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from panel.template import DarkTheme\n",
"\n",
"dark_material = pn.template.MaterialTemplate(title='Material Template', theme=DarkTheme)\n",
"\n",
"dark_material.sidebar.append(freq)\n",
"dark_material.sidebar.append(phase)\n",
"\n",
"dark_material.main.append(\n",
" pn.Row(\n",
" pn.Card(hv.DynamicMap(sine), title='Sine'),\n",
" pn.Card(hv.DynamicMap(cosine), title='Cosine')\n",
" )\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<div style=\"margin: auto 0;\">\n",
"<figure>\n",
"<img src=\"../assets/dark_theme.png\"></img>\n",
" <caption><b>Dark Theme</b>: The MaterialTemplate with a DarkTheme applied</caption>\n",
"</figure>"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Using custom templates"
]
},
{
Expand Down Expand Up @@ -195,7 +370,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"## Loading template from file\n",
"### Loading template from file\n",
"\n",
"If the template is larger it is often cleaner to define it in a separate file. You can either read it in as a string, or use the official loading mechanism available for Jinja2 templates by defining a `Environment` along with a `loader`."
]
Expand All @@ -219,9 +394,29 @@
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"pygments_lexer": "ipython3"
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.5"
},
"widgets": {
"application/vnd.jupyter.widget-state+json": {
"state": {},
"version_major": 2,
"version_minor": 0
}
}
},
"nbformat": 4,
Expand Down
4 changes: 2 additions & 2 deletions panel/io/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,12 @@ def _server_url(url, port):
return 'http://%s:%d%s' % (url.split(':')[0], port, "/")

def _eval_panel(panel, server_id, title, location, doc):
from ..template import Template
from ..template import BaseTemplate
from ..pane import panel as as_panel

if isinstance(panel, FunctionType):
panel = panel()
if isinstance(panel, Template):
if isinstance(panel, BaseTemplate):
return panel._modify_doc(server_id, title, doc, location)
return as_panel(panel)._modify_doc(server_id, title, doc, location)

Expand Down
11 changes: 7 additions & 4 deletions panel/layout/card.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,10 @@ class Card(Column):
A valid CSS color to apply to the header text.""")

header_css_classes = param.List(['card-header'], doc="""
CSS claasses to apply to the heaader element.""")
CSS classes to apply to the header element.""")

title_css_classes = param.List(['card-title'], doc="""
CSS classes to apply to the header title.""")

margin = param.Parameter(default=5)

Expand All @@ -46,12 +49,12 @@ class Card(Column):

_bokeh_model = BkCard

_rename = dict(Column._rename, title=None, header=None)
_rename = dict(Column._rename, title=None, header=None, title_css_classes=None)

def __init__(self, *objects, **params):
self._header_layout = Row(css_classes=['card-header-row'])
super(Card, self).__init__(*objects, **params)
self.param.watch(self._update_header, ['title', 'header'])
self.param.watch(self._update_header, ['title', 'header', 'title_css_classes'])
self._update_header()

def _cleanup(self, root):
Expand All @@ -70,7 +73,7 @@ def _process_param_change(self, params):
def _update_header(self, *events):
from ..pane import HTML, panel
if self.header is None:
item = HTML('%s' % (self.title or "&#8203;"), css_classes=['card-title'])
item = HTML('%s' % (self.title or "&#8203;"), css_classes=self.title_css_classes)
else:
item = panel(self.header)
self._header_layout[:] = [item]
Expand Down
4 changes: 4 additions & 0 deletions panel/template/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from .base import Template, BaseTemplate # noqa
from .bootstrap import BootstrapTemplate # noqa
from .material import MaterialTemplate # noqa
from .theme import DarkTheme, DefaultTheme # noqa
Loading

0 comments on commit b73ec77

Please sign in to comment.