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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⪼s 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 SIv}6T2M#;DGwumCAn8AcGJCB6 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