From 4a6b06f95e90201e2b7c3c2de2cf881be8b4f218 Mon Sep 17 00:00:00 2001 From: Alice Grosch <grosch1@jrl01.jureca> Date: Wed, 18 Dec 2019 09:41:24 +0100 Subject: [PATCH] Add ipywidgets examples --- .gitignore | 1 + .../ipywidgets-checkpoint.ipynb | 1829 ----------------- .../voila-basic-checkpoint.ipynb | 6 - .../voila-dashboard-checkpoint.ipynb | 148 -- .../voila-gridstack-checkpoint.ipynb | 959 --------- .../voila-ipyvolume-checkpoint.ipynb | 57 - .../vuetify-bqplot-checkpoint.ipynb | 208 -- .../vuetify-custom-checkpoint.ipynb | 443 ---- .../voila-basic.ipynb | 0 .../voila-dashboard.ipynb | 76 +- .../voila-ipyvolume.ipynb | 4 +- .../vuetify-bqplot.ipynb | 11 +- .../vuetify-custom.ipynb | 20 +- docker/Dockerfile | 33 - .../extensions/vtk-remoterender/MANIFEST.in | 3 - .../vtk-remoterender/js/lib/embed.js | 9 - .../vtk-remoterender/js/lib/extension.js | 25 - .../vtk-remoterender/js/lib/index.js | 3 - .../vtk-remoterender/js/lib/labplugin.js | 16 - .../vtk-remoterender/js/lib/remoterender.js | 72 - .../vtk-remoterender/js/package.json | 42 - .../vtk-remoterender/js/webpack.config.js | 72 - docker/extensions/vtk-remoterender/setup.cfg | 2 - docker/extensions/vtk-remoterender/setup.py | 178 -- .../vtk-remoterender/vtk-remoterender.json | 5 - .../vtk_remoterender/__init__.py | 11 - .../vtk_remoterender/_version.py | 6 - .../vtk_remoterender/remoterender.py | 20 - images/WidgetArch.png | Bin 23058 -> 0 bytes images/WidgetModelView.png | Bin 0 -> 38681 bytes ipywidgets/introduction.ipynb | 519 +++++ .../ipywidgets.ipynb | 2 +- ipywidgets/widget_events.ipynb | 816 ++++++++ ipywidgets/widget_layout.ipynb | 897 ++++++++ ipywidgets/widget_styling.ipynb | 216 ++ .../.ipynb_checkpoints/conf-checkpoint.json | 1 - .../.ipynb_checkpoints/app-checkpoint.html | 107 - .../app-original-checkpoint.html | 105 - voila-gridstack.ipynb | 966 --------- 39 files changed, 2544 insertions(+), 5344 deletions(-) create mode 100644 .gitignore delete mode 100644 .ipynb_checkpoints/ipywidgets-checkpoint.ipynb delete mode 100644 .ipynb_checkpoints/voila-basic-checkpoint.ipynb delete mode 100644 .ipynb_checkpoints/voila-dashboard-checkpoint.ipynb delete mode 100644 .ipynb_checkpoints/voila-gridstack-checkpoint.ipynb delete mode 100644 .ipynb_checkpoints/voila-ipyvolume-checkpoint.ipynb delete mode 100644 .ipynb_checkpoints/vuetify-bqplot-checkpoint.ipynb delete mode 100644 .ipynb_checkpoints/vuetify-custom-checkpoint.ipynb rename voila-basic.ipynb => dashboards/voila-basic.ipynb (100%) rename voila-dashboard.ipynb => dashboards/voila-dashboard.ipynb (68%) rename voila-ipyvolume.ipynb => dashboards/voila-ipyvolume.ipynb (93%) rename vuetify-bqplot.ipynb => dashboards/vuetify-bqplot.ipynb (94%) rename vuetify-custom.ipynb => dashboards/vuetify-custom.ipynb (94%) delete mode 100644 docker/Dockerfile delete mode 100644 docker/extensions/vtk-remoterender/MANIFEST.in delete mode 100644 docker/extensions/vtk-remoterender/js/lib/embed.js delete mode 100644 docker/extensions/vtk-remoterender/js/lib/extension.js delete mode 100644 docker/extensions/vtk-remoterender/js/lib/index.js delete mode 100644 docker/extensions/vtk-remoterender/js/lib/labplugin.js delete mode 100644 docker/extensions/vtk-remoterender/js/lib/remoterender.js delete mode 100644 docker/extensions/vtk-remoterender/js/package.json delete mode 100644 docker/extensions/vtk-remoterender/js/webpack.config.js delete mode 100644 docker/extensions/vtk-remoterender/setup.cfg delete mode 100644 docker/extensions/vtk-remoterender/setup.py delete mode 100644 docker/extensions/vtk-remoterender/vtk-remoterender.json delete mode 100644 docker/extensions/vtk-remoterender/vtk_remoterender/__init__.py delete mode 100644 docker/extensions/vtk-remoterender/vtk_remoterender/_version.py delete mode 100644 docker/extensions/vtk-remoterender/vtk_remoterender/remoterender.py delete mode 100644 images/WidgetArch.png create mode 100644 images/WidgetModelView.png create mode 100644 ipywidgets/introduction.ipynb rename ipywidgets.ipynb => ipywidgets/ipywidgets.ipynb (99%) create mode 100644 ipywidgets/widget_events.ipynb create mode 100644 ipywidgets/widget_layout.ipynb create mode 100644 ipywidgets/widget_styling.ipynb delete mode 100644 mycustomtemplate/.ipynb_checkpoints/conf-checkpoint.json delete mode 100644 mycustomtemplate/nbconvert_templates/.ipynb_checkpoints/app-checkpoint.html delete mode 100644 mycustomtemplate/nbconvert_templates/.ipynb_checkpoints/app-original-checkpoint.html delete mode 100644 voila-gridstack.ipynb diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..763513e --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.ipynb_checkpoints diff --git a/.ipynb_checkpoints/ipywidgets-checkpoint.ipynb b/.ipynb_checkpoints/ipywidgets-checkpoint.ipynb deleted file mode 100644 index 6e56208..0000000 --- a/.ipynb_checkpoints/ipywidgets-checkpoint.ipynb +++ /dev/null @@ -1,1829 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Widget List" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "import ipywidgets as widgets" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, - "source": [ - "## Numeric widgets" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "There are many widgets distributed with ipywidgets that are designed to display numeric values. Widgets exist for displaying integers and floats, both bounded and unbounded. The integer widgets share a similar naming scheme to their floating point counterparts. By replacing `Float` with `Int` in the widget name, you can find the Integer equivalent." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### IntSlider" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "3df720a5c3bd419aba880cfdf86fbed5", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "IntSlider(value=7, continuous_update=False, description='Test:', max=10)" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "widgets.IntSlider(\n", - " value=7,\n", - " min=0,\n", - " max=10,\n", - " step=1,\n", - " description='Test:',\n", - " disabled=False,\n", - " continuous_update=False,\n", - " orientation='horizontal',\n", - " readout=True,\n", - " readout_format='d'\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, - "source": [ - "### FloatSlider" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "16c5be1c41874f54bfac68020a3468da", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "FloatSlider(value=7.5, continuous_update=False, description='Test:', max=10.0, readout_format='.1f')" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "widgets.FloatSlider(\n", - " value=7.5,\n", - " min=0,\n", - " max=10.0,\n", - " step=0.1,\n", - " description='Test:',\n", - " disabled=False,\n", - " continuous_update=False,\n", - " orientation='horizontal',\n", - " readout=True,\n", - " readout_format='.1f',\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Sliders can also be **displayed vertically**." - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "689c9dec23d045ef9506c58feab9e061", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "FloatSlider(value=7.5, continuous_update=False, description='Test:', max=10.0, orientation='vertical', readout…" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "widgets.FloatSlider(\n", - " value=7.5,\n", - " min=0,\n", - " max=10.0,\n", - " step=0.1,\n", - " description='Test:',\n", - " disabled=False,\n", - " continuous_update=False,\n", - " orientation='vertical',\n", - " readout=True,\n", - " readout_format='.1f',\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### FloatLogSlider" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The `FloatLogSlider` has a log scale, which makes it easy to have a slider that covers a wide range of positive magnitudes. The `min` and `max` refer to the minimum and maximum exponents of the `base`, and the `value` refers to the actual value of the slider." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "31d66d866a4d401bb50e12d7a96d6523", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "FloatLogSlider(value=10.0, description='Log Slider', max=10.0, min=-10.0, step=0.2)" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "widgets.FloatLogSlider(\n", - " value=10,\n", - " base=10,\n", - " min=-10, # max exponent of base\n", - " max=10, # min exponent of base\n", - " step=0.2, # exponent step\n", - " description='Log Slider'\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### IntRangeSlider" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "9deb24bbe83d41cab8d5c808a4b5139b", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "IntRangeSlider(value=(5, 7), continuous_update=False, description='Test:', max=10)" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "widgets.IntRangeSlider(\n", - " value=[5, 7],\n", - " min=0,\n", - " max=10,\n", - " step=1,\n", - " description='Test:',\n", - " disabled=False,\n", - " continuous_update=False,\n", - " orientation='horizontal',\n", - " readout=True,\n", - " readout_format='d',\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### FloatRangeSlider" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "ff93fafac312434db50af030d20f8a28", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "FloatRangeSlider(value=(5.0, 7.5), continuous_update=False, description='Test:', max=10.0, readout_format='.1f…" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "widgets.FloatRangeSlider(\n", - " value=[5, 7.5],\n", - " min=0,\n", - " max=10.0,\n", - " step=0.1,\n", - " description='Test:',\n", - " disabled=False,\n", - " continuous_update=False,\n", - " orientation='horizontal',\n", - " readout=True,\n", - " readout_format='.1f',\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### IntProgress" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "1668a774f5a945ae90d5aba2ed5c2518", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "IntProgress(value=7, description='Loading:', max=10)" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "widgets.IntProgress(\n", - " value=7,\n", - " min=0,\n", - " max=10,\n", - " step=1,\n", - " description='Loading:',\n", - " bar_style='', # 'success', 'info', 'warning', 'danger' or ''\n", - " orientation='horizontal'\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, - "source": [ - "### FloatProgress" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "320256dd9f7c4357b454b60acfdb5e5b", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "FloatProgress(value=7.5, bar_style='info', description='Loading:', max=10.0)" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "widgets.FloatProgress(\n", - " value=7.5,\n", - " min=0,\n", - " max=10.0,\n", - " step=0.1,\n", - " description='Loading:',\n", - " bar_style='info',\n", - " orientation='horizontal'\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The numerical text boxes that impose some limit on the data (range, integer-only) impose that restriction when the user presses enter.\n", - "\n", - "### BoundedIntText" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "5adcec7c45134d31bfb5e84db81cc4af", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "BoundedIntText(value=7, description='Text:', max=10)" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "widgets.BoundedIntText(\n", - " value=7,\n", - " min=0,\n", - " max=10,\n", - " step=1,\n", - " description='Text:',\n", - " disabled=False\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, - "source": [ - "### BoundedFloatText" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "3bcbd68e5c3c40aaa240cb81ce4ccb8d", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "BoundedFloatText(value=7.5, description='Text:', max=10.0, step=0.1)" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "widgets.BoundedFloatText(\n", - " value=7.5,\n", - " min=0,\n", - " max=10.0,\n", - " step=0.1,\n", - " description='Text:',\n", - " disabled=False\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### IntText" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "22e9a750c9bd4197894ac32e07b1ebca", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "IntText(value=7, description='Any:')" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "widgets.IntText(\n", - " value=7,\n", - " description='Any:',\n", - " disabled=False\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, - "source": [ - "### FloatText" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "ec97436b1e0c419e8612455abfe80d44", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "FloatText(value=7.5, description='Any:')" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "widgets.FloatText(\n", - " value=7.5,\n", - " description='Any:',\n", - " disabled=False\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, - "source": [ - "## Boolean widgets" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "There are three widgets that are designed to display a boolean value." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### ToggleButton" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "fa92f1c9377f4957b1b00b5b72f246d2", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "ToggleButton(value=False, description='Click me', icon='check', tooltip='Description')" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "widgets.ToggleButton(\n", - " value=False,\n", - " description='Click me',\n", - " disabled=False,\n", - " button_style='', # 'success', 'info', 'warning', 'danger' or ''\n", - " tooltip='Description',\n", - " icon='check'\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, - "source": [ - "### Checkbox" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "842e8756ac70414f9d2ddc4f622b56a4", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "Checkbox(value=False, description='Check me')" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "widgets.Checkbox(\n", - " value=False,\n", - " description='Check me',\n", - " disabled=False\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Valid\n", - "\n", - "The valid widget provides a read-only indicator." - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "62bb07a74cd34dbd8e16da12af987adc", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "Valid(value=False, description='Valid!')" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "widgets.Valid(\n", - " value=False,\n", - " description='Valid!',\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, - "source": [ - "## Selection widgets" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "There are several widgets that can be used to display single selection lists, and two that can be used to select multiple values. All inherit from the same base class. You can specify the **enumeration of selectable options by passing a list** (options are either (label, value) pairs, or simply values for which the labels are derived by calling `str`)." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, - "source": [ - "### Dropdown" - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "88996309be5a4f20b7706b8638a54e1b", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "Dropdown(description='Number:', index=1, options=('1', '2', '3'), value='2')" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "widgets.Dropdown(\n", - " options=['1', '2', '3'],\n", - " value='2',\n", - " description='Number:',\n", - " disabled=False,\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The following is also valid, displaying the words `'One', 'Two', 'Three'` as the dropdown choices but returning the values `1, 2, 3`." - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "7d0a4a166e9b4dceacd7d98c163dbf10", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "Dropdown(description='Number:', index=1, options=(('One', 1), ('Two', 2), ('Three', 3)), value=2)" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "widgets.Dropdown(\n", - " options=[('One', 1), ('Two', 2), ('Three', 3)],\n", - " value=2,\n", - " description='Number:',\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, - "source": [ - "### RadioButtons" - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "25b9155e87f7464c9547565a496678d8", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "RadioButtons(description='Pizza topping:', options=('pepperoni', 'pineapple', 'anchovies'), value='pepperoni')" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "widgets.RadioButtons(\n", - " options=['pepperoni', 'pineapple', 'anchovies'],\n", - "# value='pineapple',\n", - " description='Pizza topping:',\n", - " disabled=False\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, - "source": [ - "### Select" - ] - }, - { - "cell_type": "code", - "execution_count": 21, - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "93cabf7953474b5792398bbaa71e4f0b", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "Select(description='OS:', index=2, options=('Linux', 'Windows', 'OSX'), value='OSX')" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "widgets.Select(\n", - " options=['Linux', 'Windows', 'OSX'],\n", - " value='OSX',\n", - " # rows=10,\n", - " description='OS:',\n", - " disabled=False\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### SelectionSlider" - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "28c0ba73b9094bcb9136205aaee5165d", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "SelectionSlider(continuous_update=False, description='I like my eggs ...', index=1, options=('scrambled', 'sun…" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "widgets.SelectionSlider(\n", - " options=['scrambled', 'sunny side up', 'poached', 'over easy'],\n", - " value='sunny side up',\n", - " description='I like my eggs ...',\n", - " disabled=False,\n", - " continuous_update=False,\n", - " orientation='horizontal',\n", - " readout=True\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### SelectionRangeSlider\n", - "\n", - "The value, index, and label keys are 2-tuples of the min and max values selected. The options must be nonempty." - ] - }, - { - "cell_type": "code", - "execution_count": 23, - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "dcebe6bc80ee49849d65247df2868581", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "SelectionRangeSlider(description='Months (2015)', index=(0, 11), options=(('Jan', datetime.date(2015, 1, 1)), …" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "import datetime\n", - "dates = [datetime.date(2015,i,1) for i in range(1,13)]\n", - "options = [(i.strftime('%b'), i) for i in dates]\n", - "widgets.SelectionRangeSlider(\n", - " options=options,\n", - " index=(0,11),\n", - " description='Months (2015)',\n", - " disabled=False\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, - "source": [ - "### ToggleButtons" - ] - }, - { - "cell_type": "code", - "execution_count": 24, - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "2b3239a049224341bbf7640d27dbed95", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "ToggleButtons(description='Speed:', options=('Slow', 'Regular', 'Fast'), tooltips=('Description of slow', 'Des…" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "widgets.ToggleButtons(\n", - " options=['Slow', 'Regular', 'Fast'],\n", - " description='Speed:',\n", - " disabled=False,\n", - " button_style='', # 'success', 'info', 'warning', 'danger' or ''\n", - " tooltips=['Description of slow', 'Description of regular', 'Description of fast'],\n", - "# icons=['check'] * 3\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### SelectMultiple\n", - "Multiple values can be selected with <kbd>shift</kbd> and/or <kbd>ctrl</kbd> (or <kbd>command</kbd>) pressed and mouse clicks or arrow keys." - ] - }, - { - "cell_type": "code", - "execution_count": 25, - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "aaddb5c053a14b2ea6e5be66796c30f4", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "SelectMultiple(description='Fruits', index=(1,), options=('Apples', 'Oranges', 'Pears'), value=('Oranges',))" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "widgets.SelectMultiple(\n", - " options=['Apples', 'Oranges', 'Pears'],\n", - " value=['Oranges'],\n", - " #rows=10,\n", - " description='Fruits',\n", - " disabled=False\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, - "source": [ - "## String widgets" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "There are several widgets that can be used to display a string value. The `Text`, `Textarea`, and `Combobox` widgets accept input. The `HTML` and `HTMLMath` widgets display a string as HTML (`HTMLMath` also renders math). The `Label` widget can be used to construct a custom control label." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, - "source": [ - "### Text" - ] - }, - { - "cell_type": "code", - "execution_count": 26, - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "e9d285cfa4e441078b8adf2ffbfb1fef", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "Text(value='Hello World', description='String:', placeholder='Type something')" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "widgets.Text(\n", - " value='Hello World',\n", - " placeholder='Type something',\n", - " description='String:',\n", - " disabled=False \n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Textarea" - ] - }, - { - "cell_type": "code", - "execution_count": 27, - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "d222ce5f3fb948ff9de5671837537304", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "Textarea(value='Hello World', description='String:', placeholder='Type something')" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "widgets.Textarea(\n", - " value='Hello World',\n", - " placeholder='Type something',\n", - " description='String:',\n", - " disabled=False\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Combobox" - ] - }, - { - "cell_type": "code", - "execution_count": 28, - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "c801434050ae4cb193958110596ea6a2", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "Combobox(value='', description='Combobox:', ensure_option=True, options=('Paul', 'John', 'George', 'Ringo'), p…" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "widgets.Combobox(\n", - " # value='John',\n", - " placeholder='Choose Someone',\n", - " options=['Paul', 'John', 'George', 'Ringo'],\n", - " description='Combobox:',\n", - " ensure_option=True,\n", - " disabled=False\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, - "source": [ - "### Label\n", - "\n", - "The `Label` widget is useful if you need to build a custom description next to a control using similar styling to the built-in control descriptions." - ] - }, - { - "cell_type": "code", - "execution_count": 29, - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "59d0024d9a0147b9aa96883c88737cd0", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "HBox(children=(Label(value='The $m$ in $E=mc^2$:'), FloatSlider(value=0.0)))" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "widgets.HBox([widgets.Label(value=\"The $m$ in $E=mc^2$:\"), widgets.FloatSlider()])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### HTML" - ] - }, - { - "cell_type": "code", - "execution_count": 30, - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "0139f274c3664cb99e1db4c9b3a984c9", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "HTML(value='Hello <b>World</b>', description='Some HTML', placeholder='Some HTML')" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "widgets.HTML(\n", - " value=\"Hello <b>World</b>\",\n", - " placeholder='Some HTML',\n", - " description='Some HTML',\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### HTML Math" - ] - }, - { - "cell_type": "code", - "execution_count": 31, - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "2a1e97bd720241808a075599b08e5457", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "HTMLMath(value='Some math and <i>HTML</i>: \\\\(x^2\\\\) and $$\\\\frac{x+1}{x-1}$$', description='Some HTML', place…" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "widgets.HTMLMath(\n", - " value=r\"Some math and <i>HTML</i>: \\(x^2\\) and $$\\frac{x+1}{x-1}$$\",\n", - " placeholder='Some HTML',\n", - " description='Some HTML',\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Image" - ] - }, - { - "cell_type": "code", - "execution_count": 33, - "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" - } - ], - "source": [ - "file = open(\"images/WidgetArch.png\", \"rb\")\n", - "image = file.read()\n", - "widgets.Image(\n", - " value=image,\n", - " format='png',\n", - " width=300,\n", - " height=400,\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, - "source": [ - "## Button" - ] - }, - { - "cell_type": "code", - "execution_count": 34, - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "1394ba2daeb340cb8c080bdb0d7db7bf", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "Button(description='Click me', icon='check', style=ButtonStyle(), tooltip='Click me')" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "widgets.Button(\n", - " description='Click me',\n", - " disabled=False,\n", - " button_style='', # 'success', 'info', 'warning', 'danger' or ''\n", - " tooltip='Click me',\n", - " icon='check'\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Output\n", - "\n", - "The `Output` widget can capture and display stdout, stderr and [rich output generated by IPython](http://ipython.readthedocs.io/en/stable/api/generated/IPython.display.html#module-IPython.display). For detailed documentation, see the [output widget examples](https://ipywidgets.readthedocs.io/en/latest/examples/Output Widget.html)." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Play (Animation) widget" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The `Play` widget is useful to perform animations by iterating on a sequence of integers with a certain speed. The value of the slider below is linked to the player." - ] - }, - { - "cell_type": "code", - "execution_count": 35, - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "5835b4929a6e4ffe800b8740085c6f7e", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "HBox(children=(Play(value=50, description='Press play'), IntSlider(value=0)))" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "play = widgets.Play(\n", - "# interval=10,\n", - " value=50,\n", - " min=0,\n", - " max=100,\n", - " step=1,\n", - " description=\"Press play\",\n", - " disabled=False\n", - ")\n", - "slider = widgets.IntSlider()\n", - "widgets.jslink((play, 'value'), (slider, 'value'))\n", - "widgets.HBox([play, slider])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Date picker\n", - "\n", - "The date picker widget works in Chrome, Firefox and IE Edge, but does not currently work in Safari because it does not support the HTML date input field." - ] - }, - { - "cell_type": "code", - "execution_count": 36, - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "c791830a12da41aaa8dea9f2f293b906", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "DatePicker(value=None, description='Pick a Date')" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "widgets.DatePicker(\n", - " description='Pick a Date',\n", - " disabled=False\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Color picker" - ] - }, - { - "cell_type": "code", - "execution_count": 37, - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "49091c2ce3e04a39b3eab4eb003de623", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "ColorPicker(value='blue', description='Pick a color')" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "widgets.ColorPicker(\n", - " concise=False,\n", - " description='Pick a color',\n", - " value='blue',\n", - " disabled=False\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## File Upload\n", - "\n", - "The `FileUpload` allows to upload any type of file(s) as bytes." - ] - }, - { - "cell_type": "code", - "execution_count": 38, - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "2596acea5cad46f0a69564e624d69216", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "FileUpload(value={}, description='Upload')" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "widgets.FileUpload(\n", - " accept='', # Accepted file extension e.g. '.txt', '.pdf', 'image/*', 'image/*,.pdf'\n", - " multiple=False # True to accept multiple files upload else False\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Controller\n", - "\n", - "The `Controller` allows a game controller to be used as an input device." - ] - }, - { - "cell_type": "code", - "execution_count": 39, - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "69ab48cc71a843b892a1c01c6ec6f49d", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "Controller()" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "widgets.Controller(\n", - " index=0,\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Container/Layout widgets\n", - "\n", - "These widgets are used to hold other widgets, called children. Each has a `children` property that may be set either when the widget is created or later." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Box" - ] - }, - { - "cell_type": "code", - "execution_count": 40, - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "cc47f0e92e3449c1bf6b451d32a11b8f", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "Box(children=(Label(value='0'), Label(value='1'), Label(value='2'), Label(value='3')))" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "items = [widgets.Label(str(i)) for i in range(4)]\n", - "widgets.Box(items)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### HBox" - ] - }, - { - "cell_type": "code", - "execution_count": 41, - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "eaba86ae7225416991f8b07328866cc8", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "HBox(children=(Label(value='0'), Label(value='1'), Label(value='2'), Label(value='3')))" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "items = [widgets.Label(str(i)) for i in range(4)]\n", - "widgets.HBox(items)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### VBox" - ] - }, - { - "cell_type": "code", - "execution_count": 42, - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "dc19cdb22b55472b86c991c37e4654d2", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "HBox(children=(VBox(children=(Label(value='0'), Label(value='1'))), VBox(children=(Label(value='2'), Label(val…" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "items = [widgets.Label(str(i)) for i in range(4)]\n", - "left_box = widgets.VBox([items[0], items[1]])\n", - "right_box = widgets.VBox([items[2], items[3]])\n", - "widgets.HBox([left_box, right_box])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### GridBox\n", - "\n", - "This box uses the HTML Grid specification to lay out its children in two dimensional grid. The example below lays out the 8 items inside in 3 columns and as many rows as needed to accommodate the items." - ] - }, - { - "cell_type": "code", - "execution_count": 43, - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "d740ffb5f64d4e43a4f0168fc231afce", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "GridBox(children=(Label(value='0'), Label(value='1'), Label(value='2'), Label(value='3'), Label(value='4'), La…" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "items = [widgets.Label(str(i)) for i in range(8)]\n", - "widgets.GridBox(items, layout=widgets.Layout(grid_template_columns=\"repeat(3, 100px)\"))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Accordion" - ] - }, - { - "cell_type": "code", - "execution_count": 44, - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "7c1c290d99644553b3c7f55afe5bfbe0", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "Accordion(children=(IntSlider(value=0), Text(value='')), _titles={'0': 'Slider', '1': 'Text'})" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "accordion = widgets.Accordion(children=[widgets.IntSlider(), widgets.Text()])\n", - "accordion.set_title(0, 'Slider')\n", - "accordion.set_title(1, 'Text')\n", - "accordion" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Tabs\n", - "\n", - "In this example the children are set after the tab is created. Titles for the tabs are set in the same way they are for `Accordion`." - ] - }, - { - "cell_type": "code", - "execution_count": 45, - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "36a6e59d9b964cf6ae547828640647ec", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "Tab(children=(Text(value='', description='P0'), Text(value='', description='P1'), Text(value='', description='…" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "tab_contents = ['P0', 'P1', 'P2', 'P3', 'P4']\n", - "children = [widgets.Text(description=name) for name in tab_contents]\n", - "tab = widgets.Tab()\n", - "tab.children = children\n", - "for i in range(len(children)):\n", - " tab.set_title(i, str(i))\n", - "tab" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Accordion and Tab use `selected_index`, not value\n", - "\n", - "Unlike the rest of the widgets discussed earlier, the container widgets `Accordion` and `Tab` update their `selected_index` attribute when the user changes which accordion or tab is selected. That means that you can both see what the user is doing *and* programmatically set what the user sees by setting the value of `selected_index`.\n", - "\n", - "Setting `selected_index = None` closes all of the accordions or deselects all tabs." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In the cells below try displaying or setting the `selected_index` of the `tab` and/or `accordion`." - ] - }, - { - "cell_type": "code", - "execution_count": 46, - "metadata": {}, - "outputs": [], - "source": [ - "tab.selected_index = 3" - ] - }, - { - "cell_type": "code", - "execution_count": 47, - "metadata": {}, - "outputs": [], - "source": [ - "accordion.selected_index = None" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Nesting tabs and accordions\n", - "\n", - "Tabs and accordions can be nested as deeply as you want. If you have a few minutes, try nesting a few accordions or putting an accordion inside a tab or a tab inside an accordion. \n", - "\n", - "The example below makes a couple of tabs with an accordion children in one of them" - ] - }, - { - "cell_type": "code", - "execution_count": 48, - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "411b7188cfc24440a48bee1c0fe3e787", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "Tab(children=(Accordion(children=(IntSlider(value=0), Text(value='')), selected_index=None, _titles={'0': 'Sli…" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "tab_nest = widgets.Tab()\n", - "tab_nest.children = [accordion, accordion]\n", - "tab_nest.set_title(0, 'An accordion')\n", - "tab_nest.set_title(1, 'Copy of the accordion')\n", - "tab_nest" - ] - } - ], - "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/.ipynb_checkpoints/voila-basic-checkpoint.ipynb b/.ipynb_checkpoints/voila-basic-checkpoint.ipynb deleted file mode 100644 index 2fd6442..0000000 --- a/.ipynb_checkpoints/voila-basic-checkpoint.ipynb +++ /dev/null @@ -1,6 +0,0 @@ -{ - "cells": [], - "metadata": {}, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/.ipynb_checkpoints/voila-dashboard-checkpoint.ipynb b/.ipynb_checkpoints/voila-dashboard-checkpoint.ipynb deleted file mode 100644 index 8bf49bd..0000000 --- a/.ipynb_checkpoints/voila-dashboard-checkpoint.ipynb +++ /dev/null @@ -1,148 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "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`" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import numpy as np\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" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import ipywidgets as widgets" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "label_selected = widgets.Label(value=\"Selected: 0\")\n", - "label_selected" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "grid_columns": 8, - "grid_rows": 4 - }, - "outputs": [], - "source": [ - "import numpy as np\n", - "from bqplot import pyplot as plt\n", - "import bqplot\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", - "display(fig)\n", - "fig.layout.width = 'auto'\n", - "fig.layout.height = 'auto'\n", - "fig.layout.min_height = '300px' # so it shows nicely in the notebook\n", - "fig.layout.flex = '1'" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "grid_columns": 12, - "grid_rows": 6 - }, - "outputs": [], - "source": [ - "import numpy as np\n", - "from bqplot import pyplot as plt\n", - "import bqplot\n", - "\n", - "fig = plt.figure( title='Line Chart')\n", - "np.random.seed(0)\n", - "n = 200\n", - "p = plt.plot(x, y)\n", - "fig" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fig.layout.width = 'auto'\n", - "fig.layout.height = 'auto'\n", - "fig.layout.min_height = '300px' # so it shows nicely in the notebook\n", - "fig.layout.flex = '1'" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "brushintsel = bqplot.interacts.BrushIntervalSelector(scale=p.scales['x'])" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "def update_range(*args):\n", - " label_selected.value = \"Selected range {}\".format(brushintsel.selected)\n", - " mask = (x > brushintsel.selected[0]) & (x < brushintsel.selected[1])\n", - " hist.sample = y[mask]\n", - " \n", - "brushintsel.observe(update_range, 'selected')\n", - "fig.interaction = brushintsel" - ] - } - ], - "metadata": { - "celltoolbar": "Edit 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.4" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/.ipynb_checkpoints/voila-gridstack-checkpoint.ipynb b/.ipynb_checkpoints/voila-gridstack-checkpoint.ipynb deleted file mode 100644 index c30c1ad..0000000 --- a/.ipynb_checkpoints/voila-gridstack-checkpoint.ipynb +++ /dev/null @@ -1,959 +0,0 @@ -{ - "cells": [ - { - "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": "a9c6b03944ad46baafdf8368105adbb5", - "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": "7d57de7083d149ac9f50b469cb69ba62", - "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": "4b92da7ecfe4405bbabf1274e2a6ec27", - "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": "5cfaa1c93f1e43beb83377cd73ce0e15", - "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": "6495dfa3b7b14c29b43472853c6e717a", - "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." - ] - }, - { - "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": null, - "metadata": { - "collapsed": true, - "extensions": { - "jupyter_dashboards": { - "version": 1, - "views": { - "grid_default": { - "hidden": true - }, - "report_default": {} - } - } - }, - "jupyter": { - "outputs_hidden": true - } - }, - "outputs": [], - "source": [] - } - ], - "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.5" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/.ipynb_checkpoints/voila-ipyvolume-checkpoint.ipynb b/.ipynb_checkpoints/voila-ipyvolume-checkpoint.ipynb deleted file mode 100644 index f2fc94b..0000000 --- a/.ipynb_checkpoints/voila-ipyvolume-checkpoint.ipynb +++ /dev/null @@ -1,57 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "22519d230eaf4b90a9aa4a8656778c59", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "VBox(children=(VBox(children=(HBox(children=(Label(value='levels:'), FloatSlider(value=0.1, max=1.0, step=0.00…" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "import ipyvolume as ipv\n", - "ipv.examples.example_ylm();" - ] - }, - { - "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/.ipynb_checkpoints/vuetify-bqplot-checkpoint.ipynb b/.ipynb_checkpoints/vuetify-bqplot-checkpoint.ipynb deleted file mode 100644 index ff392c9..0000000 --- a/.ipynb_checkpoints/vuetify-bqplot-checkpoint.ipynb +++ /dev/null @@ -1,208 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import ipyvuetify as v" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# First histogram plot" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import ipywidgets as widgets\n", - "import numpy as np\n", - "from bqplot import pyplot as plt\n", - "import bqplot\n", - "\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\n", - "fig" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "slider = v.Slider(thumb_label='always', class_=\"px-4\", v_model=30)\n", - "widgets.link((slider, 'v_model'), (hist, 'bins'))\n", - "slider" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Line chart" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fig2 = plt.figure( title='Line Chart')\n", - "np.random.seed(0)\n", - "p = plt.plot(x, y)\n", - "\n", - "fig2.layout.width = 'auto'\n", - "fig2.layout.height = 'auto'\n", - "fig2.layout.min_height = '300px' # so it shows nicely in the notebook\n", - "fig2" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "brushintsel = bqplot.interacts.BrushIntervalSelector(scale=p.scales['x'])\n", - "\n", - "def update_range(*args):\n", - " if brushintsel.selected is not None and brushintsel.selected.shape == (2,):\n", - " mask = (x > brushintsel.selected[0]) & (x < brushintsel.selected[1])\n", - " hist.sample = y[mask]\n", - " \n", - "brushintsel.observe(update_range, 'selected')\n", - "fig2.interaction = brushintsel" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Second histogram plot" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "n2 = 200\n", - "\n", - "x2 = np.linspace(0.0, 10.0, n)\n", - "y2 = np.cumsum(np.random.randn(n)*10).astype(int)\n", - "\n", - "figHist2 = plt.figure( title='Histogram 2')\n", - "np.random.seed(0)\n", - "hist2 = plt.hist(y2, bins=25)\n", - "hist2.scales['sample'].min = float(y2.min())\n", - "hist2.scales['sample'].max = float(y2.max())\n", - "figHist2.layout.width = 'auto'\n", - "figHist2.layout.height = 'auto'\n", - "figHist2.layout.min_height = '300px' # so it shows nicely in the notebook\n", - "\n", - "sliderHist2 = v.Slider(_metadata={'mount_id': 'histogram_bins2'}, thumb_label='always', class_='px-4', v_model=5)\n", - "from traitlets import link\n", - "link((sliderHist2, 'v_model'), (hist2, 'bins'))\n", - "\n", - "\n", - "display(figHist2)\n", - "display(sliderHist2)\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Set up voila vuetify layout\n", - "The voila vuetify template does not render output from the notebook, it only shows widget with the mount_id metadata." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "scrolled": true - }, - "outputs": [], - "source": [ - "v.Tabs(_metadata={'mount_id': 'content-main'}, children=[\n", - " v.Tab(children=['Tab1']),\n", - " v.Tab(children=['Tab2']),\n", - " v.TabItem(children=[\n", - " v.Layout(row=True, wrap=True, align_center=True, children=[\n", - " v.Flex(xs12=True, lg6=True, xl4=True, children=[\n", - " fig, slider\n", - " ]),\n", - " v.Flex(xs12=True, lg6=True, xl4=True, children=[\n", - " figHist2, sliderHist2\n", - " ]),\n", - " v.Flex(xs12=True, xl4=True, children=[\n", - " fig2\n", - " ]),\n", - " ])\n", - " ]),\n", - " v.TabItem(children=[\n", - " v.Container(children=['Lorum ipsum'])\n", - " ])\n", - "])" - ] - }, - { - "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.7" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/.ipynb_checkpoints/vuetify-custom-checkpoint.ipynb b/.ipynb_checkpoints/vuetify-custom-checkpoint.ipynb deleted file mode 100644 index 9b28256..0000000 --- a/.ipynb_checkpoints/vuetify-custom-checkpoint.ipynb +++ /dev/null @@ -1,443 +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 /Users/grosch/Devel/dashboards/mycustomtemplate/ vuetify-custom.ipynb\n", - "```" - ] - } - ], - "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/voila-basic.ipynb b/dashboards/voila-basic.ipynb similarity index 100% rename from voila-basic.ipynb rename to dashboards/voila-basic.ipynb diff --git a/voila-dashboard.ipynb b/dashboards/voila-dashboard.ipynb similarity index 68% rename from voila-dashboard.ipynb rename to dashboards/voila-dashboard.ipynb index 7b1a235..1c6a707 100644 --- a/voila-dashboard.ipynb +++ b/dashboards/voila-dashboard.ipynb @@ -11,7 +11,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ @@ -24,7 +24,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "metadata": {}, "outputs": [], "source": [ @@ -33,9 +33,24 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "f0a7b332b6e0490b9b3f79a39158b865", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Label(value='Selected: 0')" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "label_selected = widgets.Label(value=\"Selected: 0\")\n", "label_selected" @@ -43,12 +58,27 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "metadata": { "grid_columns": 8, "grid_rows": 4 }, - "outputs": [], + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "fa496624172f4d96a15de6a714c771a1", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Figure(axes=[Axis(orientation='vertical', scale=LinearScale()), Axis(scale=LinearScale(max=29.0, min=-181.0))]…" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "import numpy as np\n", "from bqplot import pyplot as plt\n", @@ -68,12 +98,27 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "metadata": { "grid_columns": 12, "grid_rows": 6 }, - "outputs": [], + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "db6ff5fd58ec4dcb8f015263532c0515", + "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": [ "import numpy as np\n", "from bqplot import pyplot as plt\n", @@ -88,7 +133,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, "metadata": {}, "outputs": [], "source": [ @@ -100,7 +145,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 7, "metadata": {}, "outputs": [], "source": [ @@ -109,7 +154,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 8, "metadata": {}, "outputs": [], "source": [ @@ -121,6 +166,13 @@ "brushintsel.observe(update_range, 'selected')\n", "fig.interaction = brushintsel" ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { @@ -140,7 +192,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.5" + "version": "3.6.8" } }, "nbformat": 4, diff --git a/voila-ipyvolume.ipynb b/dashboards/voila-ipyvolume.ipynb similarity index 93% rename from voila-ipyvolume.ipynb rename to dashboards/voila-ipyvolume.ipynb index bc425ec..831a8bf 100644 --- a/voila-ipyvolume.ipynb +++ b/dashboards/voila-ipyvolume.ipynb @@ -8,7 +8,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "52ec436d0cdc4a8793f1bcb2d8d3a5ec", + "model_id": "f4d715073b534597ac4e287746cc35b4", "version_major": 2, "version_minor": 0 }, @@ -49,7 +49,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.5" + "version": "3.6.8" } }, "nbformat": 4, diff --git a/vuetify-bqplot.ipynb b/dashboards/vuetify-bqplot.ipynb similarity index 94% rename from vuetify-bqplot.ipynb rename to dashboards/vuetify-bqplot.ipynb index b9fd421..8e5bed7 100644 --- a/vuetify-bqplot.ipynb +++ b/dashboards/vuetify-bqplot.ipynb @@ -176,6 +176,15 @@ "])" ] }, + { + "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", + "```" + ] + }, { "cell_type": "code", "execution_count": null, @@ -200,7 +209,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.5" + "version": "3.6.8" } }, "nbformat": 4, diff --git a/vuetify-custom.ipynb b/dashboards/vuetify-custom.ipynb similarity index 94% rename from vuetify-custom.ipynb rename to dashboards/vuetify-custom.ipynb index 9b28256..e68f9b3 100644 --- a/vuetify-custom.ipynb +++ b/dashboards/vuetify-custom.ipynb @@ -414,9 +414,25 @@ "For example:\n", "\n", "```bash\n", - "voila --template /Users/grosch/Devel/dashboards/mycustomtemplate/ vuetify-custom.ipynb\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": { @@ -435,7 +451,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.5" + "version": "3.6.8" } }, "nbformat": 4, diff --git a/docker/Dockerfile b/docker/Dockerfile deleted file mode 100644 index 80604c8..0000000 --- a/docker/Dockerfile +++ /dev/null @@ -1,33 +0,0 @@ -FROM jupyter/minimal-notebook -USER root - -RUN mkdir /software -WORKDIR /software - -RUN wget 'https://www.paraview.org/files/v5.7/ParaView-5.7.0-osmesa-MPI-Linux-Python3.7-64bit.tar.gz' \ - && tar xf ParaView-5.7.0-osmesa-MPI-Linux-Python3.7-64bit.tar.gz \ - && rm ParaView-5.7.0-osmesa-MPI-Linux-Python3.7-64bit.tar.gz - -ENV PATH="/software/ParaView-5.7.0-osmesa-MPI-Linux-Python3.7-64bit/bin:${PATH}" -RUN apt-get update && apt-get -y install openssh-server net-tools \ - && rm -rf /var/lib/apt/lists/* - -USER $NB_UID -WORKDIR /home/$NB_USER/work - -COPY --chown=1000:100 extensions/vtk-remoterender /home/$NB_USER/.jupyter/vtk-remoterender - -RUN pip install service_identity \ - voila \ - ipyvuetify voila-vuetify \ - /home/$NB_USER/.jupyter/vtk-remoterender \ - && jupyter labextension install --no-build \ - @jupyter-widgets/jupyterlab-manager \ - @jupyter-voila/jupyterlab-preview \ - jupyter-vuetify \ - /home/$NB_USER/.jupyter/vtk-remoterender/js \ - && jupyter lab build && jupyter lab clean \ - && npm cache clean --force \ - && rm -rf $CONDA_DIR/share/jupyter/lab/staging \ - && rm -rf /home/$NB_USER/.cache/yarn \ - && rm -rf /home/$NB_USER/.node-gyp diff --git a/docker/extensions/vtk-remoterender/MANIFEST.in b/docker/extensions/vtk-remoterender/MANIFEST.in deleted file mode 100644 index dc4cca5..0000000 --- a/docker/extensions/vtk-remoterender/MANIFEST.in +++ /dev/null @@ -1,3 +0,0 @@ -recursive-include vtk_remoterender/static *.* -include vtk-remoterender.json -include js/package.json \ No newline at end of file diff --git a/docker/extensions/vtk-remoterender/js/lib/embed.js b/docker/extensions/vtk-remoterender/js/lib/embed.js deleted file mode 100644 index ea545d4..0000000 --- a/docker/extensions/vtk-remoterender/js/lib/embed.js +++ /dev/null @@ -1,9 +0,0 @@ -// Entry point for the unpkg bundle containing custom model definitions. -// -// It differs from the notebook bundle in that it does not need to define a -// dynamic baseURL for the static assets and may load some css that would -// already be loaded by the notebook otherwise. - -// Export widget models and views, and the npm package version number. -module.exports = require('./remoterender.js'); -module.exports['version'] = require('../package.json').version; diff --git a/docker/extensions/vtk-remoterender/js/lib/extension.js b/docker/extensions/vtk-remoterender/js/lib/extension.js deleted file mode 100644 index a810688..0000000 --- a/docker/extensions/vtk-remoterender/js/lib/extension.js +++ /dev/null @@ -1,25 +0,0 @@ -// This file contains the javascript that is run when the notebook is loaded. -// It contains some requirejs configuration and the `load_ipython_extension` -// which is required for any notebook extension. -// -// Some static assets may be required by the custom widget javascript. The base -// url for the notebook is not known at build time and is therefore computed -// dynamically. -__webpack_public_path__ = document.querySelector('body').getAttribute('data-base-url') + 'nbextensions/vtk-remoterender'; - - -// Configure requirejs -if (window.require) { - window.require.config({ - map: { - "*" : { - "vtk-remoterender": "nbextensions/vtk-remoterender/index", - } - } - }); -} - -// Export the required load_ipython_extension -module.exports = { - load_ipython_extension: function() {} -}; diff --git a/docker/extensions/vtk-remoterender/js/lib/index.js b/docker/extensions/vtk-remoterender/js/lib/index.js deleted file mode 100644 index e814b7c..0000000 --- a/docker/extensions/vtk-remoterender/js/lib/index.js +++ /dev/null @@ -1,3 +0,0 @@ -// Export widget models and views, and the npm package version number. -module.exports = require('./remoterender.js'); -module.exports['version'] = require('../package.json').version; diff --git a/docker/extensions/vtk-remoterender/js/lib/labplugin.js b/docker/extensions/vtk-remoterender/js/lib/labplugin.js deleted file mode 100644 index 72b6230..0000000 --- a/docker/extensions/vtk-remoterender/js/lib/labplugin.js +++ /dev/null @@ -1,16 +0,0 @@ -var vtk_remoterender = require('./index'); -var base = require('@jupyter-widgets/base'); - -module.exports = { - id: 'vtk-remoterender', - requires: [base.IJupyterWidgetRegistry], - activate: function(app, widgets) { - widgets.registerWidget({ - name: 'vtk-remoterender', - version: vtk_remoterender.version, - exports: vtk_remoterender - }); - }, - autoStart: true -}; - diff --git a/docker/extensions/vtk-remoterender/js/lib/remoterender.js b/docker/extensions/vtk-remoterender/js/lib/remoterender.js deleted file mode 100644 index cdd4781..0000000 --- a/docker/extensions/vtk-remoterender/js/lib/remoterender.js +++ /dev/null @@ -1,72 +0,0 @@ -var widgets = require('@jupyter-widgets/base'); -var _ = require('lodash'); - -import ParaViewWebClient from 'paraviewweb/src/IO/WebSocket/ParaViewWebClient'; -import RemoteRenderer from 'paraviewweb/src/NativeUI/Canvas/RemoteRenderer'; -import SizeHelper from "paraviewweb/src/Common/Misc/SizeHelper"; -import SmartConnect from 'wslink/src/SmartConnect'; - -var _getId = (function () { - - var cnt = 0; - return function () { - - cnt += 1; - return 'canvas_' + cnt; - } -})(); - - -export var RemoteRenderModel = widgets.DOMWidgetModel.extend({ - defaults: _.extend(widgets.DOMWidgetModel.prototype.defaults(), { - _model_name: 'RemoteRenderModel', - _view_name: 'RemoteRenderView', - _model_module: 'vtk-remoterender', - _view_module: 'vtk-remoterender', - _model_module_version: '0.1.0', - _view_module_version: '0.1.0', - }) -}); - -export var RemoteRenderView = widgets.DOMWidgetView.extend({ - render: function () { - var parent_div = this.$el.parent().prevObject[0]; - - var divRenderer = document.createElement('div'); - divRenderer.style.minHeight = '400px'; - divRenderer.style.minWidth = '400px'; - divRenderer.style.height = '100%'; - divRenderer.style.width = '100%'; - divRenderer.id = _getId(); - this.el.appendChild(divRenderer); - - var that = this; - var config = { sessionURL: this.model.get('sessionURL'), secret: this.model.get('authKey') }; - console.log(config); - var smartConnect = SmartConnect.newInstance({ config: config }); - console.log(smartConnect); - smartConnect.onConnectionReady(function (connection) { - var pvwClient = ParaViewWebClient.createClient(connection, ['MouseHandler', 'ViewPort', 'ViewPortImageDelivery']); - var renderer = new RemoteRenderer(pvwClient); - renderer.setContainer(divRenderer); - renderer.setView(that.model.get('viewID')); - renderer.showStats = true; - renderer.onImageReady(function () { - if (parent_div.style.width != '100%') { - parent_div.style.width = '100%'; - renderer.resize(); - } - console.log("We are good."); - }); - that.model.on('change:_update', function() { - // renderer.render(true); - renderer.render(false); - }, that); - SizeHelper.onSizeChange(function () { - renderer.resize(); - }); - SizeHelper.startListening(); - }); - smartConnect.connect(); - }, -}) diff --git a/docker/extensions/vtk-remoterender/js/package.json b/docker/extensions/vtk-remoterender/js/package.json deleted file mode 100644 index 877d69c..0000000 --- a/docker/extensions/vtk-remoterender/js/package.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "name": "vtk-remoterender", - "version": "0.1.0", - "description": "Interactive rendering via a vtk remote renderer", - "author": "Alice Grosch", - "main": "lib/index.js", - "repository": { - "type": "git", - "url": "https://github.com//vtk-remoterender.git" - }, - "keywords": [ - "jupyter", - "widgets", - "ipython", - "ipywidgets", - "jupyterlab-extension" - ], - "files": [ - "lib/**/*.js", - "dist/*.js" - ], - "scripts": { - "clean": "rimraf dist/", - "prepublish": "webpack", - "test": "echo \"Error: no test specified\" && exit 1" - }, - "devDependencies": { - "webpack": "^3.5.5", - "rimraf": "^2.6.1" - }, - "dependencies": { - "@jupyter-widgets/base": "^2", - "lodash": "^4.17.4", - "hammerjs": "^2.0.8", - "monologue.js": "^0.3.5", - "paraviewweb": "3.2.2", - "wslink": "^0.1.11" - }, - "jupyterlab": { - "extension": "lib/labplugin" - } -} diff --git a/docker/extensions/vtk-remoterender/js/webpack.config.js b/docker/extensions/vtk-remoterender/js/webpack.config.js deleted file mode 100644 index 8f4a124..0000000 --- a/docker/extensions/vtk-remoterender/js/webpack.config.js +++ /dev/null @@ -1,72 +0,0 @@ -var path = require('path'); -var version = require('./package.json').version; - -// Custom webpack rules are generally the same for all webpack bundles, hence -// stored in a separate local variable. -var rules = [ - { test: /\.css$/, use: ['style-loader', 'css-loader']} -] - - -module.exports = [ - {// Notebook extension - // - // This bundle only contains the part of the JavaScript that is run on - // load of the notebook. This section generally only performs - // some configuration for requirejs, and provides the legacy - // "load_ipython_extension" function which is required for any notebook - // extension. - // - entry: './lib/extension.js', - output: { - filename: 'extension.js', - path: path.resolve(__dirname, '..', 'vtk_remoterender', 'static'), - libraryTarget: 'amd' - } - }, - {// Bundle for the notebook containing the custom widget views and models - // - // This bundle contains the implementation for the custom widget views and - // custom widget. - // It must be an amd module - // - entry: './lib/index.js', - output: { - filename: 'index.js', - path: path.resolve(__dirname, '..', 'vtk_remoterender', 'static'), - libraryTarget: 'amd' - }, - devtool: 'source-map', - module: { - rules: rules - }, - externals: ['@jupyter-widgets/base'] - }, - {// Embeddable vtk-remoterender bundle - // - // This bundle is generally almost identical to the notebook bundle - // containing the custom widget views and models. - // - // The only difference is in the configuration of the webpack public path - // for the static assets. - // - // It will be automatically distributed by unpkg to work with the static - // widget embedder. - // - // The target bundle is always `dist/index.js`, which is the path required - // by the custom widget embedder. - // - entry: './lib/embed.js', - output: { - filename: 'index.js', - path: path.resolve(__dirname, 'dist'), - libraryTarget: 'amd', - publicPath: 'https://unpkg.com/vtk-remoterender@' + version + '/dist/' - }, - devtool: 'source-map', - module: { - rules: rules - }, - externals: ['@jupyter-widgets/base'] - } -]; diff --git a/docker/extensions/vtk-remoterender/setup.cfg b/docker/extensions/vtk-remoterender/setup.cfg deleted file mode 100644 index 3c6e79c..0000000 --- a/docker/extensions/vtk-remoterender/setup.cfg +++ /dev/null @@ -1,2 +0,0 @@ -[bdist_wheel] -universal=1 diff --git a/docker/extensions/vtk-remoterender/setup.py b/docker/extensions/vtk-remoterender/setup.py deleted file mode 100644 index 5d979c1..0000000 --- a/docker/extensions/vtk-remoterender/setup.py +++ /dev/null @@ -1,178 +0,0 @@ -from __future__ import print_function -from setuptools import setup, find_packages, Command -from setuptools.command.sdist import sdist -from setuptools.command.build_py import build_py -from setuptools.command.egg_info import egg_info -from subprocess import check_call -import os -import sys -import platform - -here = os.path.dirname(os.path.abspath(__file__)) -node_root = os.path.join(here, 'js') -is_repo = os.path.exists(os.path.join(here, '.git')) - -npm_path = os.pathsep.join([ - os.path.join(node_root, 'node_modules', '.bin'), - os.environ.get('PATH', os.defpath), -]) - -from distutils import log -log.set_verbosity(log.DEBUG) -log.info('setup.py entered') -log.info('$PATH=%s' % os.environ['PATH']) - -LONG_DESCRIPTION = 'Interactive rendering via a vtk remote renderer' - -def js_prerelease(command, strict=False): - """decorator for building minified js/css prior to another command""" - class DecoratedCommand(command): - def run(self): - jsdeps = self.distribution.get_command_obj('jsdeps') - if not is_repo and all(os.path.exists(t) for t in jsdeps.targets): - # sdist, nothing to do - command.run(self) - return - - try: - self.distribution.run_command('jsdeps') - except Exception as e: - missing = [t for t in jsdeps.targets if not os.path.exists(t)] - if strict or missing: - log.warn('rebuilding js and css failed') - if missing: - log.error('missing files: %s' % missing) - raise e - else: - log.warn('rebuilding js and css failed (not a problem)') - log.warn(str(e)) - command.run(self) - update_package_data(self.distribution) - return DecoratedCommand - -def update_package_data(distribution): - """update package_data to catch changes during setup""" - build_py = distribution.get_command_obj('build_py') - # distribution.package_data = find_package_data() - # re-init build_py options which load package_data - build_py.finalize_options() - - -class NPM(Command): - description = 'install package.json dependencies using npm' - - user_options = [] - - node_modules = os.path.join(node_root, 'node_modules') - - targets = [ - os.path.join(here, 'vtk_remoterender', 'static', 'extension.js'), - os.path.join(here, 'vtk_remoterender', 'static', 'index.js') - ] - - def initialize_options(self): - pass - - def finalize_options(self): - pass - - def get_npm_name(self): - npmName = 'npm'; - if platform.system() == 'Windows': - npmName = 'npm.cmd'; - - return npmName; - - def has_npm(self): - npmName = self.get_npm_name(); - try: - check_call([npmName, '--version']) - return True - except: - return False - - def should_run_npm_install(self): - package_json = os.path.join(node_root, 'package.json') - node_modules_exists = os.path.exists(self.node_modules) - return self.has_npm() - - def run(self): - has_npm = self.has_npm() - if not has_npm: - log.error("`npm` unavailable. If you're running this command using sudo, make sure `npm` is available to sudo") - - env = os.environ.copy() - env['PATH'] = npm_path - - if self.should_run_npm_install(): - log.info("Installing build dependencies with npm. This may take a while...") - npmName = self.get_npm_name(); - check_call([npmName, 'install'], cwd=node_root, stdout=sys.stdout, stderr=sys.stderr) - os.utime(self.node_modules, None) - - for t in self.targets: - if not os.path.exists(t): - msg = 'Missing file: %s' % t - if not has_npm: - msg += '\nnpm is required to build a development version of a widget extension' - raise ValueError(msg) - - # update package data in case this created new files - update_package_data(self.distribution) - -version_ns = {} -with open(os.path.join(here, 'vtk_remoterender', '_version.py')) as f: - exec(f.read(), {}, version_ns) - -setup_args = { - 'name': 'vtk_remoterender', - 'version': version_ns['__version__'], - 'description': 'Interactive rendering via a vtk remote renderer', - 'long_description': LONG_DESCRIPTION, - 'include_package_data': True, - 'package_data': {'vtk_remoterender':['js/*']}, - 'data_files': [ - ('share/jupyter/nbextensions/vtk-remoterender', [ - 'vtk_remoterender/static/extension.js', - 'vtk_remoterender/static/index.js', - 'vtk_remoterender/static/index.js.map', - ],), - ('etc/jupyter/nbconfig/notebook.d' ,['vtk-remoterender.json']) - ], - 'install_requires': [ - 'ipywidgets>=7.0.0', - 'wslink>=0.1.11', - ], - 'packages': find_packages(), - 'zip_safe': False, - 'cmdclass': { - 'build_py': js_prerelease(build_py), - 'egg_info': js_prerelease(egg_info), - 'sdist': js_prerelease(sdist, strict=True), - 'jsdeps': NPM, - }, - - 'author': 'Alice Grosch', - 'author_email': 'a.grosch@fz-juelich.de', - 'url': 'https://github.com//vtk-remoterender', - 'keywords': [ - 'ipython', - 'jupyter', - 'widgets', - ], - 'classifiers': [ - 'Development Status :: 4 - Beta', - 'Framework :: IPython', - 'Intended Audience :: Developers', - 'Intended Audience :: Science/Research', - 'Topic :: Multimedia :: Graphics', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.3', - 'Programming Language :: Python :: 3.4', - 'Programming Language :: Python :: 3.5', - ], -} - -setup(**setup_args) diff --git a/docker/extensions/vtk-remoterender/vtk-remoterender.json b/docker/extensions/vtk-remoterender/vtk-remoterender.json deleted file mode 100644 index 26dc27a..0000000 --- a/docker/extensions/vtk-remoterender/vtk-remoterender.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "load_extensions": { - "vtk-remoterender/extension": true - } -} diff --git a/docker/extensions/vtk-remoterender/vtk_remoterender/__init__.py b/docker/extensions/vtk-remoterender/vtk_remoterender/__init__.py deleted file mode 100644 index 71e4478..0000000 --- a/docker/extensions/vtk-remoterender/vtk_remoterender/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -from ._version import version_info, __version__ - -from .remoterender import * - -def _jupyter_nbextension_paths(): - return [{ - 'section': 'notebook', - 'src': 'static', - 'dest': 'vtk-remoterender', - 'require': 'vtk-remoterender/extension' - }] diff --git a/docker/extensions/vtk-remoterender/vtk_remoterender/_version.py b/docker/extensions/vtk-remoterender/vtk_remoterender/_version.py deleted file mode 100644 index ed74cad..0000000 --- a/docker/extensions/vtk-remoterender/vtk_remoterender/_version.py +++ /dev/null @@ -1,6 +0,0 @@ -version_info = (0, 1, 0, 'alpha', 0) - -_specifier_ = {'alpha': 'a', 'beta': 'b', 'candidate': 'rc', 'final': ''} - -__version__ = '%s.%s.%s%s'%(version_info[0], version_info[1], version_info[2], - '' if version_info[3]=='final' else _specifier_[version_info[3]]+str(version_info[4])) diff --git a/docker/extensions/vtk-remoterender/vtk_remoterender/remoterender.py b/docker/extensions/vtk-remoterender/vtk_remoterender/remoterender.py deleted file mode 100644 index f744eeb..0000000 --- a/docker/extensions/vtk-remoterender/vtk_remoterender/remoterender.py +++ /dev/null @@ -1,20 +0,0 @@ -import ipywidgets as widgets -from traitlets import Int, Unicode - -@widgets.register -class RemoteRender(widgets.DOMWidget): - """An example widget.""" - _view_name = Unicode('RemoteRenderView').tag(sync=True) - _model_name = Unicode('RemoteRenderModel').tag(sync=True) - _view_module = Unicode('vtk-remoterender').tag(sync=True) - _model_module = Unicode('vtk-remoterender').tag(sync=True) - _view_module_version = Unicode('^0.1.0').tag(sync=True) - _model_module_version = Unicode('^0.1.0').tag(sync=True) - - sessionURL = Unicode('ws://localhost:8080/ws').tag(sync=True) - authKey = Unicode('wslink-secret').tag(sync=True) - _update = Int(0).tag(sync=True) - viewID = Unicode("-1").tag(sync=True) - - def update_render(self): - self._update += 1 diff --git a/images/WidgetArch.png b/images/WidgetArch.png deleted file mode 100644 index 9fadae7fceb547556600077decca2abf53e616bd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 23058 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⪼s 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 SIv}6T2M#;DGwumCAn8AcGJCB6 diff --git a/images/WidgetModelView.png b/images/WidgetModelView.png new file mode 100644 index 0000000000000000000000000000000000000000..f641f241a1faa649f8d5ac17720f4a6b65a01e0a GIT binary patch literal 38681 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≷;9OH-nf6p<YxA}({Xc3{UqM}r?c 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 SjcjP}pR|O+?fhE?{{I`MZkxXV literal 0 HcmV?d00001 diff --git a/ipywidgets/introduction.ipynb b/ipywidgets/introduction.ipynb new file mode 100644 index 0000000..05a9d44 --- /dev/null +++ b/ipywidgets/introduction.ipynb @@ -0,0 +1,519 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Simple Widget Introduction" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## What are widgets?" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "Widgets are eventful python objects that have a representation in the browser, often as a control like a slider, textbox, etc." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## What can they be used for?" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "You can use widgets to build **interactive GUIs** for your notebooks. \n", + "You can also use widgets to **synchronize stateful and stateless information** between Python and JavaScript." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Using widgets " + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "To use the widget framework, you need to import `ipywidgets`." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "import ipywidgets as widgets" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "### repr" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Widgets have their own display `repr` which allows them to be displayed using IPython's display framework. Constructing and returning an `IntSlider` automatically displays the widget (as seen below). Widgets are displayed inside the output area below the code cell. Clearing cell output will also remove the widget." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "8511c20719d945e0b9d95892c02e06e6", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "IntSlider(value=0)" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "widgets.IntSlider()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "### display()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can also explicitly display the widget using `display(...)`." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "8cb7600eadc54d0fa1327a65af6a0d0c", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "IntSlider(value=0)" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from IPython.display import display\n", + "w = widgets.IntSlider()\n", + "display(w)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "### Multiple display() calls" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If you display the same widget twice, the displayed instances in the front-end will remain in sync with each other. Try dragging the slider below and watch the slider above." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "8cb7600eadc54d0fa1327a65af6a0d0c", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "IntSlider(value=0)" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "display(w)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Why does displaying the same widget twice work?" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "Widgets are represented in the back-end by a single object. Each time a widget is displayed, a new representation of that same object is created in the front-end. These representations are called views.\n", + "\n", + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Widget properties" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "All of the IPython widgets share a similar naming scheme. To read the value of a widget, you can query its `value` property." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "71d46e39ff2a4dbd91eeed75b1f1fa69", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "IntSlider(value=0)" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "w = widgets.IntSlider()\n", + "display(w)" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "43" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "w.value" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Similarly, to set a widget's value, you can set its `value` property." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [], + "source": [ + "w.value = 100" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "### Keys" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In addition to `value`, most widgets share `keys`, `description`, and `disabled`. To see the entire list of synchronized, stateful properties of any specific widget, you can query the `keys` property. Generally you should not interact with properties starting with an underscore." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['_dom_classes',\n", + " '_model_module',\n", + " '_model_module_version',\n", + " '_model_name',\n", + " '_view_count',\n", + " '_view_module',\n", + " '_view_module_version',\n", + " '_view_name',\n", + " 'continuous_update',\n", + " 'description',\n", + " 'description_tooltip',\n", + " 'disabled',\n", + " 'layout',\n", + " 'max',\n", + " 'min',\n", + " 'orientation',\n", + " 'readout',\n", + " 'readout_format',\n", + " 'step',\n", + " 'style',\n", + " 'value']" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "w.keys" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Shorthand for setting the initial values of widget properties" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "While creating a widget, you can set some or all of the initial values of that widget by defining them as keyword arguments in the widget's constructor (as seen below)." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "0d70c9aeae0c4482b16bff8fb6100d87", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Text(value='Hello World!', disabled=True)" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "widgets.Text(value='Hello World!', disabled=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Linking two similar widgets" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "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." + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "c6e090ac3d4a4525bae67e8ecd459330", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "FloatText(value=0.0)" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "ebef28d0e3c94b699930011a43dfc79c", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "FloatSlider(value=0.0)" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "a = widgets.FloatText()\n", + "b = widgets.FloatSlider()\n", + "display(a,b)\n", + "\n", + "mylink = widgets.link((a, 'value'), (b, 'value'))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Unlinking widgets" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "Unlinking the widgets is simple. All you have to do is call `.unlink` on the link object. Try changing one of the widgets above after unlinking to see that they can be independently changed." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# mylink.unlink()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# In-Depth Tutorial\n", + "\n", + "https://github.com/jupyter-widgets/tutorial" + ] + } + ], + "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/ipywidgets.ipynb b/ipywidgets/ipywidgets.ipynb similarity index 99% rename from ipywidgets.ipynb rename to ipywidgets/ipywidgets.ipynb index 6e56208..7d2ff73 100644 --- a/ipywidgets.ipynb +++ b/ipywidgets/ipywidgets.ipynb @@ -1821,7 +1821,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.5" + "version": "3.6.8" } }, "nbformat": 4, diff --git a/ipywidgets/widget_events.ipynb b/ipywidgets/widget_events.ipynb new file mode 100644 index 0000000..36f8cbb --- /dev/null +++ b/ipywidgets/widget_events.ipynb @@ -0,0 +1,816 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "# Widget Events" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Special events" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The `Button` is not used to represent a data type. Instead the button widget is used to handle mouse clicks. The `on_click` method of the `Button` can be used to register function to be called when the button is clicked. The doc string of the `on_click` can be seen below." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Register a callback to execute when the button is clicked.\n", + "\n", + " The callback will be called with one argument, the clicked button\n", + " widget instance.\n", + "\n", + " Parameters\n", + " ----------\n", + " remove: bool (optional)\n", + " Set to true to remove the callback from the list of callbacks.\n", + " \n" + ] + } + ], + "source": [ + "import ipywidgets as widgets\n", + "from IPython.display import display\n", + "\n", + "print(widgets.Button.on_click.__doc__)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "### Example" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Since button clicks are stateless, they are transmitted from the front-end to the back-end using custom messages. By using the `on_click` method, a button that prints a message when it has been clicked is shown below. To capture `print`s (or any other kind of output including errors) and ensure it is displayed, be sure to send it to an `Output` widget (or put the information you want to display into an `HTML` widget)." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "e35a61988f8a47148ae900a300c51d32", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Button(description='Click Me!', style=ButtonStyle())" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "007b2efd405643cfae7bdcde45acdddc", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Output()" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "button = widgets.Button(description=\"Click Me!\")\n", + "output = widgets.Output()\n", + "\n", + "display(button, output)\n", + "@output.capture()\n", + "def on_button_clicked(b):\n", + " print(\"Button clicked.\")\n", + "\n", + "button.on_click(on_button_clicked)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "## Traitlet events" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Widget properties are IPython traitlets and traitlets are eventful. To handle changes, the `observe` method of the widget can be used to register a callback. The doc string for `observe` can be seen below." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Setup a handler to be called when a trait changes.\n", + "\n", + " This is used to setup dynamic notifications of trait changes.\n", + "\n", + " Parameters\n", + " ----------\n", + " handler : callable\n", + " A callable that is called when a trait changes. Its\n", + " signature should be ``handler(change)``, where ``change`` is a\n", + " dictionary. The change dictionary at least holds a 'type' key.\n", + " * ``type``: the type of notification.\n", + " Other keys may be passed depending on the value of 'type'. In the\n", + " case where type is 'change', we also have the following keys:\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.\n", + " names : list, str, All\n", + " If names is All, the handler will apply to all traits. If a list\n", + " of str, handler will apply to all names in the list. If a\n", + " str, the handler will apply just to that name.\n", + " type : str, All (default: 'change')\n", + " The type of notification to filter by. If equal to All, then all\n", + " notifications are passed to the observe handler.\n", + " \n" + ] + } + ], + "source": [ + "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, + "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", + "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": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "### Callback signatures" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Mentioned in the doc string, the callback registered must have the signature `handler(change)` where `change` is a dictionary holding the information about the change. \n", + "\n", + "Using this method, an example of how to output an `IntSlider`'s value as it is changed can be seen below." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "24a3a202baee498fafbca313d1b9e2f3", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "IntSlider(value=0)" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "9cd6d9e6c1694f88b33f0e8767f738e5", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Output()" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "int_range = widgets.IntSlider()\n", + "output2 = widgets.Output()\n", + "\n", + "display(int_range, output2)\n", + "\n", + "def on_value_change(change):\n", + " output2.clear_output()\n", + " with output2:\n", + " print(change['new'])\n", + "\n", + "int_range.observe(on_value_change, names='value')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Why `observe` instead of `link`?" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Using `link` is great if no transformation of the values is needed. `observe` is useful if some kind of calculation needs to be done with the values or if the values that are related have different types.\n", + "\n", + "The example below converts between Celsius and Farhenheit. As written, changing the temperature in Celcius will update the temperature in Farenheit, but not the other way around. You will add that as an exercise." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "4fc4d5a3ed47460ab4711b56146a9557", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "FloatText(value=0.0, description='Temp $^\\\\circ$C')" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "82da94d8bee5428182a9c610cbcd4899", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "FloatText(value=32.0, description='Temp $^\\\\circ$F')" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "def C_to_F(temp):\n", + " return 1.8 * temp + 32\n", + "\n", + "def F_to_C(temp):\n", + " return (temp -32) / 1.8\n", + "\n", + "degree_C = widgets.FloatText(description='Temp $^\\circ$C', value=0)\n", + "degree_F = widgets.FloatText(description='Temp $^\\circ$F', value=C_to_F(degree_C.value))\n", + "\n", + "def on_C_change(change):\n", + " degree_F.value = C_to_F(change['new'])\n", + " \n", + "degree_C.observe(on_C_change, names='value')\n", + "\n", + "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": {}, + "source": [ + "## Advanced Widget Linking" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In an earlier notebook you used `link` to link the value of one widget to another. \n", + "\n", + "There are a couple of other linking methods that offer more flexibility:\n", + "\n", + "+ `dlink` is a *directional* link; updates happen in one direction but not the other.\n", + "+ `jslink` and `jsdlink` do the linking in the front end (i.e. in JavaScript without any communication to Python). " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Linking traitlets attributes in the kernel (ie. in Python)\n", + "\n", + "The first method is to use the `link` and `dlink`. This only works if we are interacting with a live kernel." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "5f25499c4e094bb4b92ee5d0c3d45aa9", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Label(value='The values of slider1 and slider2 are synchronized')" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "3f551efbd31348a8989d839d4afe3167", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "IntSlider(value=0, description='Slider 1')" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "5b8c115b07a44874b84b33dfcb974f05", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "IntSlider(value=0, description='Slider 2')" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "caption = widgets.Label(value='The values of slider1 and slider2 are synchronized')\n", + "sliders1, slider2 = widgets.IntSlider(description='Slider 1'),\\\n", + " widgets.IntSlider(description='Slider 2')\n", + "\n", + "display(caption, sliders1, slider2)\n", + "\n", + "l = widgets.link((sliders1, 'value'), (slider2, 'value'))\n" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "869e7b59e3224e83ac790624c2f1b5e6", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "HTML(value='Changes in source values are reflected in target1, but changes in target1 do not affect source')" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "f9a3fa00f826428ca05314bad3698f26", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "IntSlider(value=0, description='Source')" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "dd35388d682e433e81b9262bbfef0565", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "IntSlider(value=0, description='Target 1')" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "caption = widgets.HTML(value='Changes in source values are reflected in target1, but changes in target1 do not affect source')\n", + "source, target1 = widgets.IntSlider(description='Source'),\\\n", + " widgets.IntSlider(description='Target 1')\n", + "\n", + "display(caption, source, target1)\n", + "\n", + "dl = widgets.dlink((source, 'value'), (target1, 'value'))\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Links can be broken by calling `unlink`." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "l.unlink()\n", + "dl.unlink()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Function `widgets.jslink` returns a `Link` widget. The link can be broken by calling the `unlink` method." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Linking widgets attributes from the client side" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can also directly link widget attributes in the browser using the link widgets, in either a unidirectional or a bidirectional fashion.\n", + "\n", + "Javascript links persist when embedding widgets in html web pages without a kernel." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "e9b8f82d34384819958a1c7d3bed520b", + "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": "011978ade1534cd1914e0a08ec4b8e69", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "IntSlider(value=0, description='Range 1')" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "b2c228ade9774629b53c1a8f4f1c37c7", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "IntSlider(value=0, description='Range 2')" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "caption = widgets.Label(value='The values of range1 and range2 are synchronized')\n", + "range1, range2 = widgets.IntSlider(description='Range 1'),\\\n", + " widgets.IntSlider(description='Range 2')\n", + "\n", + "display(caption, range1, range2)\n", + "\n", + "l = widgets.jslink((range1, 'value'), (range2, 'value'))" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "4519e9ad4b2f4e54ba26ef00e519f1bb", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Label(value='Changes in source_range values are reflected in target_range1')" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "fec7688a6ccf46eea4b1b36551d4044a", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "IntSlider(value=0, description='Source range')" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "3041273f6e764a92b289102b4d0c34ba", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "IntSlider(value=0, description='Target range 1')" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "caption = widgets.Label(value='Changes in source_range values are reflected in target_range1')\n", + "source_range, target_range1 = widgets.IntSlider(description='Source range'),\\\n", + " widgets.IntSlider(description='Target range 1')\n", + "\n", + "display(caption, source_range, target_range1)\n", + "\n", + "dl = widgets.jsdlink((source_range, 'value'), (target_range1, 'value'))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The links can be broken by calling the `unlink` method." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [], + "source": [ + "l.unlink()\n", + "dl.unlink()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### The difference between linking in the kernel and linking in the client\n", + "\n", + "Linking in the kernel means linking via python. If two sliders are linked in the kernel, when one slider is changed the browser sends a message to the kernel (python in this case) updating the changed slider, the link widget in the kernel then propagates the change to the other slider object in the kernel, and then the other slider's kernel object sends a message to the browser to update the other slider's views in the browser. If the kernel is not running (as in a static web page), then the controls will not be linked.\n", + "\n", + "Linking using jslink (i.e., on the browser side) means contructing the link in Javascript. When one slider is changed, Javascript running in the browser changes the value of the other slider in the browser, without needing to communicate with the kernel at all. If the sliders are attached to kernel objects, each slider will update their kernel-side objects independently.\n", + "\n", + "To see the difference between the two, go to the [static version of this page in the ipywidgets documentation](http://ipywidgets.readthedocs.io/en/latest/examples/Widget%20Events.html) and try out the sliders near the bottom. The ones linked in the kernel with `link` and `dlink` are no longer linked, but the ones linked in the browser with `jslink` and `jsdlink` are still linked." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Continuous vs delayed updates\n", + "\n", + "Some widgets offer a choice with their `continuous_update` attribute between continually updating values or only updating values when a user submits the value (for example, by pressing Enter or navigating away from the control). In the next example, we see the \"Delayed\" controls only transmit their value after the user finishes dragging the slider or submitting the textbox. The \"Continuous\" controls continually transmit their values as they are changed. Try typing a two-digit number into each of the text boxes, or dragging each of the sliders, to see the difference." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "bd9af1c27b924dbba8d5482b72ae4c53", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "VBox(children=(IntSlider(value=0, continuous_update=False, description='Delayed'), IntText(value=0, descriptio…" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "a = widgets.IntSlider(description=\"Delayed\", continuous_update=False)\n", + "b = widgets.IntText(description=\"Delayed\", continuous_update=False)\n", + "c = widgets.IntSlider(description=\"Continuous\", continuous_update=True)\n", + "d = widgets.IntText(description=\"Continuous\", continuous_update=True)\n", + "\n", + "widgets.link((a, 'value'), (b, 'value'))\n", + "widgets.link((a, 'value'), (c, 'value'))\n", + "widgets.link((a, 'value'), (d, 'value'))\n", + "widgets.VBox([a,b,c,d])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Sliders, `Text`, and `Textarea` controls default to `continuous_update=True`. `IntText` and other text boxes for entering integer or float numbers default to `continuous_update=False` (since often you'll want to type an entire number before submitting the value by pressing enter or navigating out of the box)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "cell_tags": [ + [ + "<None>", + null + ] + ], + "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/ipywidgets/widget_layout.ipynb b/ipywidgets/widget_layout.ipynb new file mode 100644 index 0000000..62d4586 --- /dev/null +++ b/ipywidgets/widget_layout.ipynb @@ -0,0 +1,897 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Layout and Styling of Jupyter widgets\n", + "\n", + "This section presents how to layout and style Jupyter interactive widgets to build rich and *reactive* widget-based applications." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## The `layout` attribute.\n", + "\n", + "Jupyter interactive widgets have a `layout` attribute exposing a number of CSS properties that impact how widgets are laid out.\n", + "\n", + "### Exposed CSS properties\n", + "\n", + "<div class=\"alert alert-info\" style=\"margin: 20px\">\n", + "The following properties map to the values of the CSS properties of the same name (underscores being replaced with dashes), applied to the top DOM elements of the corresponding widget.\n", + "</div>\n", + "\n", + "\n", + "#### Sizes\n", + "\n", + "- `height`\n", + "- `width`\n", + "- `max_height`\n", + "- `max_width`\n", + "- `min_height`\n", + "- `min_width`\n", + "\n", + "#### Display\n", + "\n", + "- `visibility`\n", + "- `display`\n", + "- `overflow`\n", + "- `overflow_x` (deprecated in `7.5`, use `overflow` instead)\n", + "- `overflow_y` (deprecated in `7.5`, use `overflow` instead)\n", + "\n", + "#### Box model\n", + "\n", + "- `border` \n", + "- `margin`\n", + "- `padding`\n", + "\n", + "#### Positioning\n", + "\n", + "- `top`\n", + "- `left`\n", + "- `bottom`\n", + "- `right`\n", + "\n", + "#### Image/media\n", + "\n", + "- `object_fit`\n", + "- `object_position`\n", + "\n", + "#### Flexbox\n", + "\n", + "- `order`\n", + "- `flex_flow`\n", + "- `align_items`\n", + "- `flex`\n", + "- `align_self`\n", + "- `align_content`\n", + "- `justify_content`\n", + "- `justify_items`\n", + "\n", + "#### Grid layout\n", + "\n", + "- `grid_auto_columns`\n", + "- `grid_auto_flow`\n", + "- `grid_auto_rows`\n", + "- `grid_gap`\n", + "- `grid_template_rows`\n", + "- `grid_template_columns`\n", + "- `grid_template_areas`\n", + "- `grid_row`\n", + "- `grid_column`\n", + "- `grid_area`\n", + "\n", + "### Shorthand CSS properties\n", + "\n", + "You may have noticed that certain CSS properties such as `margin-[top/right/bottom/left]` seem to be missing. The same holds for `padding-[top/right/bottom/left]` etc.\n", + "\n", + "In fact, you can atomically specify `[top/right/bottom/left]` margins via the `margin` attribute alone by passing the string `'100px 150px 100px 80px'` for a respectively `top`, `right`, `bottom` and `left` margins of `100`, `150`, `100` and `80` pixels.\n", + "\n", + "Similarly, the `flex` attribute can hold values for `flex-grow`, `flex-shrink` and `flex-basis`. The `border` attribute is a shorthand property for `border-width`, `border-style (required)`, and `border-color`." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Simple examples" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The following example shows how to resize a `Button` so that its views have a height of `80px` and a width of `50%` of the available space. It also includes an example of setting a CSS property that requires multiple values (a border, in thise case):" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "ea23e02ad8f0498592ca3d515a7c4e82", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Button(description='(50% width, 80px height) button', layout=Layout(border='2px dotted blue', height='80px', w…" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from ipywidgets import Button, Layout\n", + "\n", + "b = Button(description='(50% width, 80px height) button',\n", + " layout=Layout(width='50%', height='80px', border='2px dotted blue'))\n", + "b" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The `layout` property can be shared between multiple widgets and assigned directly." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "1d9ef85d393c447db8aa91d818d9f7b2", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Button(description='Another button with the same layout', layout=Layout(border='2px dotted blue', height='80px…" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "Button(description='Another button with the same layout', layout=b.layout)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Is simple layout really simple?\n", + "\n", + "The cell below adds a `min_width` and `max_width` to the button layout. The effect may be surprising; in CSS if max/min width are present they override the width." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "b.layout.min_width='10%'\n", + "b.layout.max_width='20%'" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Natural sizes, and arrangements using HBox and VBox\n", + "\n", + "Most of the core-widgets have default heights and widths that tile well together. This allows simple layouts based on the `HBox` and `VBox` helper functions to align naturally:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "1b901d6a06a24a3f9e9acd90cd6f93ed", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "HBox(children=(VBox(children=(Button(description='correct', style=ButtonStyle()), Button(description='horse', …" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from ipywidgets import Button, HBox, VBox\n", + "\n", + "words = ['correct', 'horse', 'battery', 'staple']\n", + "items = [Button(description=w) for w in words]\n", + "left_box = VBox([items[0], items[1]])\n", + "right_box = VBox([items[2], items[3]])\n", + "HBox([left_box, right_box])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Flexbox and Grid\n", + "\n", + "The *Flexbox* CSS specification is great for laying out items in a single direction, horizontally or vertically. As we saw in the previous example, two dimensional layout can be done with flexbox by using a combination of horizontal and vertical components.\n", + "\n", + "The *Grid* CSS specifation is designed to be used for two dimensional layout. There are properties for specifying the number of items in each row or column, how they should be sized, and how items should be aligned.\n", + "\n", + "### For more information about Flexbox and Grid\n", + "\n", + "The are notebooks with more detail about [widgets and the Flexbox model](reference_guides/guide-flex-box.ipynb) and [widgets and the Grid model](reference_guides/guide-grid-box.ipynb). The code examples from each of those notebooks is included here also.\n", + "\n", + "If you want to learn more about CSS layout after this tutorial, take a look at this [excellent set of articles on CSS layout at MDN](https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout). The Flexbox and Grid articles each have links to more extensive guides at the end of the article." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## The Flexbox layout\n", + "\n", + "The `HBox` and `VBox` classes above are special cases of the `Box` widget.\n", + "\n", + "The `Box` widget enables the entire CSS flexbox spec as well as the Grid layout 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", + "Again, the whole flexbox 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", + "### Acknowledgement\n", + "\n", + "The following flexbox tutorial on the flexbox layout follows the lines of the article [A Complete Guide to Flexbox](https://css-tricks.com/snippets/css/a-guide-to-flexbox/) by Chris Coyier, and uses text and various images from the article [with permission](https://css-tricks.com/license/).\n", + "\n", + "### Basics and terminology\n", + "\n", + "The flexbox layout spectrum is excellent for laying out items in a single direction, either horizontally or vertically. \n", + "\n", + "Since flexbox is a whole module and not a single property, it involves a lot of things including its whole set of properties. Some of them are meant to be set on the container (parent element, known as \"flex container\") whereas the others are meant to be set on the children (known as \"flex items\").\n", + "If regular layout is based on both block and inline flow directions, the flex layout is based on \"flex-flow directions\". Please have a look at this figure from the specification, explaining the main idea behind the flex layout.\n", + "\n", + "\n", + "\n", + "Basically, items will be laid out following either the `main axis` (from `main-start` to `main-end`) or the `cross axis` (from `cross-start` to `cross-end`).\n", + "\n", + "- `main axis` - The main axis of a flex container is the primary axis along which flex items are laid out. Beware, it is not necessarily horizontal; it depends on the flex-direction property (see below).\n", + "- `main-start | main-end` - The flex items are placed within the container starting from main-start and going to main-end.\n", + "- `main size` - A flex item's width or height, whichever is in the main dimension, is the item's main size. The flex item's main size property is either the ‘width’ or ‘height’ property, whichever is in the main dimension.\n", + "cross axis - The axis perpendicular to the main axis is called the cross axis. Its direction depends on the main axis direction.\n", + "- `cross-start | cross-end` - Flex lines are filled with items and placed into the container starting on the cross-start side of the flex container and going toward the cross-end side.\n", + "- `cross size` - The width or height of a flex item, whichever is in the cross dimension, is the item's cross size. The cross size property is whichever of ‘width’ or ‘height’ that is in the cross dimension.\n", + "\n", + "### The VBox and HBox helpers\n", + "\n", + "The `VBox` and `HBox` helper classes provide simple defaults to arrange child widgets in vertical and horizontal boxes. They are roughly equivalent to:\n", + "\n", + "```Python\n", + "def VBox(*pargs, **kwargs):\n", + " \"\"\"Displays multiple widgets vertically using the flexible box model.\"\"\"\n", + " box = Box(*pargs, **kwargs)\n", + " box.layout.display = 'flex'\n", + " box.layout.flex_flow = 'column'\n", + " box.layout.align_items = 'stretch'\n", + " return box\n", + "\n", + "def HBox(*pargs, **kwargs):\n", + " \"\"\"Displays multiple widgets horizontally using the flexible box model.\"\"\"\n", + " box = Box(*pargs, **kwargs)\n", + " box.layout.display = 'flex'\n", + " box.layout.align_items = 'stretch'\n", + " return box\n", + "```\n", + "\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Examples\n", + "\n", + "**Four buttons in a VBox. Items stretch to the maximum width, in a vertical box taking `50%` of the available space.**" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "dcc88332b5204cb6838d1be0d7610539", + "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='50%')\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": [ + "**Three buttons in an HBox. Items flex proportionally to their weight.**" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "9b1ee4576d1348aa83d08fa972642f5d", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "VBox(children=(Box(children=(Button(button_style='danger', description='weight=1; auto', layout=Layout(flex='1…" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from ipywidgets import Layout, Button, Box, VBox\n", + "\n", + "# Items flex proportionally to the weight and the left over space around the text \n", + "items_auto = [\n", + " Button(description='weight=1; auto', layout=Layout(flex='1 1 auto', width='auto'), button_style='danger'),\n", + " Button(description='weight=3; auto', layout=Layout(flex='3 1 auto', width='auto'), button_style='danger'),\n", + " Button(description='weight=1; auto', layout=Layout(flex='1 1 auto', width='auto'), button_style='danger'),\n", + " ]\n", + "\n", + "# Items flex proportionally to the weight \n", + "items_0 = [\n", + " Button(description='weight=1; 0%', layout=Layout(flex='1 1 0%', width='auto'), button_style='danger'),\n", + " Button(description='weight=3; 0%', layout=Layout(flex='3 1 0%', width='auto'), button_style='danger'),\n", + " Button(description='weight=1; 0%', layout=Layout(flex='1 1 0%', width='auto'), button_style='danger'),\n", + " ]\n", + "box_layout = Layout(display='flex',\n", + " flex_flow='row', \n", + " align_items='stretch', \n", + " width='70%')\n", + "box_auto = Box(children=items_auto, layout=box_layout)\n", + "box_0 = Box(children=items_0, layout=box_layout)\n", + "VBox([box_auto, box_0])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**A more advanced example: a reactive form.**\n", + "\n", + "The form is a `VBox` of width '50%'. Each row in the VBox is an HBox, that justifies the content with space between.." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "98f21d2d85d94c629b31300cee3296c4", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Box(children=(Box(children=(Label(value='Age of the captain'), IntSlider(value=40, max=60, min=40)), layout=La…" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from ipywidgets import Layout, Button, Box, FloatText, Textarea, Dropdown, Label, IntSlider\n", + "\n", + "form_item_layout = Layout(\n", + " display='flex',\n", + " flex_flow='row',\n", + " justify_content='space-between'\n", + ")\n", + "\n", + "form_items = [\n", + " Box([Label(value='Age of the captain'), IntSlider(min=40, max=60)], layout=form_item_layout),\n", + " Box([Label(value='Egg style'), \n", + " Dropdown(options=['Scrambled', 'Sunny side up', 'Over easy'])], layout=form_item_layout),\n", + " Box([Label(value='Ship size'), \n", + " FloatText()], layout=form_item_layout),\n", + " Box([Label(value='Information'), \n", + " Textarea()], layout=form_item_layout)\n", + "]\n", + "\n", + "form = Box(form_items, layout=Layout(\n", + " display='flex',\n", + " flex_flow='column',\n", + " border='solid 2px',\n", + " align_items='stretch',\n", + " width='50%'\n", + "))\n", + "form" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**A more advanced example: a carousel.**" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "3fc572786fc04b5d9ba1775c6cf623c9", + "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": [ + "#### *Compatibility note*\n", + "\n", + "The `overflow_x` and `overflow_y` options are deprecated in ipywidgets `7.5`. Instead, use the shorthand property `overflow='scroll hidden'`. The first part specificies overflow in `x`, the second the overflow in `y`." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## A widget for exploring layout options\n", + "\n", + "Use the dropdowns and sliders in the widget to change the layout of the box containing the colored buttons. Many of the CSS layout options described above are available, and the Python code to generate a `Layout` object reflecting the settings is in a `TextArea` in the widget.\n", + "\n", + "A few questions to answer after the demonstration of this (see the [detailed flexbox guide for a longer discussion](reference_guides/guide-flex-box.ipynb)):\n", + "\n", + "1. What does changing `justify_content` affect? You may find it easier to answer this if you set `wrap` to `wrap`.\n", + "2. What does `align_items` affect? \n", + "3. How is `align_content` different than `align_items`?\n" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "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." + ] + }, + { + "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, + "metadata": {}, + "outputs": [], + "source": [ + "from ipywidgets import Button, GridBox, Layout, ButtonStyle" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The first example defines a 3x3 grid and places 9 buttons into the grid. " + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "fca327842fd0479cadfb59a0af54a1ea", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "GridBox(children=(Button(description='0', layout=Layout(height='auto', width='auto'), style=ButtonStyle(button…" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "GridBox(children=[Button(description=str(i), layout=Layout(width='auto', height='auto'),\n", + " style=ButtonStyle(button_color='darkseagreen')) for i in range(9)\n", + " ],\n", + " layout=Layout(\n", + " width='50%',\n", + " grid_template_columns='100px 50px 100px',\n", + " grid_template_rows='80px auto 80px', \n", + " grid_gap='5px 10px')\n", + " )" + ] + }, + { + "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": {}, + "source": [ + "### An alternate way of defining the grid\n", + "\n", + "The grid can also be set up using a description words. The layout below defines a grid with 4 columns and 3 rows. The first row is a header, the bottom row is a footer, and the middle row has content in the first two columns, then an empty cell, followed by a sidebar.\n", + "\n", + "Widgets are assigned to each of these areas by setting the widgets's layout `grid_area` to the name of the area.\n", + "\n", + "```\n", + " \"header header header header\"\n", + " \"main main . sidebar \"\n", + " \"footer footer footer footer\"\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "e9eccac0c6f644b097cec80bef3c8317", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "GridBox(children=(Button(description='Header', layout=Layout(grid_area='header', width='auto'), style=ButtonSt…" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "header = Button(description='Header',\n", + " layout=Layout(width='auto', grid_area='header'),\n", + " style=ButtonStyle(button_color='lightblue'))\n", + "main = Button(description='Main',\n", + " layout=Layout(width='auto', grid_area='main'),\n", + " style=ButtonStyle(button_color='moccasin'))\n", + "sidebar = Button(description='Sidebar',\n", + " layout=Layout(width='auto', grid_area='sidebar'),\n", + " style=ButtonStyle(button_color='salmon'))\n", + "footer = Button(description='Footer',\n", + " layout=Layout(width='auto', grid_area='footer'),\n", + " style=ButtonStyle(button_color='olive'))\n", + "\n", + "GridBox(children=[header, main, sidebar, footer],\n", + " layout=Layout(\n", + " width='50%',\n", + " grid_template_rows='auto auto auto',\n", + " grid_template_columns='25% 25% 25% 25%',\n", + " grid_template_areas='''\n", + " \"header header header header\"\n", + " \"main main . sidebar \"\n", + " \"footer footer footer footer\"\n", + " ''')\n", + " )" + ] + }, + { + "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. " + ] + } + ], + "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/ipywidgets/widget_styling.ipynb b/ipywidgets/widget_styling.ipynb new file mode 100644 index 0000000..c42a9df --- /dev/null +++ b/ipywidgets/widget_styling.ipynb @@ -0,0 +1,216 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Predefined styles\n", + "\n", + "If you wish the styling of widgets to make use of colors and styles defined by the environment (to be consistent with e.g. a notebook theme), some widgets enable choosing in a list of pre-defined styles.\n", + "\n", + "For example, the `Button` widget has a `button_style` attribute that may take 5 different values:\n", + "\n", + " - `'primary'`\n", + " - `'success'`\n", + " - `'info'`\n", + " - `'warning'`\n", + " - `'danger'`\n", + "\n", + "besides the default empty string ''." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "5e4bd6b40f3e4acbb4e312f741d7760c", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Button(button_style='danger', description='Danger Button', style=ButtonStyle())" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from ipywidgets import Button, IntSlider\n", + "\n", + "Button(description='Danger Button', button_style='danger')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## The `style` attribute\n", + "\n", + "While the `layout` attribute only exposes layout-related CSS properties for the top-level DOM element of widgets, the \n", + "`style` attribute is used to expose non-layout related styling attributes of widgets.\n", + "\n", + "However, the properties of the `style` attribute are specific to each widget type." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "0ec387a4a59f418caca91387e94e10c7", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Button(description='Custom color', style=ButtonStyle(button_color='lightgreen'))" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "b1 = Button(description='Custom color')\n", + "b1.style.button_color = 'lightgreen'\n", + "b1" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can get a list of the style attributes for a widget with the `keys` property." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['_model_module',\n", + " '_model_module_version',\n", + " '_model_name',\n", + " '_view_count',\n", + " '_view_module',\n", + " '_view_module_version',\n", + " '_view_name',\n", + " 'button_color',\n", + " 'font_weight']" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "b1.style.keys" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Just like the `layout` attribute, widget styles can be assigned to other widgets." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "57d0156e8f8c4f71aa51f8f972e42697", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Button(style=ButtonStyle(button_color='lightgreen'))" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "b2 = Button()\n", + "b2.style = b1.style\n", + "b2" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Widget styling attributes are specific to each widget type." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "8dcbf68e65a94e95bcdf7df29a05afa1", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "IntSlider(value=0, description='Blue handle', style=SliderStyle(handle_color='lightblue'))" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "s1 = IntSlider(description='Blue handle')\n", + "s1.style.handle_color = 'lightblue'\n", + "s1" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "There is a [list of all style keys](Table%20of%20widget%20keys%20and%20style%20keys.ipynb#Style-keys)." + ] + } + ], + "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/mycustomtemplate/.ipynb_checkpoints/conf-checkpoint.json b/mycustomtemplate/.ipynb_checkpoints/conf-checkpoint.json deleted file mode 100644 index db61e00..0000000 --- a/mycustomtemplate/.ipynb_checkpoints/conf-checkpoint.json +++ /dev/null @@ -1 +0,0 @@ -{"base_template": "vuetify-base"} \ No newline at end of file diff --git a/mycustomtemplate/nbconvert_templates/.ipynb_checkpoints/app-checkpoint.html b/mycustomtemplate/nbconvert_templates/.ipynb_checkpoints/app-checkpoint.html deleted file mode 100644 index d9f13b9..0000000 --- a/mycustomtemplate/nbconvert_templates/.ipynb_checkpoints/app-checkpoint.html +++ /dev/null @@ -1,107 +0,0 @@ -{% raw -%} -<div id="app" style="display: none"> - <v-app> - <v-slide-y-transition> - <v-layout v-show="loading" align-center justify-center> - <v-card style="min-width: 600px"> - <v-progress-linear - :indeterminate="loadingPercentage < 0" - v-model="loadingPercentage" - height="15" - ></v-progress-linear> - - <v-card-title primary-title class="py-8"> - <h1>{{ loading_text }}</h1> - </v-card-title> - </v-card> - </v-layout> - </v-slide-y-transition> - <v-slide-y-transition> - <v-layout v-show="!loading"> - <v-navigation-drawer v-model="showNavBar" absolute app> - <v-toolbar color="primary" dark flat> - <v-list> - <v-list-item> - <v-list-item-title class="title"> - Navigation - </v-list-item-title> - </v-list-item> - </v-list> - </v-toolbar> - - <v-divider></v-divider> - <jupyter-widget-mount-point mount-id="content-nav"> - <v-container> - Placeholder for widget with mount id <i>content-nav</i>. - </v-container> - <v-list dense class="pt-0"> - <v-list-item v-for="item in items" :key="item.title" :href="item.url" - target="_blank"> - <v-list-item-action> - <v-icon>{{ item.icon }}</v-icon> - </v-list-item-action> - - <v-list-item-content> - <v-list-item-title>{{ item.title }}</v-list-item-title> - </v-list-item-content> - </v-list-item> - </v-list> - </jupyter-widget-mount-point> - </v-navigation-drawer> - <v-app-bar color="primary" app absolute dark> - <v-app-bar-nav-icon - v-if="!showNavBar" - @click.stop="showNavBar = !showNavBar"> - </v-app-bar-nav-icon> - <v-toolbar-title> - <jupyter-widget-mount-point mount-id="toolbar-title" /> - </v-toolbar-title> - </v-app-bar> - - <v-content> - <jupyter-widget-mount-point mount-id="content-main"> - <v-container> - Placeholder for widget with mount id <i>content-main</i>. - </v-container> - </jupyter-widget-mount-point> - </v-content> - </v-layout> - </v-slide-y-transition> - </v-app> -</div> - -<script> - -var app = new Vue({ - vuetify: new Vuetify({ - icons: { - iconfont: 'md', - }, - }), - el: '#app', - mounted() { - document.querySelector('#app').removeAttribute("style"); - }, - data() { - return { - loading_text: "Loading page", - loadingPercentage: -1, - showNavBar: null, - loading: true, - right: null, - url: 'https://www.w3schools.com/', - items: [ - {icon: "dashboard", title: "voila", url: "https://github.com/voila-dashboards/voila"}, - {icon: "touch_app", title: "voila-vuetify", url: "https://github.com/voila-dashboards/voila-vuetify"}, - {icon: "web", title: "ipvuetify", url: "https://github.com/mariobuikhuizen/ipyvuetify"}, - {icon: "web", title: "vuetify", url: "https://v15.vuetifyjs.com/en/"}, - {icon: "widgets", title: "jupyter widgets", url: "https://github.com/jupyter-widgets/ipywidgets"}, - {icon: "bar_chart", title: "bqplot", url: "https://github.com/bloomberg/bqplot/"}, - ], - title: 'Voila vuetify', - } - } -}); - -</script> -{% endraw -%} diff --git a/mycustomtemplate/nbconvert_templates/.ipynb_checkpoints/app-original-checkpoint.html b/mycustomtemplate/nbconvert_templates/.ipynb_checkpoints/app-original-checkpoint.html deleted file mode 100644 index 6a53288..0000000 --- a/mycustomtemplate/nbconvert_templates/.ipynb_checkpoints/app-original-checkpoint.html +++ /dev/null @@ -1,105 +0,0 @@ -{% raw -%} -<div id="app" style="display: none"> - <v-app> - <v-slide-y-transition> - <v-layout v-show="loading" align-center justify-center> - <v-card style="min-width: 600px"> - <v-progress-linear - :indeterminate="loadingPercentage < 0" - v-model="loadingPercentage" - height="15" - ></v-progress-linear> - - <v-card-title primary-title class="py-8"> - <h1>{{ loading_text }}</h1> - </v-card-title> - </v-card> - </v-layout> - </v-slide-y-transition> - <v-slide-y-transition> - <v-layout v-show="!loading"> - <v-navigation-drawer v-model="showNavBar" absolute app> - <v-toolbar flat> - <v-list> - <v-list-item> - <v-list-item-title class="title"> - Navigation - </v-list-item-title> - </v-list-item> - </v-list> - </v-toolbar> - - <v-divider></v-divider> - <jupyter-widget-mount-point mount-id="content-nav"> - <v-container> - Placeholder for widget with mount id <i>content-nav</i>. - </v-container> - <v-list dense class="pt-0"> - <v-list-item v-for="item in items" :key="item.title" :href="item.url" - target="_blank"> - <v-list-item-action> - <v-icon>{{ item.icon }}</v-icon> - </v-list-item-action> - - <v-list-item-content> - <v-list-item-title>{{ item.title }}</v-list-item-title> - </v-list-item-content> - </v-list-item> - </v-list> - </jupyter-widget-mount-point> - </v-navigation-drawer> - <v-app-bar app absolute> - <v-app-bar-nav-icon - v-if="!showNavBar" - @click.stop="showNavBar = !showNavBar"> - </v-app-bar-nav-icon> - <v-toolbar-title>{{ title }}</v-toolbar-title> - </v-app-bar> - - <v-content> - <jupyter-widget-mount-point mount-id="content-main"> - <v-container> - Placeholder for widget with mount id <i>content-main</i>. - </v-container> - </jupyter-widget-mount-point> - </v-content> - </v-layout> - </v-slide-y-transition> - </v-app> -</div> - -<script> - -var app = new Vue({ - vuetify: new Vuetify({ - icons: { - iconfont: 'md', - }, - }), - el: '#app', - mounted() { - document.querySelector('#app').removeAttribute("style"); - }, - data() { - return { - loading_text: "Loading page", - loadingPercentage: -1, - showNavBar: null, - loading: true, - right: null, - url: 'https://www.w3schools.com/', - items: [ - {icon: "dashboard", title: "voila", url: "https://github.com/voila-dashboards/voila"}, - {icon: "touch_app", title: "voila-vuetify", url: "https://github.com/voila-dashboards/voila-vuetify"}, - {icon: "web", title: "ipvuetify", url: "https://github.com/mariobuikhuizen/ipyvuetify"}, - {icon: "web", title: "vuetify", url: "https://v15.vuetifyjs.com/en/"}, - {icon: "widgets", title: "jupyter widgets", url: "https://github.com/jupyter-widgets/ipywidgets"}, - {icon: "bar_chart", title: "bqplot", url: "https://github.com/bloomberg/bqplot/"}, - ], - title: 'Voila vuetify', - } - } -}); - -</script> -{% endraw -%} \ No newline at end of file diff --git a/voila-gridstack.ipynb b/voila-gridstack.ipynb deleted file mode 100644 index 1e87980..0000000 --- a/voila-gridstack.ipynb +++ /dev/null @@ -1,966 +0,0 @@ -{ - "cells": [ - { - "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": "markdown", - "metadata": {}, - "source": [ - "voila --template=gridstack examples/ --VoilaConfiguration.resources='{\"gridstack\": {\"show_handles\": True}}'" - ] - }, - { - "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": "a9c6b03944ad46baafdf8368105adbb5", - "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": "7d57de7083d149ac9f50b469cb69ba62", - "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": "4b92da7ecfe4405bbabf1274e2a6ec27", - "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": "5cfaa1c93f1e43beb83377cd73ce0e15", - "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": "6495dfa3b7b14c29b43472853c6e717a", - "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." - ] - }, - { - "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": null, - "metadata": { - "collapsed": true, - "extensions": { - "jupyter_dashboards": { - "version": 1, - "views": { - "grid_default": { - "hidden": true - }, - "report_default": {} - } - } - }, - "jupyter": { - "outputs_hidden": true - } - }, - "outputs": [], - "source": [] - } - ], - "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.5" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} -- GitLab