From b8390864950e53d62acef2ed794aa5a226ff61d0 Mon Sep 17 00:00:00 2001 From: Alice Grosch <grosch1@jrl01.jureca> Date: Mon, 2 Mar 2020 12:27:33 +0100 Subject: [PATCH] Update examples --- README.md | 16 + dashboards/voila-basic.ipynb | 222 ----- dashboards/vuetify-custom.ipynb | 459 --------- images/WidgetArch.png | Bin 0 -> 23058 bytes ....ipynb => 0_overview_of_all_widgets.ipynb} | 25 +- ...ntroduction.ipynb => 1_introduction.ipynb} | 42 +- ...get_events.ipynb => 2_widget_events.ipynb} | 217 ++-- ...t_styling.ipynb => 3_widget_styling.ipynb} | 10 +- ...get_layout.ipynb => 4_widget_layout.ipynb} | 256 +---- .../gridstack-example.ipynb | 12 + voila-examples/gridstack-scotch.ipynb | 924 ++++++++++++++++++ voila-examples/voila-basic.ipynb | 672 +++++++++++++ .../voila-ipyvolume.ipynb | 2 +- .../vuetify-bqplot.ipynb | 189 +++- voila-examples/vuetify-custom.ipynb | 767 +++++++++++++++ 15 files changed, 2737 insertions(+), 1076 deletions(-) create mode 100644 README.md delete mode 100644 dashboards/voila-basic.ipynb delete mode 100644 dashboards/vuetify-custom.ipynb create mode 100644 images/WidgetArch.png rename ipywidgets/{ipywidgets.ipynb => 0_overview_of_all_widgets.ipynb} (96%) rename ipywidgets/{introduction.ipynb => 1_introduction.ipynb} (92%) rename ipywidgets/{widget_events.ipynb => 2_widget_events.ipynb} (91%) rename ipywidgets/{widget_styling.ipynb => 3_widget_styling.ipynb} (91%) rename ipywidgets/{widget_layout.ipynb => 4_widget_layout.ipynb} (70%) rename dashboards/voila-dashboard.ipynb => voila-examples/gridstack-example.ipynb (91%) create mode 100644 voila-examples/gridstack-scotch.ipynb create mode 100644 voila-examples/voila-basic.ipynb rename {dashboards => voila-examples}/voila-ipyvolume.ipynb (95%) rename {dashboards => voila-examples}/vuetify-bqplot.ipynb (52%) create mode 100644 voila-examples/vuetify-custom.ipynb diff --git a/README.md b/README.md new file mode 100644 index 0000000..32bcf93 --- /dev/null +++ b/README.md @@ -0,0 +1,16 @@ +# Jupyter Dashboarding + +Example dashboards using voila and voila-vuetify + +## Tutorials + +For an in-depth tutorial of `ipywidgets`, please refer to the official [tutorial](https://github.com/jupyter-widgets/tutorial). +A summary can be found under [ipywidgets](./ipywidgets), which contains the bare minimum of information to get you started. + +A tutorial for creating your own `ipyvuetify` dashboard can be found [here](./dashboards/vuetify-custom.ipynb). + +## Example apps + +You can find a collection of example either on the official [voila-gallery website](https://voila-gallery.org/) or in the subdirectory [voila-examples](./voila-examples). +Unless otherwise specified in the notebooks, you can start voila webapp over the command-line using `voila <your-notebook>`. For possible configuration options, use `voila --help`. +If using JupyterLab with `jupyterlab-preview` installed, you can render a preview of your app with the yellow-grey circle button in the notebook toolbar. \ No newline at end of file diff --git a/dashboards/voila-basic.ipynb b/dashboards/voila-basic.ipynb deleted file mode 100644 index 834ea44..0000000 --- a/dashboards/voila-basic.ipynb +++ /dev/null @@ -1,222 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "effeb1790e594756813c5e08bfcb70b2", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "VBox(children=(FloatSlider(value=4.0, description='$x$'), FloatText(value=0.0, description='$x^2$', disabled=T…" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "import ipywidgets as widgets\n", - "\n", - "slider = widgets.FloatSlider(description='$x$', value=4)\n", - "text = widgets.FloatText(disabled=True, description='$x^2$')\n", - "\n", - "def compute(*ignore):\n", - " text.value = str(slider.value ** 2)\n", - "\n", - "slider.observe(compute, 'value')\n", - "\n", - "widgets.VBox([slider, text])" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "<div>\n", - "<style scoped>\n", - " .dataframe tbody tr th:only-of-type {\n", - " vertical-align: middle;\n", - " }\n", - "\n", - " .dataframe tbody tr th {\n", - " vertical-align: top;\n", - " }\n", - "\n", - " .dataframe thead th {\n", - " text-align: right;\n", - " }\n", - "</style>\n", - "<table border=\"1\" class=\"dataframe\">\n", - " <thead>\n", - " <tr style=\"text-align: right;\">\n", - " <th></th>\n", - " <th>sepal_length</th>\n", - " <th>sepal_width</th>\n", - " <th>petal_length</th>\n", - " <th>petal_width</th>\n", - " <th>species</th>\n", - " </tr>\n", - " </thead>\n", - " <tbody>\n", - " <tr>\n", - " <th>0</th>\n", - " <td>5.1</td>\n", - " <td>3.5</td>\n", - " <td>1.4</td>\n", - " <td>0.2</td>\n", - " <td>setosa</td>\n", - " </tr>\n", - " <tr>\n", - " <th>1</th>\n", - " <td>4.9</td>\n", - " <td>3.0</td>\n", - " <td>1.4</td>\n", - " <td>0.2</td>\n", - " <td>setosa</td>\n", - " </tr>\n", - " <tr>\n", - " <th>2</th>\n", - " <td>4.7</td>\n", - " <td>3.2</td>\n", - " <td>1.3</td>\n", - " <td>0.2</td>\n", - " <td>setosa</td>\n", - " </tr>\n", - " <tr>\n", - " <th>3</th>\n", - " <td>4.6</td>\n", - " <td>3.1</td>\n", - " <td>1.5</td>\n", - " <td>0.2</td>\n", - " <td>setosa</td>\n", - " </tr>\n", - " <tr>\n", - " <th>4</th>\n", - " <td>5.0</td>\n", - " <td>3.6</td>\n", - " <td>1.4</td>\n", - " <td>0.2</td>\n", - " <td>setosa</td>\n", - " </tr>\n", - " <tr>\n", - " <th>...</th>\n", - " <td>...</td>\n", - " <td>...</td>\n", - " <td>...</td>\n", - " <td>...</td>\n", - " <td>...</td>\n", - " </tr>\n", - " <tr>\n", - " <th>145</th>\n", - " <td>6.7</td>\n", - " <td>3.0</td>\n", - " <td>5.2</td>\n", - " <td>2.3</td>\n", - " <td>virginica</td>\n", - " </tr>\n", - " <tr>\n", - " <th>146</th>\n", - " <td>6.3</td>\n", - " <td>2.5</td>\n", - " <td>5.0</td>\n", - " <td>1.9</td>\n", - " <td>virginica</td>\n", - " </tr>\n", - " <tr>\n", - " <th>147</th>\n", - " <td>6.5</td>\n", - " <td>3.0</td>\n", - " <td>5.2</td>\n", - " <td>2.0</td>\n", - " <td>virginica</td>\n", - " </tr>\n", - " <tr>\n", - " <th>148</th>\n", - " <td>6.2</td>\n", - " <td>3.4</td>\n", - " <td>5.4</td>\n", - " <td>2.3</td>\n", - " <td>virginica</td>\n", - " </tr>\n", - " <tr>\n", - " <th>149</th>\n", - " <td>5.9</td>\n", - " <td>3.0</td>\n", - " <td>5.1</td>\n", - " <td>1.8</td>\n", - " <td>virginica</td>\n", - " </tr>\n", - " </tbody>\n", - "</table>\n", - "<p>150 rows × 5 columns</p>\n", - "</div>" - ], - "text/plain": [ - " sepal_length sepal_width petal_length petal_width species\n", - "0 5.1 3.5 1.4 0.2 setosa\n", - "1 4.9 3.0 1.4 0.2 setosa\n", - "2 4.7 3.2 1.3 0.2 setosa\n", - "3 4.6 3.1 1.5 0.2 setosa\n", - "4 5.0 3.6 1.4 0.2 setosa\n", - ".. ... ... ... ... ...\n", - "145 6.7 3.0 5.2 2.3 virginica\n", - "146 6.3 2.5 5.0 1.9 virginica\n", - "147 6.5 3.0 5.2 2.0 virginica\n", - "148 6.2 3.4 5.4 2.3 virginica\n", - "149 5.9 3.0 5.1 1.8 virginica\n", - "\n", - "[150 rows x 5 columns]" - ] - }, - "execution_count": 2, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "import pandas as pd\n", - "\n", - "iris = pd.read_csv('https://raw.githubusercontent.com/mwaskom/seaborn-data/master/iris.csv')\n", - "iris" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "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", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.6.5" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/dashboards/vuetify-custom.ipynb b/dashboards/vuetify-custom.ipynb deleted file mode 100644 index e68f9b3..0000000 --- a/dashboards/vuetify-custom.ipynb +++ /dev/null @@ -1,459 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "import ipyvuetify as v" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Documentation" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Ipyvuetify\n", - "https://github.com/mariobuikhuizen/ipyvuetify\n", - "\n", - "### Vuetify Documentation\n", - "https://vuetifyjs.com/en/components/buttons#buttons\n", - "\n", - "### Material UI Icons\n", - "https://www.materialui.co/icons" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Create a Custom Template" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "1. create `conf.json` with \n", - "\n", - "```json\n", - "{\"base_template\": \"vuetify-base\"}\n", - "```\n", - "\n", - "2. copy https://raw.githubusercontent.com/voila-dashboards/voila-vuetify/master/share/jupyter/voila/templates/vuetify-default/nbconvert_templates/app.html (`app.html`) into a directory called `nbconvert_templates`" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Navigation Bar\n", - "\n", - "Set color in custom template `app.html`\n", - "```html\n", - "<v-navigation-drawer v-model=\"showNavBar\" absolute app>\n", - " <v-toolbar color=\"primary\" dark flat>\n", - " [...]\n", - " </v-toolbar>\n", - " [...]\n", - "</v-navigation-drawer>\n", - "```" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### How do widgets in ipyvuetify work? \n", - "Think of them as HTML in Python code!\n", - "\n", - "```html\n", - "<v-list-item link>\n", - " <v-list-item-icon>\n", - " <v-icon>{{ item.icon }}</v-icon>\n", - " </v-list-item-icon>\n", - " \n", - " <v-list-item-content>\n", - " <v-list-item-title>{{ item.title }}</v-list-item-title>\n", - " </v-list-item-content>\n", - "</v-list-item>\n", - "```\n", - "\n", - "Compare the Python code below:" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "84ea2e28ba9c43d6b60e979270f7153b", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "List(children=[ListItem(children=[ListItemIcon(children=[Icon(children=['account_box'])]), ListItemContent(chi…" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "list_items = [\n", - " v.ListItem(link=True, children=[\n", - " v.ListItemIcon(children=[\n", - " v.Icon(children=[\"account_box\"]),\n", - " ]),\n", - " v.ListItemContent(children=[\n", - " v.ListItemTitle(children=[\"Account\"])\n", - " ])\n", - " ]),\n", - " v.ListItem(link=True, children=[\n", - " v.ListItemIcon(children=[\n", - " v.Icon(children=[\"timeline\"]),\n", - " ]),\n", - " v.ListItemContent(children=[\n", - " v.ListItemTitle(children=[\"Analytics\"])\n", - " ])\n", - " ]),\n", - " v.ListItem(link=True, children=[\n", - " v.ListItemIcon(children=[\n", - " v.Icon(children=[\"build\"]),\n", - " ]),\n", - " v.ListItemContent(children=[\n", - " v.ListItemTitle(children=[\"Settings\"])\n", - " ])\n", - " ])\n", - "]\n", - "\n", - "v.List(\n", - " _metadata={'mount_id':'content-nav'},\n", - " column=True,\n", - " children=list_items\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Toolbar Title" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Change colors\n", - "\n", - "```html\n", - "<v-app-bar color=\"primary\" app absolute dark>\n", - "```" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Add mount point to dynamically change the toolbar title\n", - "\n", - "```html\n", - "<v-toolbar-title>\n", - " <jupyter-widget-mount-point mount-id=\"toolbar-title\" />\n", - "</v-toolbar-title>\n", - "```" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "12492fe621d946d6a0d4a38994ad7df4", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "ToolbarTitle(children=['My Custom Title'])" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "toolbar_title = v.ToolbarTitle(\n", - " _metadata={'mount_id':'toolbar-title'},\n", - " children=['My Custom Title']\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Content Main" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Content 1" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [], - "source": [ - "card1 = v.Card(children=[\n", - " v.CardTitle(children=['Card 1']),\n", - " v.CardText(children=[\n", - " \"Some text\",\n", - " v.Spacer(),\n", - " v.Btn(children=[\"Or a widget\"])\n", - " ]),\n", - " v.CardActions(children=[\n", - " v.Layout(row=True, children=[\n", - " v.Btn(icon=True, children=[\n", - " v.Icon(children=['favorite'])\n", - " ]),\n", - " v.Btn(icon=True, children=[\n", - " v.Icon(children=['bookmark'])\n", - " ]),\n", - " v.Btn(icon=True, children=[\n", - " v.Icon(children=['share'])\n", - " ]) \n", - " ])\n", - "\n", - " ])\n", - "])\n", - "\n", - "\n", - "card2 = v.Card(children=[\n", - " v.CardTitle(children=['Card 2']),\n", - " v.CardText(children=[\"With flex, the layout will be responsive\"])\n", - "])\n", - "\n", - "\n", - "content1 = v.Layout(row=True, wrap=True, align_center=True, children=[\n", - " v.Flex(xs12=True, md6=True, xl4=True, children=[\n", - " card1\n", - " ]),\n", - " v.Flex(xs12=True, md6=True, xl4=True, children=[\n", - " card2\n", - " ]),\n", - "])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Content 2" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [], - "source": [ - "from bqplot import pyplot as plt\n", - "import bqplot\n", - "import numpy as np\n", - "\n", - "n = 200\n", - "\n", - "x = np.linspace(0.0, 10.0, n)\n", - "y = np.cumsum(np.random.randn(n)*10).astype(int)\n", - "\n", - "fig = plt.figure( title='Histogram')\n", - "np.random.seed(0)\n", - "hist = plt.hist(y, bins=25)\n", - "hist.scales['sample'].min = float(y.min())\n", - "hist.scales['sample'].max = float(y.max())\n", - "fig.layout.width = 'auto'\n", - "fig.layout.height = 'auto'\n", - "fig.layout.min_height = '300px' # so it shows nicely in the notebook" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [], - "source": [ - "content2 = v.Layout(column=True, children=[\n", - " v.Html(tag='h1', children=['An example plot']),\n", - " fig\n", - "])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Content 3" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [], - "source": [ - "from ipywidgets import jslink" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "Link(source=(Switch(label='Switch', v_model=False), 'v_model'), target=(Checkbox(label='Checkbox', v_model=Fal…" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "switch = v.Switch(label=\"Switch\", v_model=False)\n", - "checkbox = v.Checkbox(label=\"Checkbox\", v_model=False)\n", - "\n", - "content3 = v.Layout(column=True, px_5=True, children=[\n", - " switch,\n", - " checkbox\n", - "])\n", - "\n", - "jslink((switch, 'v_model'), (checkbox, 'v_model'))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Switch between contents when the navigation bar is clicked" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [], - "source": [ - "content_main = v.Layout(\n", - " _metadata={'mount_id': 'content-main'},\n", - " children=[content1]\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [], - "source": [ - "def switch_to_first(*args):\n", - " toolbar_title.children = ['Account']\n", - " content_main.children = [content1]\n", - " \n", - "def switch_to_second(*args):\n", - " toolbar_title.children = ['Analytics']\n", - " content_main.children = [content2]\n", - " \n", - "def switch_to_third(*args):\n", - " toolbar_title.children = ['Settings']\n", - " content_main.children = [content3]\n", - " \n", - "list_items[0].on_event('click', switch_to_first)\n", - "list_items[1].on_event('click', switch_to_second)\n", - "list_items[2].on_event('click', switch_to_third)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Start voila with your custom template" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "```bash\n", - "voila --template path/to/your/template path/to/your/notebook\n", - "```" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "For example:\n", - "\n", - "```bash\n", - "voila --template /p/scratch/ccstvs/grosch1/jureca/dashboards/mycustomtemplate --server_url=/ --base_url=/user/a.grosch@fz-juelich.de/labtest/proxy/8866/ --VoilaConfiguration.file_whitelist=\"['.*']\" dashboards/vuetify-custom.ipynb\n", - "```" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Dashboard now runs on\n", - "\n", - "https://jupyter-jsc.fz-juelich.de/user/[your_web_account]/[labserver_name]/proxy/[port]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "https://jupyter-jsc.fz-juelich.de/user/a.grosch@fz-juelich.de/labtest/proxy/8866" - ] - } - ], - "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", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.6.8" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/images/WidgetArch.png b/images/WidgetArch.png new file mode 100644 index 0000000000000000000000000000000000000000..9fadae7fceb547556600077decca2abf53e616bd GIT binary patch literal 23058 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 Hr-UW|hcbJu literal 0 HcmV?d00001 diff --git a/ipywidgets/ipywidgets.ipynb b/ipywidgets/0_overview_of_all_widgets.ipynb similarity index 96% rename from ipywidgets/ipywidgets.ipynb rename to ipywidgets/0_overview_of_all_widgets.ipynb index 7d2ff73..0a87a27 100644 --- a/ipywidgets/ipywidgets.ipynb +++ b/ipywidgets/0_overview_of_all_widgets.ipynb @@ -1244,26 +1244,23 @@ }, { "cell_type": "code", - "execution_count": 33, + "execution_count": 2, "metadata": {}, "outputs": [ { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "16d8cacddd7a4a4aa082ae39e5f9ce41", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "Image(value=b'\\x89PNG\\r\\n\\x1a\\n\\x00\\x00\\x00\\rIHDR\\x00\\x00\\x012\\x00\\x00\\x01\\xb1\\x08\\x06\\x00\\x00\\x00\\xe1\\xe2:\\xb…" - ] - }, - "metadata": {}, - "output_type": "display_data" + "ename": "NameError", + "evalue": "name 'widgets' is not defined", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m<ipython-input-2-90d673488eed>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0mfile\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mopen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"../images/WidgetArch.png\"\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"rb\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 2\u001b[0m \u001b[0mimage\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mfile\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mread\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 3\u001b[0;31m widgets.Image(\n\u001b[0m\u001b[1;32m 4\u001b[0m \u001b[0mvalue\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mimage\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0mformat\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m'png'\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;31mNameError\u001b[0m: name 'widgets' is not defined" + ] } ], "source": [ - "file = open(\"images/WidgetArch.png\", \"rb\")\n", + "file = open(\"../images/WidgetArch.png\", \"rb\")\n", "image = file.read()\n", "widgets.Image(\n", " value=image,\n", diff --git a/ipywidgets/introduction.ipynb b/ipywidgets/1_introduction.ipynb similarity index 92% rename from ipywidgets/introduction.ipynb rename to ipywidgets/1_introduction.ipynb index 05a9d44..91a79d4 100644 --- a/ipywidgets/introduction.ipynb +++ b/ipywidgets/1_introduction.ipynb @@ -64,7 +64,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ @@ -91,13 +91,13 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 2, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "8511c20719d945e0b9d95892c02e06e6", + "model_id": "980e6336444c4f89a15a6cc23762bf90", "version_major": 2, "version_minor": 0 }, @@ -133,13 +133,13 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "8cb7600eadc54d0fa1327a65af6a0d0c", + "model_id": "b1138345ae2242d2b71f460cc8b61c7a", "version_major": 2, "version_minor": 0 }, @@ -177,13 +177,13 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "8cb7600eadc54d0fa1327a65af6a0d0c", + "model_id": "b1138345ae2242d2b71f460cc8b61c7a", "version_major": 2, "version_minor": 0 }, @@ -239,13 +239,13 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "71d46e39ff2a4dbd91eeed75b1f1fa69", + "model_id": "4c1814ffdab04bcb99bd839e28c39fc2", "version_major": 2, "version_minor": 0 }, @@ -264,16 +264,16 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "43" + "57" ] }, - "execution_count": 15, + "execution_count": 6, "metadata": {}, "output_type": "execute_result" } @@ -291,7 +291,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 7, "metadata": {}, "outputs": [], "source": [ @@ -318,7 +318,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 9, "metadata": {}, "outputs": [ { @@ -347,7 +347,7 @@ " 'value']" ] }, - "execution_count": 17, + "execution_count": 9, "metadata": {}, "output_type": "execute_result" } @@ -376,13 +376,13 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "0d70c9aeae0c4482b16bff8fb6100d87", + "model_id": "fdfe84af11894789af9d5d5487ab2f12", "version_major": 2, "version_minor": 0 }, @@ -413,18 +413,18 @@ } }, "source": [ - "If you need to display the same value two different ways, you'll have to use two different widgets. Instead of attempting to manually synchronize the values of the two widgets, you can use the `link` or `jslink` function to link two properties together (the difference between these is discussed in [Widget Events](08.00-Widget_Events.ipynb)). Below, the values of two widgets are linked together." + "If you need to display the same value two different ways, you'll have to use two different widgets. Instead of attempting to manually synchronize the values of the two widgets, you can use the `link` or `jslink` function to link two properties together (the difference between these is discussed in Widget Events). Below, the values of two widgets are linked together." ] }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 11, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "c6e090ac3d4a4525bae67e8ecd459330", + "model_id": "36a3fa22dd054a499a241a6b2e8de2db", "version_major": 2, "version_minor": 0 }, @@ -438,7 +438,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "ebef28d0e3c94b699930011a43dfc79c", + "model_id": "42083938affb4f088a967c8a1e68371a", "version_major": 2, "version_minor": 0 }, diff --git a/ipywidgets/widget_events.ipynb b/ipywidgets/2_widget_events.ipynb similarity index 91% rename from ipywidgets/widget_events.ipynb rename to ipywidgets/2_widget_events.ipynb index 36f8cbb..28052b9 100644 --- a/ipywidgets/widget_events.ipynb +++ b/ipywidgets/2_widget_events.ipynb @@ -27,7 +27,35 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "cc40e7bff4e14d7aaf74f6bc5f675d71", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Button(description='Click Me!', style=ButtonStyle())" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import ipywidgets as widgets\n", + "from IPython.display import display\n", + "\n", + "button = widgets.Button(description=\"Click Me!\")\n", + "display(button)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, "metadata": {}, "outputs": [ { @@ -48,9 +76,6 @@ } ], "source": [ - "import ipywidgets as widgets\n", - "from IPython.display import display\n", - "\n", "print(widgets.Button.on_click.__doc__)" ] }, @@ -74,13 +99,13 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 2, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "e35a61988f8a47148ae900a300c51d32", + "model_id": "cc40e7bff4e14d7aaf74f6bc5f675d71", "version_major": 2, "version_minor": 0 }, @@ -94,7 +119,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "007b2efd405643cfae7bdcde45acdddc", + "model_id": "118a3b96e47f4b4f94ceb095636c3ef5", "version_major": 2, "version_minor": 0 }, @@ -107,7 +132,7 @@ } ], "source": [ - "button = widgets.Button(description=\"Click Me!\")\n", + "# button = widgets.Button(description=\"Click Me!\")\n", "output = widgets.Output()\n", "\n", "display(button, output)\n", @@ -138,7 +163,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 3, "metadata": {}, "outputs": [ { @@ -177,52 +202,20 @@ "print(widgets.Widget.observe.__doc__)" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Registering callbacks to trait changes in the kernel\n", - "\n", - "Since attributes of widgets on the Python side are traitlets, you can register handlers to the change events whenever the model gets updates from the front-end.\n", - "\n", - "The handler passed to observe will be called with one change argument. The change object holds at least a `type` key and a `name` key, corresponding respectively to the type of notification and the name of the attribute that triggered the notification.\n", - "\n", - "Other keys may be passed depending on the value of `type`. In the case where type is `change`, we also have the following keys:\n", - "\n", - "- `owner` : the HasTraits instance\n", - "- `old` : the old value of the modified trait attribute\n", - "- `new` : the new value of the modified trait attribute\n", - "- `name` : the name of the modified trait attribute." - ] - }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "7c246e30f0e44b369c080894b012a891", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "Label(value='The values of range1 and range2 are synchronized')" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "7f09ac3d738d45459c11e44eb386d207", + "model_id": "f5f939f96b1443bfa4cf78180b1bee77", "version_major": 2, "version_minor": 0 }, "text/plain": [ - "IntSlider(value=1, description='Slider', max=5, min=-5)" + "IntSlider(value=0)" ] }, "metadata": {}, @@ -230,17 +223,18 @@ } ], "source": [ - "caption = widgets.Label(value='The values of range1 and range2 are synchronized')\n", - "slider = widgets.IntSlider(min=-5, max=5, value=1, description='Slider')\n", - "\n", - "def handle_slider_change(change):\n", - " caption.value = 'The slider value is ' + (\n", - " 'negative' if change.new < 0 else 'nonnegative'\n", - " )\n", + "widgets.IntSlider()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Registering callbacks to trait changes in the kernel\n", "\n", - "slider.observe(handle_slider_change, names='value')\n", + "Since attributes of widgets on the Python side are traitlets, you can register handlers to the change events whenever the model gets updates from the front-end.\n", "\n", - "display(caption, slider)" + "The handler passed to observe will be called with one change argument. The change object holds at least a `type` key and a `name` key, corresponding respectively to the type of notification and the name of the attribute that triggered the notification." ] }, { @@ -265,13 +259,13 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "24a3a202baee498fafbca313d1b9e2f3", + "model_id": "8ac0f4f86fdb440685aef2828674ead6", "version_major": 2, "version_minor": 0 }, @@ -285,7 +279,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "9cd6d9e6c1694f88b33f0e8767f738e5", + "model_id": "7b2742ecc38949af8f0fd0afaf74bdac", "version_major": 2, "version_minor": 0 }, @@ -311,6 +305,54 @@ "int_range.observe(on_value_change, names='value')" ] }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "c71d961de4b040218fbed6be5a605f8c", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Label(value='The values of range1 and range2 are synchronized')" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "1e50363a65e5409d936e3eb90a14bb05", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "IntSlider(value=1, description='Slider', max=5, min=-5)" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "caption = widgets.Label(value='The values of range1 and range2 are synchronized')\n", + "slider = widgets.IntSlider(min=-5, max=5, value=1, description='Slider')\n", + "\n", + "def handle_slider_change(change):\n", + " caption.value = 'The slider value is ' + (\n", + " 'negative' if change.new < 0 else 'nonnegative'\n", + " )\n", + "\n", + "slider.observe(handle_slider_change, names='value')\n", + "\n", + "display(caption, slider)" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -335,7 +377,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "4fc4d5a3ed47460ab4711b56146a9557", + "model_id": "f07c584778244df0a3b995b944fe63ec", "version_major": 2, "version_minor": 0 }, @@ -349,7 +391,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "82da94d8bee5428182a9c610cbcd4899", + "model_id": "fce68e14733e4a3382a539e9129fe945", "version_major": 2, "version_minor": 0 }, @@ -379,27 +421,6 @@ "display(degree_C, degree_F)" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Exercise\n", - "\n", - "Add a callback that is called when `degree_F` is changed. An outline of the callback function is below. Fill it in, and make `degree_F` `observe` call `on_F_change` if the `value` changes." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "def on_F_change(change):\n", - " degree_C.value = # Fill this in!\n", - " \n", - "# Add line here to have degree_F observe changes in value and call on_F_change " - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -430,13 +451,13 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 11, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "5f25499c4e094bb4b92ee5d0c3d45aa9", + "model_id": "11b6970421704dd4a9fd21640605dec3", "version_major": 2, "version_minor": 0 }, @@ -450,7 +471,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "3f551efbd31348a8989d839d4afe3167", + "model_id": "fca5a7c000314bb58f434aa84aa6806e", "version_major": 2, "version_minor": 0 }, @@ -464,7 +485,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "5b8c115b07a44874b84b33dfcb974f05", + "model_id": "08a35127291b424d96798a59ceb007ef", "version_major": 2, "version_minor": 0 }, @@ -483,18 +504,18 @@ "\n", "display(caption, sliders1, slider2)\n", "\n", - "l = widgets.link((sliders1, 'value'), (slider2, 'value'))\n" + "l = widgets.link((sliders1, 'value'), (slider2, 'value'))" ] }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "869e7b59e3224e83ac790624c2f1b5e6", + "model_id": "ae807ba500a04f5482d80e3e7a0c3045", "version_major": 2, "version_minor": 0 }, @@ -508,7 +529,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "f9a3fa00f826428ca05314bad3698f26", + "model_id": "2804522dfe1644c8abf8b52f03fff9d5", "version_major": 2, "version_minor": 0 }, @@ -522,7 +543,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "dd35388d682e433e81b9262bbfef0565", + "model_id": "9cc84cfae3864ba09f0b907e7f2fac93", "version_major": 2, "version_minor": 0 }, @@ -586,13 +607,13 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 13, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "e9b8f82d34384819958a1c7d3bed520b", + "model_id": "f11c648c0ae74c6b9125eccbad64df4f", "version_major": 2, "version_minor": 0 }, @@ -606,7 +627,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "011978ade1534cd1914e0a08ec4b8e69", + "model_id": "cee3c8c7ab174beb88989f24cd686c29", "version_major": 2, "version_minor": 0 }, @@ -620,7 +641,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "b2c228ade9774629b53c1a8f4f1c37c7", + "model_id": "e5a7e2a6b68e4d01a2111d1ea4bb5a89", "version_major": 2, "version_minor": 0 }, @@ -644,13 +665,13 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 14, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "4519e9ad4b2f4e54ba26ef00e519f1bb", + "model_id": "eae0cfd1bcf14f939e88bd3915ccef9c", "version_major": 2, "version_minor": 0 }, @@ -664,7 +685,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "fec7688a6ccf46eea4b1b36551d4044a", + "model_id": "64c819b36240491e93080167546996a1", "version_major": 2, "version_minor": 0 }, @@ -678,7 +699,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "3041273f6e764a92b289102b4d0c34ba", + "model_id": "1988145dca8346158cbe547435dfbeee", "version_major": 2, "version_minor": 0 }, @@ -741,13 +762,13 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "bd9af1c27b924dbba8d5482b72ae4c53", + "model_id": "b15f60f06eb7425290493f7755b44d7c", "version_major": 2, "version_minor": 0 }, diff --git a/ipywidgets/widget_styling.ipynb b/ipywidgets/3_widget_styling.ipynb similarity index 91% rename from ipywidgets/widget_styling.ipynb rename to ipywidgets/3_widget_styling.ipynb index c42a9df..46972dc 100644 --- a/ipywidgets/widget_styling.ipynb +++ b/ipywidgets/3_widget_styling.ipynb @@ -27,7 +27,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "5e4bd6b40f3e4acbb4e312f741d7760c", + "model_id": "065ce69f476a46a78ea5da6585c35fbe", "version_major": 2, "version_minor": 0 }, @@ -65,7 +65,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "0ec387a4a59f418caca91387e94e10c7", + "model_id": "15aa6ff1b5654d1499b204e6a7cecc8f", "version_major": 2, "version_minor": 0 }, @@ -133,7 +133,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "57d0156e8f8c4f71aa51f8f972e42697", + "model_id": "e3c38058c2254a34b7614a433a18abd9", "version_major": 2, "version_minor": 0 }, @@ -166,7 +166,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "8dcbf68e65a94e95bcdf7df29a05afa1", + "model_id": "c4e43f653ed642d28517594558e66cb3", "version_major": 2, "version_minor": 0 }, @@ -188,7 +188,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "There is a [list of all style keys](Table%20of%20widget%20keys%20and%20style%20keys.ipynb#Style-keys)." + "There is a [list of all style keys](https://github.com/jupyter-widgets/tutorial/blob/1dde8988b0ba083b1644b87514e3bcad1559f3af/notebooks/Table%20of%20widget%20keys%20and%20style%20keys.ipynb#Style-keys)." ] } ], diff --git a/ipywidgets/widget_layout.ipynb b/ipywidgets/4_widget_layout.ipynb similarity index 70% rename from ipywidgets/widget_layout.ipynb rename to ipywidgets/4_widget_layout.ipynb index 62d4586..16ef8d4 100644 --- a/ipywidgets/widget_layout.ipynb +++ b/ipywidgets/4_widget_layout.ipynb @@ -114,7 +114,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "ea23e02ad8f0498592ca3d515a7c4e82", + "model_id": "e2ed55e864a24f238341ffe7ca027def", "version_major": 2, "version_minor": 0 }, @@ -149,7 +149,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "1d9ef85d393c447db8aa91d818d9f7b2", + "model_id": "e93b134de64644f7acd5bb83963e55ac", "version_major": 2, "version_minor": 0 }, @@ -201,7 +201,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "1b901d6a06a24a3f9e9acd90cd6f93ed", + "model_id": "94693ef03ece4a549fd9b9416d683598", "version_major": 2, "version_minor": 0 }, @@ -315,7 +315,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "dcc88332b5204cb6838d1be0d7610539", + "model_id": "49e444450f5b45a489e1474936aab3fc", "version_major": 2, "version_minor": 0 }, @@ -334,7 +334,7 @@ "\n", "box_layout = Layout(display='flex',\n", " flex_flow='column', \n", - " align_items='stretch', \n", + " align_items='stretch',\n", " border='solid',\n", " width='50%')\n", "\n", @@ -359,7 +359,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "9b1ee4576d1348aa83d08fa972642f5d", + "model_id": "2568f76bc3d94f7ca4922ab92e56598e", "version_major": 2, "version_minor": 0 }, @@ -413,7 +413,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "98f21d2d85d94c629b31300cee3296c4", + "model_id": "354743ad07494ca68fc42a4f656b6aa1", "version_major": 2, "version_minor": 0 }, @@ -469,7 +469,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "3fc572786fc04b5d9ba1775c6cf623c9", + "model_id": "0dfe6ccc43924137ace6f07f58197c70", "version_major": 2, "version_minor": 0 }, @@ -526,203 +526,13 @@ "metadata": {}, "outputs": [], "source": [ - "# from layout_preview import layout\n", - "# layout" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Exercises" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**Four buttons in a box revisted: Change order and orientation**\n", - "\n", - "This example, from earlier in this notebook, lays out 4 buttons vertically.\n", - "\n", - "Flexbox allows you to change the order and orientation of the children items in the flexbox without changing the children themselves.\n", - "\n", - "1. Change the `flex_flow` so that the buttons are displayed in a single column in *reverse order*.\n", - "2. Change the `flex_flow` so that the buttons are displayed in a single *row* instead of a column.\n", - "3. Try setting a few values of `align_items` and describe how it affects the display of the buttons. \n", - "4. Make the box narrower by changing the `width`, then change `flex_flow` to lay out the buttons in rows that wrap so that there is a 2x2 grid of buttons.\n", - "\n", - "Feel free to figure out the layout using the tool above and copy/paste the layout here!" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "c7808762fee74aba820019a3389db5e2", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "Box(children=(Button(button_style='danger', description='correct', layout=Layout(width='auto'), style=ButtonSt…" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "from ipywidgets import Layout, Button, Box\n", - "\n", - "items_layout = Layout(width='auto') # override the default width of the button to 'auto' to let the button grow\n", - "\n", - "box_layout = Layout(display='flex',\n", - " flex_flow='column', \n", - " align_items='stretch', \n", - " border='solid',\n", - " width='20%')\n", - "\n", - "words = ['correct', 'horse', 'battery', 'staple']\n", - "items = [Button(description=word, layout=items_layout, button_style='danger') for word in words]\n", - "box = Box(children=items, layout=box_layout)\n", - "box" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**Carousel revisted: item layout**\n", - "\n", - "The code that generated the carousel is reproduced below. Run the cell, then continue reading." + "from layout_preview import layout\n", + "layout" ] }, { "cell_type": "code", - "execution_count": 12, - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "cd4e4411ae8d4d4383b03e395ad6f2d9", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "VBox(children=(Label(value='Scroll horizontally:'), Box(children=(Button(button_style='warning', description='…" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "from ipywidgets import Layout, Button, Box, Label\n", - "\n", - "item_layout = Layout(height='100px', min_width='40px')\n", - "items = [Button(layout=item_layout, description=str(i), button_style='warning') for i in range(40)]\n", - "box_layout = Layout(overflow_x='scroll',\n", - " border='3px solid black',\n", - " width='500px',\n", - " height='',\n", - " flex_flow='row',\n", - " display='flex')\n", - "carousel = Box(children=items, layout=box_layout)\n", - "VBox([Label('Scroll horizontally:'), carousel])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**To do:**\n", - "\n", - "+ Change the `min_width` for *one* of the `items`, say the first one. Does it affect only the first one, or all of them? Why?\n", - "+ Change the `height` of *only* the first button. *Hint:* It needs its own `Layout`." - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": {}, - "outputs": [], - "source": [ - "items[0].layout.min_width = 'FILL IN WITH A WIDTH'" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## The Grid layout\n", - "\n", - "The `GridBox` class is a special case of the `Box` widget.\n", - "\n", - "The `Box` widget enables the entire CSS flexbox spec, enabling rich reactive layouts in the Jupyter notebook. It aims at providing an efficient way to lay out, align and distribute space among items in a container.\n", - "\n", - "A more detailed description of the [Grid layout is available](reference_guides/guide-grid-box.ipynb).\n", - "\n", - "The whole grid layout spec is exposed via the `layout` attribute of the container widget (`Box`) and the contained items. One may share the same `layout` attribute among all the contained items.\n", - "\n", - "The following flexbox tutorial on the flexbox layout follows the lines of the article [A Complete Guide to Grid](https://css-tricks.com/snippets/css/complete-guide-grid/) by Chris House, and uses text and various images from the article [with permission](https://css-tricks.com/license/).\n", - "\n", - "### Basics\n", - "\n", - "To get started you have to define a container element as a grid with display: grid, set the column and row sizes with grid-template-rows, grid-template-columns, and grid_template_areas, and then place its child elements into the grid with grid-column and grid-row. Similarly to flexbox, the source order of the grid items doesn't matter. Your CSS can place them in any order, which makes it super easy to rearrange your grid with media queries. Imagine defining the layout of your entire page, and then completely rearranging it to accommodate a different screen width all with only a couple lines of CSS. Grid is one of the most powerful CSS modules ever introduced.\n", - "\n", - "### Important terminology\n", - "\n", - "Before diving into the concepts of Grid it's important to understand the terminology. Since the terms involved here are all kinda conceptually similar, it's easy to confuse them with one another if you don't first memorize their meanings defined by the Grid specification. But don't worry, there aren't many of them.\n", - "\n", - "**Grid Container**\n", - "\n", - "The element on which `display: grid` is applied. It's the direct parent of all the grid items. In this example container is the grid container.\n", - "\n", - "```html\n", - "<div class=\"container\">\n", - " <div class=\"item item-1\"></div>\n", - " <div class=\"item item-2\"></div>\n", - " <div class=\"item item-3\"></div>\n", - "</div>\n", - "```\n", - "\n", - "**Grid Item**\n", - "\n", - "The children (e.g. direct descendants) of the grid container. Here the item elements are grid items, but sub-item isn't.\n", - "\n", - "```html\n", - "<div class=\"container\">\n", - " <div class=\"item\"></div> \n", - " <div class=\"item\">\n", - " \t<p class=\"sub-item\"></p>\n", - " </div>\n", - " <div class=\"item\"></div>\n", - "</div>\n", - "```\n", - "\n", - "**Grid Line**\n", - "\n", - "The dividing lines that make up the structure of the grid. They can be either vertical (\"column grid lines\") or horizontal (\"row grid lines\") and reside on either side of a row or column. Here the yellow line is an example of a column grid line.\n", - "\n", - "\n", - "\n", - "**Grid Track**\n", - "\n", - "The space between two adjacent grid lines. You can think of them like the columns or rows of the grid. Here's the grid track between the second and third row grid lines.\n", - "\n", - "\n", - "\n", - "A more detailed description of the [Grid layout is available](reference_guides/guide-grid-box.ipynb). The [Grid layout guide on MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Grid_Layout#Guides) is also excellent." - ] - }, - { - "cell_type": "code", - "execution_count": 14, + "execution_count": 9, "metadata": {}, "outputs": [], "source": [ @@ -738,13 +548,13 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "fca327842fd0479cadfb59a0af54a1ea", + "model_id": "adebd32fb5514b849c7d888a6645db16", "version_major": 2, "version_minor": 0 }, @@ -768,30 +578,6 @@ " )" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Exercises" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**Add more buttons**\n", - "\n", - "Modify the code above to place more buttons in the `GridBox` (do *not* modify the layout). Any number of buttons larger than 9 is fine.\n", - "\n", - "1. What happens to the extra buttons? Are they laid out like the first 9 buttons?\n", - "\n", - "The grid template defines a 3x3 grid. If additional children are placed in the grid their properties are determined by the layout properties `grid_auto_columns`, `grid_auto_rows` and `grid_auto_flow` properties.\n", - "\n", - "2. Set `grid_auto_rows=\"10px\"` and rerun the example with more than 9 buttons.\n", - "\n", - "3. Set `grid_auto_rows` so that the automatically added rows have the same format as the templated rows." - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -811,13 +597,13 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 11, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "e9eccac0c6f644b097cec80bef3c8317", + "model_id": "5e0ccfbca4494d20afc11971c8b4337c", "version_major": 2, "version_minor": 0 }, @@ -860,16 +646,8 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### Exercises" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**Make the main area larger**\n", - "\n", - "1. Add another row or two to the template area so that the main area is 3 rows high and 2 columns wide. " + "# Layout Templates\n", + "See https://ipywidgets.readthedocs.io/en/latest/examples/Layout%20Templates.html" ] } ], diff --git a/dashboards/voila-dashboard.ipynb b/voila-examples/gridstack-example.ipynb similarity index 91% rename from dashboards/voila-dashboard.ipynb rename to voila-examples/gridstack-example.ipynb index 1c6a707..1ec307d 100644 --- a/dashboards/voila-dashboard.ipynb +++ b/voila-examples/gridstack-example.ipynb @@ -4,6 +4,18 @@ "cell_type": "markdown", "metadata": {}, "source": [ + "# Usage\n", + "Start voila with the gridstack template. If `show_handles` is set to True, you can drag the individual dashboard items around.\n", + "```\n", + "voila --template=gridstack --VoilaConfiguration.resources='{\"gridstack\": {\"show_handles\": True}}' ./dashboards/gridstack-example.ipynb\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Demo\n", "This demo uses voila to render a notebook to a custom HTML page using gridstack.js for the layout of each output. In the cell metadata you can change the default cell with and height (in grid units between 1 and 12) by specifying.\n", " * `grid_row`\n", " * `grid_columns`" diff --git a/voila-examples/gridstack-scotch.ipynb b/voila-examples/gridstack-scotch.ipynb new file mode 100644 index 0000000..0d4ff22 --- /dev/null +++ b/voila-examples/gridstack-scotch.ipynb @@ -0,0 +1,924 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Usage\n", + "Start voila with the gridstack template. If `show_handles` is set to True, you can drag the individual dashboard items around.\n", + "```\n", + "voila --template=gridstack --VoilaConfiguration.resources='{\"gridstack\": {\"show_handles\": True}}' ./dashboards/gridstack-scotch.ipynb\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "extensions": { + "jupyter_dashboards": { + "version": 1, + "views": { + "grid_default": { + "col": 0, + "height": 2, + "hidden": false, + "row": 0, + "width": 12 + }, + "report_default": { + "hidden": false + } + } + } + } + }, + "source": [ + "# Got Scotch?" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "extensions": { + "jupyter_dashboards": { + "version": 1, + "views": { + "grid_default": { + "hidden": true + }, + "report_default": { + "hidden": false + } + } + } + } + }, + "source": [ + "In this notebook, we're going to create a dashboard that recommends scotches based on their taste profiles. \n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "extensions": { + "jupyter_dashboards": { + "version": 1, + "views": { + "grid_default": { + "hidden": true + }, + "report_default": { + "hidden": true + } + } + } + } + }, + "outputs": [], + "source": [ + "%matplotlib widget" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "extensions": { + "jupyter_dashboards": { + "version": 1, + "views": { + "grid_default": { + "hidden": true + }, + "report_default": { + "hidden": true + } + } + } + } + }, + "outputs": [], + "source": [ + "import pandas as pd\n", + "import seaborn as sns\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "import os" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "extensions": { + "jupyter_dashboards": { + "version": 1, + "views": { + "grid_default": { + "hidden": true + }, + "report_default": { + "hidden": true + } + } + } + } + }, + "outputs": [], + "source": [ + "import ipywidgets as widgets\n", + "from traitlets import Unicode, List, Instance, link, HasTraits\n", + "from IPython.display import display, clear_output, HTML, Javascript" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "extensions": { + "jupyter_dashboards": { + "version": 1, + "views": { + "grid_default": { + "col": 0, + "height": 4, + "hidden": true, + "row": 14, + "width": 4 + }, + "report_default": {} + } + } + } + }, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "7c98a57403e24f81b1162defb578f5e7", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Button(style=ButtonStyle())" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "display(widgets.Button())" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "extensions": { + "jupyter_dashboards": { + "version": 1, + "views": { + "grid_default": { + "hidden": true + }, + "report_default": { + "hidden": false + } + } + } + } + }, + "source": [ + "## Load Data <span style=\"float: right; font-size: 0.5em\"><a href=\"#Got-Scotch?\">Top</a></span>" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "extensions": { + "jupyter_dashboards": { + "version": 1, + "views": { + "grid_default": { + "hidden": true + }, + "report_default": {} + } + } + } + }, + "outputs": [], + "source": [ + "\n", + "\n", + "features = [[2, 2, 2, 0, 0, 2, 1, 2, 2, 2, 2, 2],\n", + " [3, 3, 1, 0, 0, 4, 3, 2, 2, 3, 3, 2],\n", + " [1, 3, 2, 0, 0, 2, 0, 0, 2, 2, 3, 1],\n", + " [4, 1, 4, 4, 0, 0, 2, 0, 1, 2, 1, 0],\n", + " [2, 2, 2, 0, 0, 1, 1, 1, 2, 3, 1, 3],\n", + " [2, 3, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1],\n", + " [0, 2, 0, 0, 0, 1, 1, 0, 2, 2, 3, 1],\n", + " [2, 3, 1, 0, 0, 2, 1, 2, 2, 2, 2, 2],\n", + " [2, 2, 1, 0, 0, 1, 0, 0, 2, 2, 2, 1],\n", + " [2, 3, 2, 1, 0, 0, 2, 0, 2, 1, 2, 3],\n", + " [4, 3, 2, 0, 0, 2, 1, 3, 3, 0, 1, 2],\n", + " [3, 2, 1, 0, 0, 3, 2, 1, 0, 2, 2, 2],\n", + " [4, 2, 2, 0, 0, 2, 2, 0, 2, 2, 2, 2],\n", + " [2, 2, 1, 0, 0, 2, 2, 0, 0, 2, 3, 1],\n", + " [3, 2, 2, 0, 0, 3, 1, 1, 2, 3, 2, 2],\n", + " [2, 2, 2, 0, 0, 2, 2, 1, 2, 2, 2, 2],\n", + " [1, 2, 1, 0, 0, 0, 1, 1, 0, 2, 2, 1],\n", + " [2, 2, 2, 0, 0, 1, 2, 2, 2, 2, 2, 2],\n", + " [2, 2, 3, 1, 0, 2, 2, 1, 1, 1, 1, 3],\n", + " [1, 1, 2, 2, 0, 2, 2, 1, 2, 2, 2, 3],\n", + " [1, 2, 1, 1, 0, 1, 1, 1, 1, 2, 2, 1],\n", + " [3, 1, 4, 2, 1, 0, 2, 0, 2, 1, 1, 0],\n", + " [1, 3, 1, 0, 0, 1, 1, 0, 2, 2, 2, 1],\n", + " [3, 2, 3, 3, 1, 0, 2, 0, 1, 1, 2, 0],\n", + " [2, 2, 2, 0, 1, 2, 2, 1, 2, 2, 1, 2],\n", + " [2, 3, 2, 1, 0, 0, 1, 0, 2, 2, 2, 1],\n", + " [4, 2, 2, 0, 0, 1, 2, 2, 2, 2, 2, 2],\n", + " [3, 2, 2, 1, 0, 1, 2, 2, 1, 2, 3, 2],\n", + " [2, 2, 2, 0, 0, 2, 1, 0, 1, 2, 2, 1],\n", + " [2, 2, 1, 0, 0, 2, 1, 1, 1, 3, 2, 2],\n", + " [2, 3, 1, 1, 0, 0, 0, 0, 1, 2, 2, 1],\n", + " [2, 3, 1, 0, 0, 2, 1, 1, 4, 2, 2, 2],\n", + " [2, 3, 1, 1, 1, 1, 1, 2, 0, 2, 0, 3],\n", + " [2, 3, 1, 0, 0, 2, 1, 1, 1, 1, 2, 1],\n", + " [2, 1, 3, 0, 0, 0, 3, 1, 0, 2, 2, 3],\n", + " [1, 2, 0, 0, 0, 1, 0, 1, 2, 1, 2, 1],\n", + " [2, 3, 1, 0, 0, 1, 2, 1, 2, 1, 2, 2],\n", + " [1, 2, 1, 0, 0, 1, 2, 1, 2, 2, 2, 1],\n", + " [3, 2, 1, 0, 0, 1, 2, 1, 1, 2, 2, 2],\n", + " [2, 2, 2, 2, 0, 1, 0, 1, 2, 2, 1, 3],\n", + " [1, 3, 1, 0, 0, 0, 1, 1, 1, 2, 0, 1],\n", + " [1, 3, 1, 0, 0, 1, 1, 0, 1, 2, 2, 1],\n", + " [4, 2, 2, 0, 0, 2, 1, 4, 2, 2, 2, 2],\n", + " [3, 2, 1, 0, 0, 2, 1, 2, 1, 2, 3, 2],\n", + " [2, 4, 1, 0, 0, 1, 2, 3, 2, 3, 2, 2],\n", + " [1, 3, 1, 0, 0, 0, 0, 0, 0, 2, 2, 1],\n", + " [1, 2, 0, 0, 0, 1, 1, 1, 2, 2, 3, 1],\n", + " [1, 2, 1, 0, 0, 1, 2, 0, 0, 2, 2, 1],\n", + " [2, 3, 1, 0, 0, 2, 2, 2, 1, 2, 2, 2],\n", + " [1, 2, 1, 0, 0, 1, 2, 0, 1, 2, 2, 1],\n", + " [2, 2, 1, 1, 0, 1, 2, 0, 2, 1, 2, 1],\n", + " [2, 3, 1, 0, 0, 1, 1, 2, 1, 2, 2, 2],\n", + " [2, 3, 1, 0, 0, 2, 2, 2, 2, 2, 1, 2],\n", + " [2, 2, 3, 1, 0, 2, 1, 1, 1, 2, 1, 3],\n", + " [1, 3, 1, 1, 0, 2, 2, 0, 1, 2, 1, 1],\n", + " [2, 1, 2, 2, 0, 1, 1, 0, 2, 1, 1, 3],\n", + " [2, 3, 1, 0, 0, 2, 2, 1, 2, 1, 2, 2],\n", + " [4, 1, 4, 4, 1, 0, 1, 2, 1, 1, 1, 0],\n", + " [4, 2, 4, 4, 1, 0, 0, 1, 1, 1, 0, 0],\n", + " [2, 3, 1, 0, 0, 1, 1, 2, 0, 1, 3, 1],\n", + " [1, 1, 1, 1, 0, 1, 1, 0, 1, 2, 1, 1],\n", + " [3, 2, 1, 0, 0, 1, 1, 1, 3, 3, 2, 2],\n", + " [4, 3, 1, 0, 0, 2, 1, 4, 2, 2, 3, 2],\n", + " [2, 1, 1, 0, 0, 1, 1, 1, 2, 1, 2, 1],\n", + " [2, 4, 1, 0, 0, 1, 0, 0, 2, 1, 1, 1],\n", + " [3, 2, 2, 0, 0, 2, 3, 3, 2, 1, 2, 2],\n", + " [2, 2, 2, 2, 0, 0, 2, 0, 2, 2, 2, 3],\n", + " [1, 2, 2, 0, 1, 2, 2, 1, 2, 3, 1, 3],\n", + " [2, 1, 2, 2, 1, 0, 1, 1, 2, 2, 2, 3],\n", + " [2, 3, 2, 1, 1, 1, 2, 1, 0, 2, 3, 1],\n", + " [3, 2, 2, 0, 0, 2, 2, 2, 2, 2, 3, 2],\n", + " [2, 2, 1, 1, 0, 2, 1, 1, 2, 2, 2, 2],\n", + " [2, 4, 1, 0, 0, 2, 1, 0, 0, 2, 1, 1],\n", + " [2, 2, 1, 0, 0, 1, 0, 1, 2, 2, 2, 1],\n", + " [2, 2, 2, 2, 0, 2, 2, 1, 2, 1, 0, 3],\n", + " [2, 2, 1, 0, 0, 2, 2, 2, 3, 3, 3, 2],\n", + " [2, 3, 1, 0, 0, 0, 2, 0, 2, 1, 3, 1],\n", + " [4, 2, 3, 3, 0, 1, 3, 0, 1, 2, 2, 0],\n", + " [1, 2, 1, 0, 0, 2, 0, 1, 1, 2, 2, 1],\n", + " [1, 3, 2, 0, 0, 0, 2, 0, 2, 1, 2, 1],\n", + " [2, 2, 2, 1, 0, 0, 2, 0, 0, 0, 2, 3],\n", + " [1, 1, 1, 0, 0, 1, 0, 0, 1, 2, 2, 1],\n", + " [2, 3, 2, 0, 0, 2, 2, 1, 1, 2, 0, 3],\n", + " [0, 3, 1, 0, 0, 2, 2, 1, 1, 2, 1, 1],\n", + " [2, 2, 1, 0, 0, 1, 0, 1, 2, 1, 0, 3],\n", + " [2, 3, 0, 0, 1, 0, 2, 1, 1, 2, 2, 1]]\n", + "\n", + "feature_names = ['Body', 'Sweetness', 'Smoky', \n", + " 'Medicinal', 'Tobacco', 'Honey',\n", + " 'Spicy', 'Winey', 'Nutty',\n", + " 'Malty', 'Fruity', 'cluster']\n", + "\n", + "brand_names = ['Aberfeldy',\n", + " 'Aberlour',\n", + " 'AnCnoc',\n", + " 'Ardbeg',\n", + " 'Ardmore',\n", + " 'ArranIsleOf',\n", + " 'Auchentoshan',\n", + " 'Auchroisk',\n", + " 'Aultmore',\n", + " 'Balblair',\n", + " 'Balmenach',\n", + " 'Belvenie',\n", + " 'BenNevis',\n", + " 'Benriach',\n", + " 'Benrinnes',\n", + " 'Benromach',\n", + " 'Bladnoch',\n", + " 'BlairAthol',\n", + " 'Bowmore',\n", + " 'Bruichladdich',\n", + " 'Bunnahabhain',\n", + " 'Caol Ila',\n", + " 'Cardhu',\n", + " 'Clynelish',\n", + " 'Craigallechie',\n", + " 'Craigganmore',\n", + " 'Dailuaine',\n", + " 'Dalmore',\n", + " 'Dalwhinnie',\n", + " 'Deanston',\n", + " 'Dufftown',\n", + " 'Edradour',\n", + " 'GlenDeveronMacduff',\n", + " 'GlenElgin',\n", + " 'GlenGarioch',\n", + " 'GlenGrant',\n", + " 'GlenKeith',\n", + " 'GlenMoray',\n", + " 'GlenOrd',\n", + " 'GlenScotia',\n", + " 'GlenSpey',\n", + " 'Glenallachie',\n", + " 'Glendronach',\n", + " 'Glendullan',\n", + " 'Glenfarclas',\n", + " 'Glenfiddich',\n", + " 'Glengoyne',\n", + " 'Glenkinchie',\n", + " 'Glenlivet',\n", + " 'Glenlossie',\n", + " 'Glenmorangie',\n", + " 'Glenrothes',\n", + " 'Glenturret',\n", + " 'Highland Park',\n", + " 'Inchgower',\n", + " 'Isle of Jura',\n", + " 'Knochando',\n", + " 'Lagavulin',\n", + " 'Laphroig',\n", + " 'Linkwood',\n", + " 'Loch Lomond',\n", + " 'Longmorn',\n", + " 'Macallan',\n", + " 'Mannochmore',\n", + " 'Miltonduff',\n", + " 'Mortlach',\n", + " 'Oban',\n", + " 'OldFettercairn',\n", + " 'OldPulteney',\n", + " 'RoyalBrackla',\n", + " 'RoyalLochnagar',\n", + " 'Scapa',\n", + " 'Speyburn',\n", + " 'Speyside',\n", + " 'Springbank',\n", + " 'Strathisla',\n", + " 'Strathmill',\n", + " 'Talisker',\n", + " 'Tamdhu',\n", + " 'Tamnavulin',\n", + " 'Teaninich',\n", + " 'Tobermory',\n", + " 'Tomatin',\n", + " 'Tomintoul',\n", + " 'Tormore',\n", + " 'Tullibardine']" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "extensions": { + "jupyter_dashboards": { + "version": 1, + "views": { + "grid_default": { + "hidden": true + }, + "report_default": {} + } + } + } + }, + "outputs": [], + "source": [ + "features_df = pd.DataFrame(features, columns=feature_names, index=brand_names)\n", + "features_df = features_df.drop('cluster', axis=1)" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "extensions": { + "jupyter_dashboards": { + "version": 1, + "views": { + "grid_default": { + "hidden": true + }, + "report_default": {} + } + } + } + }, + "outputs": [], + "source": [ + "norm = (features_df ** 2).sum(axis=1).apply('sqrt')\n", + "normed_df = features_df.divide(norm, axis=0)\n", + "sim_df = normed_df.dot(normed_df.T)" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "extensions": { + "jupyter_dashboards": { + "version": 1, + "views": { + "grid_default": { + "hidden": true + }, + "report_default": { + "hidden": false + } + } + } + } + }, + "outputs": [], + "source": [ + "def radar(df, ax=None):\n", + " # calculate evenly-spaced axis angles\n", + " num_vars = len(df.columns)\n", + " theta = 2*np.pi * np.linspace(0, 1-1./num_vars, num_vars)\n", + " # rotate theta such that the first axis is at the top\n", + " theta += np.pi/2\n", + " if not ax:\n", + " fig = plt.figure(figsize=(4, 4))\n", + "\n", + " ax = fig.add_subplot(1,1,1, projection='polar')\n", + " else:\n", + " ax.clear()\n", + " for d, color in zip(df.itertuples(), sns.color_palette()):\n", + " ax.plot(theta, d[1:], color=color, alpha=0.7)\n", + " ax.fill(theta, d[1:], facecolor=color, alpha=0.5)\n", + " ax.set_xticklabels(df.columns)\n", + "\n", + " legend = ax.legend(df.index, loc=(0.9, .95))\n", + " return ax" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true, + "extensions": { + "jupyter_dashboards": { + "version": 1, + "views": { + "grid_default": { + "hidden": true + }, + "report_default": {} + } + } + }, + "jupyter": { + "outputs_hidden": true + } + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "extensions": { + "jupyter_dashboards": { + "version": 1, + "views": { + "grid_default": { + "hidden": true + }, + "report_default": { + "hidden": false + } + } + } + } + }, + "outputs": [], + "source": [ + "class RadarWidget(HasTraits):\n", + "\n", + " factors_keys = List(['Aberfeldy'])\n", + " \n", + " def __init__(self, df, **kwargs):\n", + " self.df = df\n", + " super(RadarWidget, self).__init__(**kwargs)\n", + " self.ax = None\n", + " self.factors_keys_changed()\n", + " \n", + " \n", + " def factors_keys_changed(self):\n", + " new_value = self.factors_keys\n", + " if self.ax:\n", + " self.ax.clear()\n", + " self.ax = radar(self.df.loc[new_value], self.ax)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "extensions": { + "jupyter_dashboards": { + "version": 1, + "views": { + "grid_default": { + "hidden": true + }, + "report_default": { + "hidden": false + } + } + } + } + }, + "source": [ + "We now define a *get_similar( )* function to return the data of the top n similar scotches to a given scotch." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "extensions": { + "jupyter_dashboards": { + "version": 1, + "views": { + "grid_default": { + "hidden": true + }, + "report_default": { + "hidden": false + } + } + } + } + }, + "outputs": [], + "source": [ + "def get_similar(name, n, top=True):\n", + " a = sim_df[name].sort_values(ascending=False)\n", + " a.name = 'Similarity'\n", + " df = pd.DataFrame(a) #.join(features_df).iloc[start:end]\n", + " return df.head(n) if top else df.tail(n)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "extensions": { + "jupyter_dashboards": { + "version": 1, + "views": { + "grid_default": { + "hidden": true + }, + "report_default": { + "hidden": false + } + } + } + } + }, + "source": [ + "We also need a function *on_pick_scotch* that will display a table of the top 5 similar scotches that Radar View watches, based on a given selected Scotch." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "extensions": { + "jupyter_dashboards": { + "version": 1, + "views": { + "grid_default": { + "hidden": true + }, + "report_default": { + "hidden": false + } + } + } + } + }, + "outputs": [], + "source": [ + "def on_pick_scotch(Scotch):\n", + " name = Scotch\n", + " # Get top 6 similar whiskeys, and remove this one\n", + " top_df = get_similar(name, 6).iloc[1:]\n", + " # Get bottom 5 similar whiskeys\n", + " df = top_df\n", + " \n", + " # Make table index a set of links that the radar widget will watch\n", + " df.index = ['''<a class=\"scotch\" href=\"#\" data-factors_keys='[\"{}\",\"{}\"]'>{}</a>'''.format(name, i, i) for i in df.index]\n", + " \n", + " tmpl = f'''<p>If you like {name} you might want to try these five brands. Click one to see how its taste profile compares.</p>'''\n", + " prompt_w.value = tmpl\n", + " table.value = df.to_html(escape=False)\n", + " radar_w.factors_keys = [name]\n", + " plot = radar_w.factors_keys_changed()" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "extensions": { + "jupyter_dashboards": { + "version": 1, + "views": { + "grid_default": { + "col": 0, + "height": 2, + "hidden": false, + "row": 2, + "width": 12 + }, + "report_default": { + "hidden": false + } + } + } + } + }, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "d88cc57f5ce444bfb58a080cc9c12aa4", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "HTML(value='Aberfeldy')" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "prompt_w = widgets.HTML(value='Aberfeldy')\n", + "display(prompt_w)" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "extensions": { + "jupyter_dashboards": { + "version": 1, + "views": { + "grid_default": { + "col": 0, + "height": 6, + "hidden": false, + "row": 6, + "width": 7 + }, + "report_default": {} + } + } + } + }, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "ffa6b42a2fec42a0aed90f95f4a7cc32", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "HTML(value='Hello <b>World</b>')" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "table = widgets.HTML(\n", + " value=\"Hello <b>World</b>\"\n", + ")\n", + "display(table)" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "extensions": { + "jupyter_dashboards": { + "version": 1, + "views": { + "grid_default": { + "col": 7, + "height": 8, + "hidden": false, + "row": 4, + "width": 5 + }, + "report_default": { + "hidden": false + } + } + } + } + }, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "93c9dc73eec6494db4a09769a5bf56e7", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Canvas(toolbar=Toolbar(toolitems=[('Home', 'Reset original view', 'home', 'home'), ('Back', 'Back to previous …" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "radar_w = RadarWidget(df=features_df)" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": { + "extensions": { + "jupyter_dashboards": { + "version": 1, + "views": { + "grid_default": { + "col": 0, + "height": 2, + "hidden": false, + "row": 4, + "width": 7 + }, + "report_default": { + "hidden": false + } + } + } + } + }, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "0db0914efd8844ffbdeaea2f2f386776", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "interactive(children=(Dropdown(description='Scotch', options=('Aberfeldy', 'Aberlour', 'AnCnoc', 'Ardbeg', 'Ar…" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "picker_w = widgets.interact(on_pick_scotch, Scotch=list(sim_df.index))" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": { + "extensions": { + "jupyter_dashboards": { + "version": 1, + "views": { + "grid_default": { + "col": 8, + "height": 4, + "hidden": true, + "row": 14, + "width": 4 + }, + "report_default": {} + } + } + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "['Aberfeldy']" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "radar_w.factors_keys" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "extensions": { + "jupyter_dashboards": { + "version": 1, + "views": { + "grid_default": { + "col": 0, + "height": 2, + "hidden": false, + "row": 12, + "width": 12 + }, + "report_default": { + "hidden": false + } + } + } + } + }, + "source": [ + "Powered by data from https://www.mathstat.strath.ac.uk/outreach/nessie/nessie_whisky.html and inspired by analysis from http://blog.revolutionanalytics.com/2013/12/k-means-clustering-86-single-malt-scotch-whiskies.html. This dashboard originated as a Jupyter Notebook." + ] + } + ], + "metadata": { + "anaconda-cloud": {}, + "celltoolbar": "Edit Metadata", + "extensions": { + "jupyter_dashboards": { + "activeView": "grid_default", + "version": 1, + "views": { + "grid_default": { + "cellMargin": 10, + "defaultCellHeight": 50, + "maxColumns": 12, + "name": "grid", + "type": "grid" + }, + "report_default": { + "name": "report", + "type": "report" + } + } + } + }, + "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", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.8" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/voila-examples/voila-basic.ipynb b/voila-examples/voila-basic.ipynb new file mode 100644 index 0000000..b42018e --- /dev/null +++ b/voila-examples/voila-basic.ipynb @@ -0,0 +1,672 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "af650d1282d74970b9ae5d3276834b6a", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "VBox(children=(FloatSlider(value=4.0, description='$x$'), FloatText(value=0.0, description='$x^2$', disabled=T…" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import ipywidgets as widgets\n", + "\n", + "slider = widgets.FloatSlider(description='$x$', value=4)\n", + "text = widgets.FloatText(disabled=True, description='$x^2$')\n", + "\n", + "def compute(*ignore):\n", + " text.value = str(slider.value ** 2)\n", + "\n", + "slider.observe(compute, 'value')\n", + "\n", + "widgets.VBox([slider, text])" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "<div>\n", + "<style scoped>\n", + " .dataframe tbody tr th:only-of-type {\n", + " vertical-align: middle;\n", + " }\n", + "\n", + " .dataframe tbody tr th {\n", + " vertical-align: top;\n", + " }\n", + "\n", + " .dataframe thead th {\n", + " text-align: right;\n", + " }\n", + "</style>\n", + "<table border=\"1\" class=\"dataframe\">\n", + " <thead>\n", + " <tr style=\"text-align: right;\">\n", + " <th></th>\n", + " <th>sepal_length</th>\n", + " <th>sepal_width</th>\n", + " <th>petal_length</th>\n", + " <th>petal_width</th>\n", + " <th>species</th>\n", + " </tr>\n", + " </thead>\n", + " <tbody>\n", + " <tr>\n", + " <th>0</th>\n", + " <td>5.1</td>\n", + " <td>3.5</td>\n", + " <td>1.4</td>\n", + " <td>0.2</td>\n", + " <td>setosa</td>\n", + " </tr>\n", + " <tr>\n", + " <th>1</th>\n", + " <td>4.9</td>\n", + " <td>3.0</td>\n", + " <td>1.4</td>\n", + " <td>0.2</td>\n", + " <td>setosa</td>\n", + " </tr>\n", + " <tr>\n", + " <th>2</th>\n", + " <td>4.7</td>\n", + " <td>3.2</td>\n", + " <td>1.3</td>\n", + " <td>0.2</td>\n", + " <td>setosa</td>\n", + " </tr>\n", + " <tr>\n", + " <th>3</th>\n", + " <td>4.6</td>\n", + " <td>3.1</td>\n", + " <td>1.5</td>\n", + " <td>0.2</td>\n", + " <td>setosa</td>\n", + " </tr>\n", + " <tr>\n", + " <th>4</th>\n", + " <td>5.0</td>\n", + " <td>3.6</td>\n", + " <td>1.4</td>\n", + " <td>0.2</td>\n", + " <td>setosa</td>\n", + " </tr>\n", + " <tr>\n", + " <th>5</th>\n", + " <td>5.4</td>\n", + " <td>3.9</td>\n", + " <td>1.7</td>\n", + " <td>0.4</td>\n", + " <td>setosa</td>\n", + " </tr>\n", + " <tr>\n", + " <th>6</th>\n", + " <td>4.6</td>\n", + " <td>3.4</td>\n", + " <td>1.4</td>\n", + " <td>0.3</td>\n", + " <td>setosa</td>\n", + " </tr>\n", + " <tr>\n", + " <th>7</th>\n", + " <td>5.0</td>\n", + " <td>3.4</td>\n", + " <td>1.5</td>\n", + " <td>0.2</td>\n", + " <td>setosa</td>\n", + " </tr>\n", + " <tr>\n", + " <th>8</th>\n", + " <td>4.4</td>\n", + " <td>2.9</td>\n", + " <td>1.4</td>\n", + " <td>0.2</td>\n", + " <td>setosa</td>\n", + " </tr>\n", + " <tr>\n", + " <th>9</th>\n", + " <td>4.9</td>\n", + " <td>3.1</td>\n", + " <td>1.5</td>\n", + " <td>0.1</td>\n", + " <td>setosa</td>\n", + " </tr>\n", + " <tr>\n", + " <th>10</th>\n", + " <td>5.4</td>\n", + " <td>3.7</td>\n", + " <td>1.5</td>\n", + " <td>0.2</td>\n", + " <td>setosa</td>\n", + " </tr>\n", + " <tr>\n", + " <th>11</th>\n", + " <td>4.8</td>\n", + " <td>3.4</td>\n", + " <td>1.6</td>\n", + " <td>0.2</td>\n", + " <td>setosa</td>\n", + " </tr>\n", + " <tr>\n", + " <th>12</th>\n", + " <td>4.8</td>\n", + " <td>3.0</td>\n", + " <td>1.4</td>\n", + " <td>0.1</td>\n", + " <td>setosa</td>\n", + " </tr>\n", + " <tr>\n", + " <th>13</th>\n", + " <td>4.3</td>\n", + " <td>3.0</td>\n", + " <td>1.1</td>\n", + " <td>0.1</td>\n", + " <td>setosa</td>\n", + " </tr>\n", + " <tr>\n", + " <th>14</th>\n", + " <td>5.8</td>\n", + " <td>4.0</td>\n", + " <td>1.2</td>\n", + " <td>0.2</td>\n", + " <td>setosa</td>\n", + " </tr>\n", + " <tr>\n", + " <th>15</th>\n", + " <td>5.7</td>\n", + " <td>4.4</td>\n", + " <td>1.5</td>\n", + " <td>0.4</td>\n", + " <td>setosa</td>\n", + " </tr>\n", + " <tr>\n", + " <th>16</th>\n", + " <td>5.4</td>\n", + " <td>3.9</td>\n", + " <td>1.3</td>\n", + " <td>0.4</td>\n", + " <td>setosa</td>\n", + " </tr>\n", + " <tr>\n", + " <th>17</th>\n", + " <td>5.1</td>\n", + " <td>3.5</td>\n", + " <td>1.4</td>\n", + " <td>0.3</td>\n", + " <td>setosa</td>\n", + " </tr>\n", + " <tr>\n", + " <th>18</th>\n", + " <td>5.7</td>\n", + " <td>3.8</td>\n", + " <td>1.7</td>\n", + " <td>0.3</td>\n", + " <td>setosa</td>\n", + " </tr>\n", + " <tr>\n", + " <th>19</th>\n", + " <td>5.1</td>\n", + " <td>3.8</td>\n", + " <td>1.5</td>\n", + " <td>0.3</td>\n", + " <td>setosa</td>\n", + " </tr>\n", + " <tr>\n", + " <th>20</th>\n", + " <td>5.4</td>\n", + " <td>3.4</td>\n", + " <td>1.7</td>\n", + " <td>0.2</td>\n", + " <td>setosa</td>\n", + " </tr>\n", + " <tr>\n", + " <th>21</th>\n", + " <td>5.1</td>\n", + " <td>3.7</td>\n", + " <td>1.5</td>\n", + " <td>0.4</td>\n", + " <td>setosa</td>\n", + " </tr>\n", + " <tr>\n", + " <th>22</th>\n", + " <td>4.6</td>\n", + " <td>3.6</td>\n", + " <td>1.0</td>\n", + " <td>0.2</td>\n", + " <td>setosa</td>\n", + " </tr>\n", + " <tr>\n", + " <th>23</th>\n", + " <td>5.1</td>\n", + " <td>3.3</td>\n", + " <td>1.7</td>\n", + " <td>0.5</td>\n", + " <td>setosa</td>\n", + " </tr>\n", + " <tr>\n", + " <th>24</th>\n", + " <td>4.8</td>\n", + " <td>3.4</td>\n", + " <td>1.9</td>\n", + " <td>0.2</td>\n", + " <td>setosa</td>\n", + " </tr>\n", + " <tr>\n", + " <th>25</th>\n", + " <td>5.0</td>\n", + " <td>3.0</td>\n", + " <td>1.6</td>\n", + " <td>0.2</td>\n", + " <td>setosa</td>\n", + " </tr>\n", + " <tr>\n", + " <th>26</th>\n", + " <td>5.0</td>\n", + " <td>3.4</td>\n", + " <td>1.6</td>\n", + " <td>0.4</td>\n", + " <td>setosa</td>\n", + " </tr>\n", + " <tr>\n", + " <th>27</th>\n", + " <td>5.2</td>\n", + " <td>3.5</td>\n", + " <td>1.5</td>\n", + " <td>0.2</td>\n", + " <td>setosa</td>\n", + " </tr>\n", + " <tr>\n", + " <th>28</th>\n", + " <td>5.2</td>\n", + " <td>3.4</td>\n", + " <td>1.4</td>\n", + " <td>0.2</td>\n", + " <td>setosa</td>\n", + " </tr>\n", + " <tr>\n", + " <th>29</th>\n", + " <td>4.7</td>\n", + " <td>3.2</td>\n", + " <td>1.6</td>\n", + " <td>0.2</td>\n", + " <td>setosa</td>\n", + " </tr>\n", + " <tr>\n", + " <th>...</th>\n", + " <td>...</td>\n", + " <td>...</td>\n", + " <td>...</td>\n", + " <td>...</td>\n", + " <td>...</td>\n", + " </tr>\n", + " <tr>\n", + " <th>120</th>\n", + " <td>6.9</td>\n", + " <td>3.2</td>\n", + " <td>5.7</td>\n", + " <td>2.3</td>\n", + " <td>virginica</td>\n", + " </tr>\n", + " <tr>\n", + " <th>121</th>\n", + " <td>5.6</td>\n", + " <td>2.8</td>\n", + " <td>4.9</td>\n", + " <td>2.0</td>\n", + " <td>virginica</td>\n", + " </tr>\n", + " <tr>\n", + " <th>122</th>\n", + " <td>7.7</td>\n", + " <td>2.8</td>\n", + " <td>6.7</td>\n", + " <td>2.0</td>\n", + " <td>virginica</td>\n", + " </tr>\n", + " <tr>\n", + " <th>123</th>\n", + " <td>6.3</td>\n", + " <td>2.7</td>\n", + " <td>4.9</td>\n", + " <td>1.8</td>\n", + " <td>virginica</td>\n", + " </tr>\n", + " <tr>\n", + " <th>124</th>\n", + " <td>6.7</td>\n", + " <td>3.3</td>\n", + " <td>5.7</td>\n", + " <td>2.1</td>\n", + " <td>virginica</td>\n", + " </tr>\n", + " <tr>\n", + " <th>125</th>\n", + " <td>7.2</td>\n", + " <td>3.2</td>\n", + " <td>6.0</td>\n", + " <td>1.8</td>\n", + " <td>virginica</td>\n", + " </tr>\n", + " <tr>\n", + " <th>126</th>\n", + " <td>6.2</td>\n", + " <td>2.8</td>\n", + " <td>4.8</td>\n", + " <td>1.8</td>\n", + " <td>virginica</td>\n", + " </tr>\n", + " <tr>\n", + " <th>127</th>\n", + " <td>6.1</td>\n", + " <td>3.0</td>\n", + " <td>4.9</td>\n", + " <td>1.8</td>\n", + " <td>virginica</td>\n", + " </tr>\n", + " <tr>\n", + " <th>128</th>\n", + " <td>6.4</td>\n", + " <td>2.8</td>\n", + " <td>5.6</td>\n", + " <td>2.1</td>\n", + " <td>virginica</td>\n", + " </tr>\n", + " <tr>\n", + " <th>129</th>\n", + " <td>7.2</td>\n", + " <td>3.0</td>\n", + " <td>5.8</td>\n", + " <td>1.6</td>\n", + " <td>virginica</td>\n", + " </tr>\n", + " <tr>\n", + " <th>130</th>\n", + " <td>7.4</td>\n", + " <td>2.8</td>\n", + " <td>6.1</td>\n", + " <td>1.9</td>\n", + " <td>virginica</td>\n", + " </tr>\n", + " <tr>\n", + " <th>131</th>\n", + " <td>7.9</td>\n", + " <td>3.8</td>\n", + " <td>6.4</td>\n", + " <td>2.0</td>\n", + " <td>virginica</td>\n", + " </tr>\n", + " <tr>\n", + " <th>132</th>\n", + " <td>6.4</td>\n", + " <td>2.8</td>\n", + " <td>5.6</td>\n", + " <td>2.2</td>\n", + " <td>virginica</td>\n", + " </tr>\n", + " <tr>\n", + " <th>133</th>\n", + " <td>6.3</td>\n", + " <td>2.8</td>\n", + " <td>5.1</td>\n", + " <td>1.5</td>\n", + " <td>virginica</td>\n", + " </tr>\n", + " <tr>\n", + " <th>134</th>\n", + " <td>6.1</td>\n", + " <td>2.6</td>\n", + " <td>5.6</td>\n", + " <td>1.4</td>\n", + " <td>virginica</td>\n", + " </tr>\n", + " <tr>\n", + " <th>135</th>\n", + " <td>7.7</td>\n", + " <td>3.0</td>\n", + " <td>6.1</td>\n", + " <td>2.3</td>\n", + " <td>virginica</td>\n", + " </tr>\n", + " <tr>\n", + " <th>136</th>\n", + " <td>6.3</td>\n", + " <td>3.4</td>\n", + " <td>5.6</td>\n", + " <td>2.4</td>\n", + " <td>virginica</td>\n", + " </tr>\n", + " <tr>\n", + " <th>137</th>\n", + " <td>6.4</td>\n", + " <td>3.1</td>\n", + " <td>5.5</td>\n", + " <td>1.8</td>\n", + " <td>virginica</td>\n", + " </tr>\n", + " <tr>\n", + " <th>138</th>\n", + " <td>6.0</td>\n", + " <td>3.0</td>\n", + " <td>4.8</td>\n", + " <td>1.8</td>\n", + " <td>virginica</td>\n", + " </tr>\n", + " <tr>\n", + " <th>139</th>\n", + " <td>6.9</td>\n", + " <td>3.1</td>\n", + " <td>5.4</td>\n", + " <td>2.1</td>\n", + " <td>virginica</td>\n", + " </tr>\n", + " <tr>\n", + " <th>140</th>\n", + " <td>6.7</td>\n", + " <td>3.1</td>\n", + " <td>5.6</td>\n", + " <td>2.4</td>\n", + " <td>virginica</td>\n", + " </tr>\n", + " <tr>\n", + " <th>141</th>\n", + " <td>6.9</td>\n", + " <td>3.1</td>\n", + " <td>5.1</td>\n", + " <td>2.3</td>\n", + " <td>virginica</td>\n", + " </tr>\n", + " <tr>\n", + " <th>142</th>\n", + " <td>5.8</td>\n", + " <td>2.7</td>\n", + " <td>5.1</td>\n", + " <td>1.9</td>\n", + " <td>virginica</td>\n", + " </tr>\n", + " <tr>\n", + " <th>143</th>\n", + " <td>6.8</td>\n", + " <td>3.2</td>\n", + " <td>5.9</td>\n", + " <td>2.3</td>\n", + " <td>virginica</td>\n", + " </tr>\n", + " <tr>\n", + " <th>144</th>\n", + " <td>6.7</td>\n", + " <td>3.3</td>\n", + " <td>5.7</td>\n", + " <td>2.5</td>\n", + " <td>virginica</td>\n", + " </tr>\n", + " <tr>\n", + " <th>145</th>\n", + " <td>6.7</td>\n", + " <td>3.0</td>\n", + " <td>5.2</td>\n", + " <td>2.3</td>\n", + " <td>virginica</td>\n", + " </tr>\n", + " <tr>\n", + " <th>146</th>\n", + " <td>6.3</td>\n", + " <td>2.5</td>\n", + " <td>5.0</td>\n", + " <td>1.9</td>\n", + " <td>virginica</td>\n", + " </tr>\n", + " <tr>\n", + " <th>147</th>\n", + " <td>6.5</td>\n", + " <td>3.0</td>\n", + " <td>5.2</td>\n", + " <td>2.0</td>\n", + " <td>virginica</td>\n", + " </tr>\n", + " <tr>\n", + " <th>148</th>\n", + " <td>6.2</td>\n", + " <td>3.4</td>\n", + " <td>5.4</td>\n", + " <td>2.3</td>\n", + " <td>virginica</td>\n", + " </tr>\n", + " <tr>\n", + " <th>149</th>\n", + " <td>5.9</td>\n", + " <td>3.0</td>\n", + " <td>5.1</td>\n", + " <td>1.8</td>\n", + " <td>virginica</td>\n", + " </tr>\n", + " </tbody>\n", + "</table>\n", + "<p>150 rows × 5 columns</p>\n", + "</div>" + ], + "text/plain": [ + " sepal_length sepal_width petal_length petal_width species\n", + "0 5.1 3.5 1.4 0.2 setosa\n", + "1 4.9 3.0 1.4 0.2 setosa\n", + "2 4.7 3.2 1.3 0.2 setosa\n", + "3 4.6 3.1 1.5 0.2 setosa\n", + "4 5.0 3.6 1.4 0.2 setosa\n", + "5 5.4 3.9 1.7 0.4 setosa\n", + "6 4.6 3.4 1.4 0.3 setosa\n", + "7 5.0 3.4 1.5 0.2 setosa\n", + "8 4.4 2.9 1.4 0.2 setosa\n", + "9 4.9 3.1 1.5 0.1 setosa\n", + "10 5.4 3.7 1.5 0.2 setosa\n", + "11 4.8 3.4 1.6 0.2 setosa\n", + "12 4.8 3.0 1.4 0.1 setosa\n", + "13 4.3 3.0 1.1 0.1 setosa\n", + "14 5.8 4.0 1.2 0.2 setosa\n", + "15 5.7 4.4 1.5 0.4 setosa\n", + "16 5.4 3.9 1.3 0.4 setosa\n", + "17 5.1 3.5 1.4 0.3 setosa\n", + "18 5.7 3.8 1.7 0.3 setosa\n", + "19 5.1 3.8 1.5 0.3 setosa\n", + "20 5.4 3.4 1.7 0.2 setosa\n", + "21 5.1 3.7 1.5 0.4 setosa\n", + "22 4.6 3.6 1.0 0.2 setosa\n", + "23 5.1 3.3 1.7 0.5 setosa\n", + "24 4.8 3.4 1.9 0.2 setosa\n", + "25 5.0 3.0 1.6 0.2 setosa\n", + "26 5.0 3.4 1.6 0.4 setosa\n", + "27 5.2 3.5 1.5 0.2 setosa\n", + "28 5.2 3.4 1.4 0.2 setosa\n", + "29 4.7 3.2 1.6 0.2 setosa\n", + ".. ... ... ... ... ...\n", + "120 6.9 3.2 5.7 2.3 virginica\n", + "121 5.6 2.8 4.9 2.0 virginica\n", + "122 7.7 2.8 6.7 2.0 virginica\n", + "123 6.3 2.7 4.9 1.8 virginica\n", + "124 6.7 3.3 5.7 2.1 virginica\n", + "125 7.2 3.2 6.0 1.8 virginica\n", + "126 6.2 2.8 4.8 1.8 virginica\n", + "127 6.1 3.0 4.9 1.8 virginica\n", + "128 6.4 2.8 5.6 2.1 virginica\n", + "129 7.2 3.0 5.8 1.6 virginica\n", + "130 7.4 2.8 6.1 1.9 virginica\n", + "131 7.9 3.8 6.4 2.0 virginica\n", + "132 6.4 2.8 5.6 2.2 virginica\n", + "133 6.3 2.8 5.1 1.5 virginica\n", + "134 6.1 2.6 5.6 1.4 virginica\n", + "135 7.7 3.0 6.1 2.3 virginica\n", + "136 6.3 3.4 5.6 2.4 virginica\n", + "137 6.4 3.1 5.5 1.8 virginica\n", + "138 6.0 3.0 4.8 1.8 virginica\n", + "139 6.9 3.1 5.4 2.1 virginica\n", + "140 6.7 3.1 5.6 2.4 virginica\n", + "141 6.9 3.1 5.1 2.3 virginica\n", + "142 5.8 2.7 5.1 1.9 virginica\n", + "143 6.8 3.2 5.9 2.3 virginica\n", + "144 6.7 3.3 5.7 2.5 virginica\n", + "145 6.7 3.0 5.2 2.3 virginica\n", + "146 6.3 2.5 5.0 1.9 virginica\n", + "147 6.5 3.0 5.2 2.0 virginica\n", + "148 6.2 3.4 5.4 2.3 virginica\n", + "149 5.9 3.0 5.1 1.8 virginica\n", + "\n", + "[150 rows x 5 columns]" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import pandas as pd\n", + "\n", + "iris = pd.read_csv('https://raw.githubusercontent.com/mwaskom/seaborn-data/master/iris.csv')\n", + "iris" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "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", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.8" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/dashboards/voila-ipyvolume.ipynb b/voila-examples/voila-ipyvolume.ipynb similarity index 95% rename from dashboards/voila-ipyvolume.ipynb rename to voila-examples/voila-ipyvolume.ipynb index 831a8bf..933f63b 100644 --- a/dashboards/voila-ipyvolume.ipynb +++ b/voila-examples/voila-ipyvolume.ipynb @@ -8,7 +8,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "f4d715073b534597ac4e287746cc35b4", + "model_id": "42a1bdd953f94049af663c59ce4391e2", "version_major": 2, "version_minor": 0 }, diff --git a/dashboards/vuetify-bqplot.ipynb b/voila-examples/vuetify-bqplot.ipynb similarity index 52% rename from dashboards/vuetify-bqplot.ipynb rename to voila-examples/vuetify-bqplot.ipynb index 8e5bed7..cf0f350 100644 --- a/dashboards/vuetify-bqplot.ipynb +++ b/voila-examples/vuetify-bqplot.ipynb @@ -1,8 +1,18 @@ { "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Usage\n", + "```bash\n", + "voila -template vuetify-default ./dashboards/vuetify-bqplot.ipynb\n", + "```" + ] + }, { "cell_type": "code", - "execution_count": null, + "execution_count": 19, "metadata": {}, "outputs": [], "source": [ @@ -18,9 +28,24 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 20, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "2e9b001ca6c141d0811a477653e243b7", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Figure(axes=[Axis(orientation='vertical', scale=LinearScale()), Axis(scale=LinearScale(max=205.0, min=-37.0))]…" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "import ipywidgets as widgets\n", "import numpy as np\n", @@ -46,9 +71,24 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 21, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "bd196c325e02440f94f4d58e2e7d1f3d", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Slider(class_='px-4', thumb_label='always', v_model=30)" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "slider = v.Slider(thumb_label='always', class_=\"px-4\", v_model=30)\n", "widgets.link((slider, 'v_model'), (hist, 'bins'))\n", @@ -64,9 +104,24 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 22, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "ac5629dd47ee4df386ca6dfcbb12ceec", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Figure(axes=[Axis(scale=LinearScale()), Axis(orientation='vertical', scale=LinearScale())], fig_margin={'top':…" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "fig2 = plt.figure( title='Line Chart')\n", "np.random.seed(0)\n", @@ -87,7 +142,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 23, "metadata": {}, "outputs": [], "source": [ @@ -111,9 +166,38 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 24, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "fd37581404d94109ac5f1cc39896c315", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Figure(axes=[Axis(orientation='vertical', scale=LinearScale()), Axis(scale=LinearScale(max=205.0, min=-37.0))]…" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "8d72b63a649e4c51a9e259ac1801e064", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Slider(class_='px-4', thumb_label='always', v_model=5)" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "n2 = 200\n", "\n", @@ -148,11 +232,26 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 25, "metadata": { "scrolled": true }, - "outputs": [], + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "94cf6abf4bc34fcfa2681fb8cb609338", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Tabs(children=[Tab(children=['Tab1']), Tab(children=['Tab2']), TabItem(children=[Layout(align_center=True, chi…" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "v.Tabs(_metadata={'mount_id': 'content-main'}, children=[\n", " v.Tab(children=['Tab1']),\n", @@ -180,17 +279,73 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "```bash\n", - "voila --template vuetify-default --server_url=/ --base_url=/user/a.grosch@fz-juelich.de/labtest/proxy/8866/ --VoilaConfiguration.file_whitelist=\"['.*']\" dashboards/vuetify-bqplot.ipynb\n", - "```" + "## Optional\n", + "# Using Jupyter Server Proxy\n", + "Alternatively, if wanting to access over Jupyter Server Proxy on jupyter-jsc:\n", + "> No ssh tunnel needed, but ugly start command. \n", + "> Might need to change path to notebook!" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 7, "metadata": {}, "outputs": [], - "source": [] + "source": [ + "port = 8866" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "voila --template vuetify-default --server_url=/ --base_url=/user/a.grosch@fz-juelich.de/rhinodiagnost/proxy/8866/ --VoilaConfiguration.file_whitelist=\"['.*']\" ./jupyter-dashboarding/dashboards/vuetify-bqplot.ipynb\n", + "\n" + ] + } + ], + "source": [ + "import os\n", + "print(\"\"\"\n", + "voila --template vuetify-default \\\n", + "--server_url=/ --base_url={prefix}proxy/{port}/ --VoilaConfiguration.file_whitelist=\"['.*']\" \\\n", + "./jupyter-dashboarding/dashboards/vuetify-bqplot.ipynb\n", + "\"\"\".format(prefix=os.environ['JUPYTERHUB_SERVICE_PREFIX'], port=port))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Dashboard now runs on" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "https://jupyter-jsc.fz-juelich.de/user/a.grosch@fz-juelich.de/rhinodiagnost/proxy/8866\n", + "\n" + ] + } + ], + "source": [ + "print(\"\"\"\n", + "https://jupyter-jsc.fz-juelich.de{prefix}proxy/{port}\n", + "\"\"\".format(prefix=os.environ['JUPYTERHUB_SERVICE_PREFIX'], port=port))" + ] } ], "metadata": { diff --git a/voila-examples/vuetify-custom.ipynb b/voila-examples/vuetify-custom.ipynb new file mode 100644 index 0000000..4f39d31 --- /dev/null +++ b/voila-examples/vuetify-custom.ipynb @@ -0,0 +1,767 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "import ipyvuetify as v" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Documentation\n", + "\n", + "### ipyvuetify\n", + "https://github.com/mariobuikhuizen/ipyvuetify\n", + "\n", + "### Vuetify Documentation\n", + "https://vuetifyjs.com/en/components/buttons#buttons\n", + "\n", + "### Material UI Icons\n", + "https://www.materialui.co/icons" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Creating your app\n", + "\n", + "The *voila vuetify* template **does not** render output from the notebook, it only shows widgets with the mount_id metadata. \n", + "By default, the mount points \n", + "1. `content-title` for the title in the navigation drawer, \n", + "2. `content-nav` for the content in the navigation drawer,\n", + "3. `content-bar` for the title in the app bar and \n", + "4. `content-main` for the main app area \n", + "\n", + "are defined." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For example, we can set the app bar title with" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "appbar_title = v.ToolbarTitle(\n", + " _metadata={'mount_id':'toolbar-title'},\n", + " children=['My first vuetify app']\n", + ")\n", + "# No need to render it in the notebook, since the voila vuetify template \n", + "# only looks at the mount_id metadata and not at the notebook output." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## How do widgets in ipyvuetify work? \n", + "Think of them as HTML in Python code!\n", + "\n", + "```html\n", + "<v-list-item link>\n", + " <v-list-item-icon>\n", + " <v-icon>{{ item.icon }}</v-icon>\n", + " </v-list-item-icon>\n", + " \n", + " <v-list-item-content>\n", + " <v-list-item-title>{{ item.title }}</v-list-item-title>\n", + " </v-list-item-content>\n", + "</v-list-item>\n", + "```\n", + "\n", + "Compare the Python code below:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "list_items = [\n", + " v.ListItem(link=True, children=[\n", + " v.ListItemIcon(children=[\n", + " v.Icon(children=[\"account_box\"]),\n", + " ]),\n", + " v.ListItemContent(children=[\n", + " v.ListItemTitle(children=[\"Account\"])\n", + " ])\n", + " ]),\n", + " v.ListItem(link=True, children=[\n", + " v.ListItemIcon(children=[\n", + " v.Icon(children=[\"timeline\"]),\n", + " ]),\n", + " v.ListItemContent(children=[\n", + " v.ListItemTitle(children=[\"Analytics\"])\n", + " ])\n", + " ]),\n", + " v.ListItem(link=True, children=[\n", + " v.ListItemIcon(children=[\n", + " v.Icon(children=[\"build\"]),\n", + " ]),\n", + " v.ListItemContent(children=[\n", + " v.ListItemTitle(children=[\"Settings\"])\n", + " ])\n", + " ])\n", + "]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Create a list and mount it as the navigation drawer content\n" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "b848c76eee144e4b89a9a90a9bbbf8db", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "List(children=[ListItem(children=[ListItemIcon(children=[Icon(children=['account_box'])]), ListItemContent(chi…" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "v.List(\n", + " _metadata={'mount_id':'content-nav'},\n", + " column=True,\n", + " children=list_items\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "> *ipyvuetify* tries to stay as close to the *Vue.js* and *Vuetify template* syntax as possible. You should be able to find a corresponding *ipyvuetify* widget for all *vuetify* components. \n", + "> For an overview of all existing components, take a look at the [*vuetify* api explorer](https://vuetifyjs.com/en/components/api-explorer)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Setting `content-main`\n", + "`content-main` is the main area of the app and where you will want to render your interactive widgets. \n", + "Since we have three entries in the navigation bar, we will create three separate app pages. These are meant to show some of the possibilities of ipyvuetify." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "lorem_ipsum = \"Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. \\\n", + "At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.\" * 2" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Page 1" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "card1 = v.Card(children=[\n", + " v.CardTitle(children=[\n", + " v.ListItem(class_=\"grow\", children=[\n", + " v.ListItemAvatar(children=[\n", + " v.Icon(large=True, left=True,\n", + " children=[\"account_circle\"])\n", + " ]),\n", + " v.ListItemContent(children=[\"Evan You\"])\n", + " ])\n", + " ]),\n", + " v.ExpansionPanels(children=[\n", + " v.ExpansionPanel(children=[\n", + " v.ExpansionPanelHeader(children=[header]),\n", + " v.ExpansionPanelContent(children=[lorem_ipsum])\n", + " ])\n", + " for header in [\"Add\", \"Some\", \"Content\"]])\n", + "])\n", + "\n", + "\n", + "card2 = v.Card(height=\"100%\", children=[\n", + " v.CardTitle(children=[\"Responsiveness\"]),\n", + " v.CardText(children=[\"Vuetify automatically creates a responsive layout if you use v.Layout() and v.Flex()\",\n", + " v.List(children=[\n", + " v.ListItem(href=\"https://vuetifyjs.com/en/styles/display#display\",\n", + " children=[\n", + " v.ListItemContent(children=[\"Click here to open the documentation for the material design viewport breakpoints.\"])\n", + " ])\n", + " ])])\n", + "])\n", + "\n", + "\n", + "card3 = v.Card(height=\"100%\", children=[\n", + " v.CardTitle(children=[\"CSS Spacing helpers\"]),\n", + " v.CardText(children=[\n", + " v.List(children=[\n", + " v.ListItem(href=\"https://vuetifyjs.com/en/styles/spacing#how-it-works\",\n", + " children=[\n", + " v.ListItemContent(children=[\"\"\"Similarly, click here for the documentation on vuetify's css spacing helpers.\n", + "You can update your layout without creating new classes. \n", + "Spacing helpers are useful for modifying the padding and margin of an element.\"\"\" ])\n", + " ]),\n", + " v.ListItem(href=\"https://vuetifyjs.com/en/styles/spacing#playground\",\n", + " children=[\n", + " v.ListItemContent(children=[\"Click here to directly head to the playground to get a feel for what the different helper classes can do.\"])\n", + " ])\n", + " ])\n", + " ])\n", + "])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`v.Layout` is a basic flexbox. For the *vuetify* CSS flex helpers, see https://vuetifyjs.com/en/styles/flex#flex. \n", + "\n", + "For `v.Flex`, *xs12*, *md6* and *xl4* stand for the viewport breakpoints, *pa_4* for **p**adding in **a**ll directions. \n", + "For more information, follow the links in the cards." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "page1 = v.Layout(row=True, wrap=True, align_center=True, children=[\n", + " v.Flex(xs12=True, md6=True, xl4=True, pa_4=True, children=[ card ]) \n", + " for card in [card1, card2, card3]\n", + "])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In the standard notebook, the output will likely have wrong styling and layouts. \n", + "This is because the vuetify css style sheets are not loaded in the notebook. \n", + "Once started with the voila vuetify template, they should render correctly." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "0663d15979a14bde91f1f6d95566c217", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Layout(align_center=True, children=[Flex(children=[Card(children=[CardTitle(children=[ListItem(children=[ListI…" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "page1" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Page 2\n", + "Widgets from other notebook extensions can be rendered in the app as well." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [], + "source": [ + "from bqplot import pyplot as plt\n", + "from ipywidgets import link\n", + "import numpy as np\n", + "\n", + "# Create a random plot\n", + "n = 200\n", + "x = np.linspace(0.0, 10.0, n)\n", + "y = np.cumsum(np.random.randn(n)*10).astype(int)\n", + "\n", + "fig = plt.figure( title='Histogram')\n", + "np.random.seed(0)\n", + "hist = plt.hist(y, bins=25)\n", + "hist.scales['sample'].min = float(y.min())\n", + "hist.scales['sample'].max = float(y.max())\n", + "fig.layout.width = 'auto'\n", + "fig.layout.height = 'auto'\n", + "fig.layout.min_height = '300px' # so it shows nicely in the notebook" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Ipyvuetify widgets don't use `value` but `v_model`:" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [], + "source": [ + "slider = v.Slider(thumb_label='always', class_=\"px-4\", v_model=30)\n", + "link((slider, 'v_model'), (hist, 'bins'));" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "8e371a63b9c74503af6af5505c7e579a", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Layout(align_center=True, children=[Flex(children=[Figure(axes=[Axis(orientation='vertical', scale=LinearScale…" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "page2 = v.Layout(row=True, wrap=True, align_center=True, children=[\n", + " v.Flex(xs12=True, lg6=True, xl4=True, children=[ fig, slider ]) \n", + "])\n", + "\n", + "page2" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Page 3\n", + "Some more advanced usage of ipyvuetify widgets." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### The `v_model` trait" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "02cbedb9d3b041b8acc7f3e270ce80b4", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Switch(label='Switch', v_model=False)" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "5c38f32176894b049c2038dd55e3a6a9", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Checkbox(label='Checkbox', v_model=False)" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "switch = v.Switch(label=\"Switch\", v_model=False)\n", + "checkbox = v.Checkbox(label=\"Checkbox\", v_model=False)\n", + "link((switch, 'v_model'), (checkbox, 'v_model'))\n", + "\n", + "display(switch)\n", + "display(checkbox)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The `value` trait of ipywidgets corresponds to `v_model` in ipyvuetify widgets.\n", + "So for example, \n", + "```python \n", + "v_model=False\n", + "``` \n", + "sets a `v.Checkbox` to \"not selected\", \n", + "```python \n", + "v_model=True\n", + "``` \n", + "to \"selected\".\n", + "\n", + "Uncomment the code below to play around with the `v_model` trait:" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [], + "source": [ + "switch.v_model = True" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [], + "source": [ + "checkbox.v_model = False" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### (Scoped) slots\n", + "For (scoped) slots, the argument `v_slots` can be used, see the [scoped slots](https://github.com/mariobuikhuizen/ipyvuetify#scoped-slots) section of the ipyvuetify documentation." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The CSS attributes class and style need to be suffixed with an underscore: `class_`, `style_`" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [], + "source": [ + "accept_btn = v.Btn(color=\"primary\", children=[\"I accept\"])\n", + "dialog = v.Dialog(max_width=\"50%\",\n", + " v_model=False, \n", + " v_slots=[{\n", + " 'name': 'activator',\n", + " 'variable': 'x',\n", + " 'children': v.Btn(v_on='x.on',\n", + " color=\"red lighten-2\",\n", + " children=['Click to open dialog'])\n", + " }], \n", + " children=[v.Card(children=[\n", + " v.CardTitle(class_='headline gray lighten-2',\n", + " children=[\"Lorem Ipsum\"]),\n", + " v.CardText(style_='width: \"auto\";', \n", + " children=[lorem_ipsum]),\n", + " v.Divider(),\n", + " v.CardActions(children=[\n", + " v.Spacer(),\n", + " accept_btn\n", + " ])\n", + " ])])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### *ipyvuetify* widget events\n", + "Widget events for *ipyvuetify* widgets are handled using the `on_event` method." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [], + "source": [ + "# Dialog can be closed by clicking the accept button\n", + "def close_dialog(*args):\n", + " dialog.v_model = False\n", + "accept_btn.on_event('click', close_dialog)" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "34eeffe0a7104a9aa8bb3a0e84f06820", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Layout(align_center=True, children=[Flex(children=[Switch(label='Switch', v_model=False), Checkbox(label='Chec…" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "page3 = v.Layout(row=True, wrap=True, align_center=True, children=[\n", + " v.Flex(xs12=True, lg6=True, xl4=True, px_4=True, children=[\n", + " switch, checkbox, v.Layout(children=[dialog]),\n", + " ])\n", + "])\n", + "page3" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Switch between pages when the corresponding entry in the navigation drawer is clicked" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [], + "source": [ + "# By default, page1 is shown\n", + "content_main = v.Layout(\n", + " _metadata={'mount_id': 'content-main'},\n", + " children=[page1]\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [], + "source": [ + "def switch_to_first(*args):\n", + " appbar_title.children = ['Account']\n", + " content_main.children = [page1]\n", + " \n", + "def switch_to_second(*args):\n", + " appbar_title.children = ['Analytics']\n", + " content_main.children = [page2]\n", + " \n", + "def switch_to_third(*args):\n", + " appbar_title.children = ['Settings']\n", + " content_main.children = [page3]\n", + " \n", + "list_items[0].on_event('click', switch_to_first)\n", + "list_items[1].on_event('click', switch_to_second)\n", + "list_items[2].on_event('click', switch_to_third)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Advanced usage - Create a custom template" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Setting up your custom template\n", + "\n", + "1. Create a directory called `template` to hold your custom template. In that directory, create \n", + " a) the sub-directory `nbconvert_templates` and \n", + " b) a file `conf.json` which specifies from which template you want to inherit.\n", + "\n", + " > *Example*: To inherit from the base vuetify template, we create the file `conf.json` with\n", + " > ```json\n", + "{\"base_template\": \"vuetify-base\"}\n", + " ```\n", + "\n", + "\n", + "2. In the sub-directory `nbconvert_templates`, create a file called `app.html`. This is where you can customize your template. \n", + " To get started, you can copy the [base vuetify](https://raw.githubusercontent.com/voila-dashboards/voila-vuetify/master/share/jupyter/voila/templates/vuetify-default/nbconvert_templates/app.html) `app.html` and play around with it.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Customizing your template\n", + "\n", + "### Layout and Styling\n", + "In the template, you can change the layout and visual properties of the fixed app components, like the navigation drawer or the app bar. \n", + "\n", + "For example, we can customize the styling of the app bar. For possible properties, see the [app bar props](https://vuetifyjs.com/en/components/app-bars#api) from the vueitfy documentation. \n", + "Here, we color the app bar blue and apply the dark theme variant to the component to the the title more legible on the dark background.\n", + "```html\n", + "<v-app-bar color=\"primary\" app absolute dark>\n", + "```\n", + "\n", + "### Adding custom components\n", + "You can add a new app component that can be updated from the notebook by adding a new widget mount point.\n", + "```html\n", + "<jupyter-widget-mount-point mount-id=\"mycustomcomponent\">\n", + "...\n", + "</jupyter-widget-mount-point>\n", + "```\n", + "\n", + "Remember to add a mount id and set the mount_id metadata in the widget in the notebook so the output gets rendered.\n", + "```python\n", + "mycustomcomponent = v.Btn({'mount_id': 'mycustomcomponent'})\n", + "```\n", + " " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Starting voila with your custom template" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "```bash\n", + "voila --template full/path/to/your/template path/to/your/notebook\n", + "```\n", + "\n", + "For example, using Jupyter Server Proxy (see the [vuetify-bqplot.ipynb](./vuetify-bqplot.ipynb#Using-Jupyter-Server-Proxy) notebook):" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "voila --template ./mycustomtemplate --server_url=/ --base_url=/user/a.grosch@fz-juelich.de/rhinodiagnost/proxy/8866/ --VoilaConfiguration.file_whitelist=\"['.*']\" ./dashboards/vuetify-custom.ipynb\n", + "\n" + ] + } + ], + "source": [ + "import os\n", + "print(\"\"\"voila --template ./mycustomtemplate \\\n", + "--server_url=/ --base_url={prefix}proxy/{port}/ --VoilaConfiguration.file_whitelist=\"['.*']\" \\\n", + "./dashboards/vuetify-custom.ipynb\n", + "\"\"\".format(prefix=os.environ['JUPYTERHUB_SERVICE_PREFIX'], port=8866))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Dashboard now runs on\n", + "\n", + "https://jupyter-jsc.fz-juelich.de/user/[your_web_account]/[labserver_name]/proxy/[port]\n", + "\n", + "Example:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "https://jupyter-jsc.fz-juelich.de/user/a.grosch@fz-juelich.de/rhinodiagnost/proxy/8866\n", + "\n" + ] + } + ], + "source": [ + "print(\"\"\"\n", + "https://jupyter-jsc.fz-juelich.de{prefix}proxy/{port}\n", + "\"\"\".format(prefix=os.environ['JUPYTERHUB_SERVICE_PREFIX'], port=8866))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "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", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.8" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} -- GitLab