├── notebooks ├── solutions │ ├── bqplot-as-control │ │ ├── box-widget.py │ │ ├── update_line.py │ │ └── bad-pass-pass1.py │ ├── bad-password-generator │ │ ├── bad-pass-pass1-observe.py │ │ ├── bad-pass-pass1-passgen.py │ │ └── bad-pass-pass1-widgets.py │ ├── interact-basic-list │ │ ├── reverse-text.py │ │ ├── plot-function.py │ │ ├── widgets-in-a-box.py │ │ └── sample-linked-widgets.py │ ├── container-exercises │ │ ├── password-ex3ii.py │ │ ├── password-ex4.py │ │ ├── password-ex3i.py │ │ └── password-ex2.py │ └── bad-password-final │ │ ├── bad-pass-gui.py │ │ └── bad-pass-model.py ├── README.md ├── images │ ├── flexbox.png │ ├── widgets.PNG │ ├── WidgetArch.png │ ├── inputoutput.PNG │ ├── pass-gen-demo.gif │ ├── Binary_Star_Sim.png │ ├── ParallelKernels.png │ ├── WidgetModelView.png │ ├── bad-pass-gen-v1.png │ ├── plot-as-control.gif │ ├── VizInteractCompute.png │ ├── MultilanguageKernels.png │ ├── 05-container-exercises-ex4.png │ ├── container-exercises-special-chars.png │ ├── container-exercises1-final-layout.png │ ├── container-exercises2-final-layout.png │ ├── flex-container.svg │ ├── align-self.svg │ ├── flex-wrap.svg │ ├── flex-items.svg │ ├── flex-grow.svg │ ├── flex-direction1.svg │ ├── justify-content.svg │ ├── order-2.svg │ ├── align-items.svg │ ├── state_sync.svg │ ├── align-content.svg │ ├── assoc.svg │ ├── transport.svg │ ├── busy.svg │ ├── WidgetArch.graffle │ └── execute.svg ├── _version_compatibility_checks │ ├── README.md │ ├── ipyvolume-trial.ipynb │ ├── bqplot-trial.ipynb │ └── pythreejs-trial.ipynb ├── 10.05-embed.ipynb ├── 10.00-More widget libraries.ipynb ├── 10.06-widget-library-exercises.ipynb ├── 09.00-Widget Events 2.ipynb ├── password_generator_example │ ├── gui.py │ ├── Widget example.ipynb │ └── model.py ├── HTML-widget.ipynb ├── 01.00-overview.ipynb ├── index.ipynb ├── 09.01-Widget Events 2 -- bad password generator, version 1.ipynb ├── 05.00-interact and widget basics exercises.ipynb ├── 00.00-introduction.ipynb ├── Flow.svg ├── 10.04-ipyleaflet.ipynb ├── 10.03-ipyvolume.ipynb ├── 09.02-Widget Events 2 -- Separating Concerns.ipynb ├── 07.00-container-exercises.ipynb └── 10.07-bqplot--A plot as a control in a widget.ipynb ├── .gitignore ├── binder └── environment.yml ├── test_kernel_name.py ├── tools ├── README.md ├── licenses │ └── LICENSE-CODE ├── add_navigation.py ├── kernel_names.py ├── generate_contents.py └── dependency_finder.py ├── outline.md ├── outline-jupytercon.md ├── LICENSE ├── install_check.py └── README.md /notebooks/solutions/bqplot-as-control/box-widget.py: -------------------------------------------------------------------------------- 1 | box = widgets.HBox(children=[amplitude_control, result]) 2 | box 3 | -------------------------------------------------------------------------------- /notebooks/README.md: -------------------------------------------------------------------------------- 1 | Notebooks for tutorial on Jupyter widgets at SciPy 2017. 2 | 3 | Start at [index.ipynb](index.ipynb). 4 | -------------------------------------------------------------------------------- /notebooks/solutions/bad-password-generator/bad-pass-pass1-observe.py: -------------------------------------------------------------------------------- 1 | password_length.observe(calculate_password, names='value') 2 | -------------------------------------------------------------------------------- /notebooks/images/flexbox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwcraig/scipy2017-jupyter-widgets-tutorial/HEAD/notebooks/images/flexbox.png -------------------------------------------------------------------------------- /notebooks/images/widgets.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwcraig/scipy2017-jupyter-widgets-tutorial/HEAD/notebooks/images/widgets.PNG -------------------------------------------------------------------------------- /notebooks/solutions/interact-basic-list/reverse-text.py: -------------------------------------------------------------------------------- 1 | def reverse(x): 2 | return x[::-1] 3 | 4 | interact(reverse, x='Hello') 5 | -------------------------------------------------------------------------------- /notebooks/images/WidgetArch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwcraig/scipy2017-jupyter-widgets-tutorial/HEAD/notebooks/images/WidgetArch.png -------------------------------------------------------------------------------- /notebooks/images/inputoutput.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwcraig/scipy2017-jupyter-widgets-tutorial/HEAD/notebooks/images/inputoutput.PNG -------------------------------------------------------------------------------- /notebooks/images/pass-gen-demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwcraig/scipy2017-jupyter-widgets-tutorial/HEAD/notebooks/images/pass-gen-demo.gif -------------------------------------------------------------------------------- /notebooks/images/Binary_Star_Sim.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwcraig/scipy2017-jupyter-widgets-tutorial/HEAD/notebooks/images/Binary_Star_Sim.png -------------------------------------------------------------------------------- /notebooks/images/ParallelKernels.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwcraig/scipy2017-jupyter-widgets-tutorial/HEAD/notebooks/images/ParallelKernels.png -------------------------------------------------------------------------------- /notebooks/images/WidgetModelView.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwcraig/scipy2017-jupyter-widgets-tutorial/HEAD/notebooks/images/WidgetModelView.png -------------------------------------------------------------------------------- /notebooks/images/bad-pass-gen-v1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwcraig/scipy2017-jupyter-widgets-tutorial/HEAD/notebooks/images/bad-pass-gen-v1.png -------------------------------------------------------------------------------- /notebooks/images/plot-as-control.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwcraig/scipy2017-jupyter-widgets-tutorial/HEAD/notebooks/images/plot-as-control.gif -------------------------------------------------------------------------------- /notebooks/images/VizInteractCompute.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwcraig/scipy2017-jupyter-widgets-tutorial/HEAD/notebooks/images/VizInteractCompute.png -------------------------------------------------------------------------------- /notebooks/images/MultilanguageKernels.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwcraig/scipy2017-jupyter-widgets-tutorial/HEAD/notebooks/images/MultilanguageKernels.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ipywidget-example-notebooks 2 | ipywidgets-examples-v2 3 | .ipynb_checkpoints/ 4 | notebooks/__pycache__ 5 | .cache 6 | .pytest_cache 7 | __pycache__ 8 | -------------------------------------------------------------------------------- /notebooks/images/05-container-exercises-ex4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwcraig/scipy2017-jupyter-widgets-tutorial/HEAD/notebooks/images/05-container-exercises-ex4.png -------------------------------------------------------------------------------- /notebooks/solutions/bqplot-as-control/update_line.py: -------------------------------------------------------------------------------- 1 | def update_line(change): 2 | new_x, new_y = fourier_series(change['new']) 3 | line.x = new_x 4 | line.y = new_y 5 | -------------------------------------------------------------------------------- /notebooks/images/container-exercises-special-chars.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwcraig/scipy2017-jupyter-widgets-tutorial/HEAD/notebooks/images/container-exercises-special-chars.png -------------------------------------------------------------------------------- /notebooks/images/container-exercises1-final-layout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwcraig/scipy2017-jupyter-widgets-tutorial/HEAD/notebooks/images/container-exercises1-final-layout.png -------------------------------------------------------------------------------- /notebooks/images/container-exercises2-final-layout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwcraig/scipy2017-jupyter-widgets-tutorial/HEAD/notebooks/images/container-exercises2-final-layout.png -------------------------------------------------------------------------------- /notebooks/solutions/interact-basic-list/plot-function.py: -------------------------------------------------------------------------------- 1 | @interact(k=(0.5, 2), p=(0, 2 * np.pi)) 2 | def f(k, p): 3 | x = np.linspace(0, 4 * np.pi) 4 | y = np.sin(k*x + p) 5 | plt.plot(x, y) 6 | -------------------------------------------------------------------------------- /notebooks/solutions/container-exercises/password-ex3ii.py: -------------------------------------------------------------------------------- 1 | numbers = widgets.Checkbox(description='Include numbers in password', 2 | style={'description_width': 'initial'}) 3 | numbers 4 | -------------------------------------------------------------------------------- /binder/environment.yml: -------------------------------------------------------------------------------- 1 | channels: 2 | - conda-forge 3 | - defaults 4 | dependencies: 5 | - numpy 6 | - scikit-image 7 | - scipy 8 | - bqplot 9 | - pythreejs 10 | - ipyvolume 11 | - gdal 12 | - krb5 13 | - ipyleaflet 14 | -------------------------------------------------------------------------------- /notebooks/solutions/interact-basic-list/widgets-in-a-box.py: -------------------------------------------------------------------------------- 1 | application = widgets.Textarea(description='Fill this box') 2 | b = widgets.Button(description='Click me') 3 | v = widgets.Valid(description='The text is') 4 | 5 | vbox = widgets.VBox(children=[application, b, v]) 6 | vbox 7 | -------------------------------------------------------------------------------- /notebooks/solutions/interact-basic-list/sample-linked-widgets.py: -------------------------------------------------------------------------------- 1 | # This example links two selection widgets 2 | 3 | options = ['yes', 'no', 'maybe'] 4 | 5 | drop = widgets.Dropdown(options=options) 6 | radio = widgets.RadioButtons(options=options) 7 | widgets.jslink((drop, 'index'), (radio, 'index')) 8 | display(drop, radio) 9 | -------------------------------------------------------------------------------- /notebooks/_version_compatibility_checks/README.md: -------------------------------------------------------------------------------- 1 | These notebooks contain short examples from each of the larger ipywidget-based packages to be demonstrated during the tutorial: 2 | 3 | + bqplot 4 | + ipyvolume 5 | + ipyleaflet 6 | + pythreejs 7 | 8 | They are **NOT** intended to be the primary content on these libraries for the tutorial attendees. 9 | -------------------------------------------------------------------------------- /notebooks/solutions/bad-password-generator/bad-pass-pass1-passgen.py: -------------------------------------------------------------------------------- 1 | def calculate_password(change): 2 | import string 3 | from secrets import choice 4 | length = change.new 5 | # Generate a list of random letters of the correct length. 6 | password = ''.join(choice(string.ascii_letters) for _ in range(length)) 7 | # Add a line below to set the value of the widget password_text 8 | password_text.value = password 9 | -------------------------------------------------------------------------------- /test_kernel_name.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | from tools.kernel_names import get_kernel_name 4 | 5 | TUTORIAL_KERNEL_NAME = 'widget-tutorial' 6 | NOTEBOOK_DIRECTORY = Path(__file__) / '..' / 'notebooks' 7 | 8 | 9 | def test_kernel_name(): 10 | notebooks = NOTEBOOK_DIRECTORY.glob('**/*.ipynb') 11 | for notebook in notebooks: 12 | kernel_name = get_kernel_name(str(notebook)) 13 | assert kernel_name == TUTORIAL_KERNEL_NAME 14 | -------------------------------------------------------------------------------- /tools/README.md: -------------------------------------------------------------------------------- 1 | # Tools for managing a collection of notebooks 2 | 3 | ## Credit and license 4 | 5 | Some of these tools (and all of the idea for them) is from [Jake VanerPlas](https://jakevdp.github.io/)'s [Python Data Science Handbook](https://github.com/jakevdp/PythonDataScienceHandbook). 6 | 7 | The files `add_navigation.py` and `generate_contents.py` are modified from the same files in the Handbook, whose code license, a copy of which is in [licenses/LICENSE-CODE](licenses/LICENSE-CODE). 8 | -------------------------------------------------------------------------------- /notebooks/solutions/container-exercises/password-ex4.py: -------------------------------------------------------------------------------- 1 | words = widgets.Label('The generated password is:') 2 | 3 | vbox = widgets.VBox() 4 | 5 | # The border is set here just to make it easier to see the position of 6 | # the children with respect to the box. 7 | vbox.layout.border = '2px solid grey' 8 | vbox.layout.height = '250px' 9 | 10 | # Added lines: 11 | vbox.layout.justify_content = 'space-around' 12 | vbox.layout.align_items = 'flex-start' 13 | # Don't forget to add the children... 14 | vbox.children = [words, better_toggles, numbers] 15 | 16 | vbox 17 | -------------------------------------------------------------------------------- /notebooks/solutions/bqplot-as-control/bad-pass-pass1.py: -------------------------------------------------------------------------------- 1 | helpful_title = widgets.HTML('Generated password is:') 2 | password_text = widgets.HTML('No password yet', placeholder='No password generated yet') 3 | password_text.layout.margin = '0 0 0 20px' 4 | password_length = widgets.IntSlider(description='Length of password', 5 | min=8, max=20, 6 | style={'description_width': 'initial'}) 7 | 8 | password_widget = widgets.VBox(children=[helpful_title, password_text, password_length]) 9 | password_widget 10 | -------------------------------------------------------------------------------- /notebooks/solutions/bad-password-generator/bad-pass-pass1-widgets.py: -------------------------------------------------------------------------------- 1 | helpful_title = widgets.HTML('Generated password is:') 2 | password_text = widgets.HTML('No password yet', placeholder='No password generated yet') 3 | password_text.layout.margin = '0 0 0 20px' 4 | password_length = widgets.IntSlider(description='Length of password', 5 | min=8, max=20, 6 | style={'description_width': 'initial'}) 7 | 8 | password_widget = widgets.VBox(children=[helpful_title, password_text, password_length]) 9 | password_widget 10 | -------------------------------------------------------------------------------- /notebooks/solutions/container-exercises/password-ex3i.py: -------------------------------------------------------------------------------- 1 | vbox_left_layout = widgets.Layout(align_items='flex-start') 2 | 3 | label = widgets.Label('Choose special characters to include') 4 | toggles = widgets.ToggleButtons(description='', 5 | options=[',./;[', '!@#~%', '^&*()'], 6 | style={'description_width': 'initial'}) 7 | 8 | # Set the margins to control the indentation. 9 | # The order is top right bottom left 10 | toggles.layout.margin = '0 0 0 20px' 11 | 12 | better_toggles = widgets.VBox([label, toggles]) 13 | better_toggles.layout = vbox_left_layout 14 | better_toggles 15 | -------------------------------------------------------------------------------- /notebooks/10.05-embed.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Embedding widgets " 8 | ] 9 | } 10 | ], 11 | "metadata": { 12 | "kernelspec": { 13 | "display_name": "widgets-tutorial", 14 | "language": "python", 15 | "name": "widgets-tutorial" 16 | }, 17 | "language_info": { 18 | "codemirror_mode": { 19 | "name": "ipython", 20 | "version": 3 21 | }, 22 | "file_extension": ".py", 23 | "mimetype": "text/x-python", 24 | "name": "python", 25 | "nbconvert_exporter": "python", 26 | "pygments_lexer": "ipython3", 27 | "version": "3.6.1" 28 | } 29 | }, 30 | "nbformat": 4, 31 | "nbformat_minor": 2 32 | } 33 | -------------------------------------------------------------------------------- /notebooks/solutions/container-exercises/password-ex2.py: -------------------------------------------------------------------------------- 1 | numbers = widgets.Checkbox(description='Include numbers in password') 2 | words = widgets.Label('The generated password is:') 3 | toggles = widgets.ToggleButtons(description='Type of special characters to include', 4 | options=[',./;[', '!@#~%', '^&*()'], 5 | style={'description_width': 'initial'}) 6 | vbox = widgets.VBox() 7 | 8 | # The border is set here just to make it easier to see the position of 9 | # the children with respect to the box. 10 | vbox.layout.border = '2px solid grey' 11 | vbox.layout.height = '250px' 12 | 13 | # Added lines: 14 | vbox.layout.justify_content = 'space-around' 15 | vbox.layout.align_items = 'flex-start' 16 | # Don't forget to add the children... 17 | vbox.children = [words, toggles, numbers] 18 | 19 | vbox 20 | -------------------------------------------------------------------------------- /notebooks/10.00-More widget libraries.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Building blocks II: more widget libraries" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "There are several widget libraries beyond the core `ipywidgets`. The libraries include plotting tools (bqplot and ipyvolume), an interface to three dimensional renderings (pythreejs), mapping (ipyleaflet) and more." 15 | ] 16 | } 17 | ], 18 | "metadata": { 19 | "kernelspec": { 20 | "display_name": "widgets-tutorial", 21 | "language": "python", 22 | "name": "widgets-tutorial" 23 | }, 24 | "language_info": { 25 | "codemirror_mode": { 26 | "name": "ipython", 27 | "version": 3 28 | }, 29 | "file_extension": ".py", 30 | "mimetype": "text/x-python", 31 | "name": "python", 32 | "nbconvert_exporter": "python", 33 | "pygments_lexer": "ipython3", 34 | "version": "3.6.5" 35 | } 36 | }, 37 | "nbformat": 4, 38 | "nbformat_minor": 2 39 | } 40 | -------------------------------------------------------------------------------- /tools/licenses/LICENSE-CODE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Jacob VanderPlas 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /notebooks/images/flex-container.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | container 19 | 20 | -------------------------------------------------------------------------------- /outline.md: -------------------------------------------------------------------------------- 1 | # Outline 2 | 3 | 1. 0:00 Widget overview: 4 | 1. Overview/introduction (10 min) 5 | 2. `@interact` notebook and example (10 min) 6 | 3. Widget Basics notebook and Widget List, which includes Output widget (20 min) 7 | 4. Exercises (20 min) 8 | 1. interact extension 9 | 2. widget basics extension 10 | 5. Break (10 min) 11 | 2. start 1:10 Widget styling: 12 | 1. Talk (15 min) 13 | 1. Add some basic css 14 | 2. Notebooks (15 min) 15 | 3. "stretch" break (5 min) (please don't leave room) 16 | 3. start 1:45 Widget events 17 | 1. Events: observe, link, traitlets (15 min) 18 | 2. Exercises (30 min total) 19 | 3. Break (10 min) 20 | 4. start 2:40 More widget libraries 21 | 1. bqplot (15 min) 22 | 2. pythreejs (5 min) 23 | 3. ipyvolume (5 min) 24 | 4. ipyleaflet (5 min) 25 | 5. ipyfits, ipyaladdin, ipywwt (5 min) 26 | 6. two exercises: (10 min) 27 | 1. Link one of these with a slider or dropdown or.... 28 | 2. bqplot as control (fourier series or Sylvain's regression example) 29 | 5. Widgets outside the context of the notebook (20 min) 30 | 1. Embedding widgets in static web pages 31 | 2. Jupyter widget sphinx extension 32 | 3. ipywidget-server 33 | 6. starts 3:45 Flexible/Q&A time 34 | -------------------------------------------------------------------------------- /outline-jupytercon.md: -------------------------------------------------------------------------------- 1 | 1. *0:00* Widget overview: **Jason** 2 | 1. Overview/introduction (10 min) 3 | 1. Add `@interact` notebook and example (10 min) 4 | 1. Widget Basics notebook and Widget List, which includes Output widget (20 min) 5 | 1. Exercises (20 min) 6 | 1. interact extension 7 | 2. widget basics extension 8 | 3. solve install problems here 9 | 1. Break (10 min) 10 | 2. *start 10:10* Widget styling: **Sylvain** 11 | 1. Talk (15 min) 12 | 1. Notebooks (15 min) 13 | 2. "stretch" break (5 min) (please don't leave room) 14 | 3. *start 10:45* Widget events 15 | 1. Events: observe, link, traitlets (15 min) **Jason** 16 | 2. Exercises (15 min total) **Sylvain** 17 | 3. Break (10 min) 18 | 4. *start 11:25* More widget libraries 19 | 1. bqplot (5 min) **Sylvain** 20 | 3. pythreejs (5 min) **Sylvain** 21 | 5. **users do not install** game controller (5 min) **Sylvain** 22 | 2. ipyvolume (5 min) **Maarten** 23 | 4. ipyleaflet (5 min) **Jason** 24 | 5. Embedding (5 min) **Jason** 25 | 6. two exercises: (0 min) (No time?) 26 | 1. Link one of these with a slider or dropdown or.... **Matt** 27 | 2. bqplot as control (fourier series or Sylvain's regression example) 28 | 6. *starts 12:10* Flexible/Q&A time 29 | -------------------------------------------------------------------------------- /notebooks/10.06-widget-library-exercises.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Widget library exercise: Link some widgets up" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "We have covered several widget libraries. The idea in this exercise is that you should one of the complex widgets (bqplot, iyvolume, ipyleaflet etc) and combine it with one or more controls from the core widgets in `ipywidgets`.\n", 15 | "\n", 16 | "Anything is fine, so take a few minutes to play around." 17 | ] 18 | }, 19 | { 20 | "cell_type": "code", 21 | "execution_count": null, 22 | "metadata": { 23 | "collapsed": true 24 | }, 25 | "outputs": [], 26 | "source": [] 27 | } 28 | ], 29 | "metadata": { 30 | "kernelspec": { 31 | "display_name": "widgets-tutorial", 32 | "language": "python", 33 | "name": "widgets-tutorial" 34 | }, 35 | "language_info": { 36 | "codemirror_mode": { 37 | "name": "ipython", 38 | "version": 3 39 | }, 40 | "file_extension": ".py", 41 | "mimetype": "text/x-python", 42 | "name": "python", 43 | "nbconvert_exporter": "python", 44 | "pygments_lexer": "ipython3", 45 | "version": "3.6.5" 46 | } 47 | }, 48 | "nbformat": 4, 49 | "nbformat_minor": 2 50 | } 51 | -------------------------------------------------------------------------------- /notebooks/images/align-self.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | 11 | flex-end 12 | flex-start 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /notebooks/09.00-Widget Events 2.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Three approaches to events" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "The next series of notebooks presents three ways of handling widget events. In each of the notebooks we'll construct the same simple password generator.\n", 15 | "\n", 16 | "The first uses a mix of functions and global variables, and approach like that in the notebook introducing widget events.\n", 17 | "\n", 18 | "The second separates the logic of the password generation from the password generation user interface.\n", 19 | "\n", 20 | "The third takes that separation a step further by using a class for the user interface, a class for the logic, and a class to bring them together." 21 | ] 22 | } 23 | ], 24 | "metadata": { 25 | "kernelspec": { 26 | "display_name": "widgets-tutorial", 27 | "language": "python", 28 | "name": "widgets-tutorial" 29 | }, 30 | "language_info": { 31 | "codemirror_mode": { 32 | "name": "ipython", 33 | "version": 3 34 | }, 35 | "file_extension": ".py", 36 | "mimetype": "text/x-python", 37 | "name": "python", 38 | "nbconvert_exporter": "python", 39 | "pygments_lexer": "ipython3", 40 | "version": "3.6.5" 41 | } 42 | }, 43 | "nbformat": 4, 44 | "nbformat_minor": 2 45 | } 46 | -------------------------------------------------------------------------------- /notebooks/_version_compatibility_checks/ipyvolume-trial.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [ 8 | { 9 | "data": { 10 | "application/vnd.jupyter.widget-view+json": { 11 | "model_id": "cd4e9b74358d42ffbb78dc7f2d64276a", 12 | "version_major": "2", 13 | "version_minor": "0" 14 | }, 15 | "text/plain": [ 16 | "A Jupyter Widget" 17 | ] 18 | }, 19 | "metadata": {}, 20 | "output_type": "display_data" 21 | } 22 | ], 23 | "source": [ 24 | "import ipyvolume\n", 25 | "import numpy as np\n", 26 | "x, y, z = np.random.random((3, 10000))\n", 27 | "ipyvolume.quickscatter(x, y, z, size=1, marker=\"sphere\")" 28 | ] 29 | }, 30 | { 31 | "cell_type": "code", 32 | "execution_count": null, 33 | "metadata": { 34 | "collapsed": true 35 | }, 36 | "outputs": [], 37 | "source": [] 38 | } 39 | ], 40 | "metadata": { 41 | "kernelspec": { 42 | "display_name": "widgets-tutorial", 43 | "language": "python", 44 | "name": "widgets-tutorial" 45 | }, 46 | "language_info": { 47 | "codemirror_mode": { 48 | "name": "ipython", 49 | "version": 3 50 | }, 51 | "file_extension": ".py", 52 | "mimetype": "text/x-python", 53 | "name": "python", 54 | "nbconvert_exporter": "python", 55 | "pygments_lexer": "ipython3", 56 | "version": "3.6.1" 57 | } 58 | }, 59 | "nbformat": 4, 60 | "nbformat_minor": 2 61 | } 62 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2017, Project Jupyter Contributors 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /notebooks/images/flex-wrap.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /notebooks/images/flex-items.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | items 35 | 36 | -------------------------------------------------------------------------------- /notebooks/_version_compatibility_checks/bqplot-trial.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "from bqplot import pyplot as plt\n", 10 | "import numpy as np" 11 | ] 12 | }, 13 | { 14 | "cell_type": "code", 15 | "execution_count": 3, 16 | "metadata": {}, 17 | "outputs": [ 18 | { 19 | "data": { 20 | "application/vnd.jupyter.widget-view+json": { 21 | "model_id": "b469883f366b4743a1b5b47c01d0b9e2", 22 | "version_major": "2", 23 | "version_minor": "0" 24 | }, 25 | "text/plain": [ 26 | "A Jupyter Widget" 27 | ] 28 | }, 29 | "metadata": {}, 30 | "output_type": "display_data" 31 | } 32 | ], 33 | "source": [ 34 | "from bqplot import pyplot as plt\n", 35 | "import numpy as np\n", 36 | "\n", 37 | "x = np.linspace(0, 4 * np.pi, 100)\n", 38 | "y = np.sin(x)\n", 39 | "plt.plot(x, y)\n", 40 | "plt.show()" 41 | ] 42 | }, 43 | { 44 | "cell_type": "code", 45 | "execution_count": null, 46 | "metadata": {}, 47 | "outputs": [], 48 | "source": [ 49 | "y" 50 | ] 51 | }, 52 | { 53 | "cell_type": "code", 54 | "execution_count": null, 55 | "metadata": {}, 56 | "outputs": [], 57 | "source": [] 58 | } 59 | ], 60 | "metadata": { 61 | "kernelspec": { 62 | "display_name": "widgets-tutorial", 63 | "language": "python", 64 | "name": "widgets-tutorial" 65 | }, 66 | "language_info": { 67 | "codemirror_mode": { 68 | "name": "ipython", 69 | "version": 3 70 | }, 71 | "file_extension": ".py", 72 | "mimetype": "text/x-python", 73 | "name": "python", 74 | "nbconvert_exporter": "python", 75 | "pygments_lexer": "ipython3", 76 | "version": "3.6.1" 77 | } 78 | }, 79 | "nbformat": 4, 80 | "nbformat_minor": 2 81 | } 82 | -------------------------------------------------------------------------------- /notebooks/images/flex-grow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | 1 11 | 1 12 | 1 13 | 14 | 15 | 16 | 17 | 1 18 | 2 19 | 1 20 | 21 | -------------------------------------------------------------------------------- /notebooks/solutions/bad-password-final/bad-pass-gui.py: -------------------------------------------------------------------------------- 1 | from ipywidgets import widgets 2 | 3 | class PassGenGUI(widgets.VBox): 4 | def __init__(self): 5 | super(PassGenGUI, self).__init__() 6 | 7 | self._helpful_title = widgets.HTML('Generated password is:') 8 | self._password_text = widgets.HTML('', placeholder='No password generated yet') 9 | self._password_text.layout.margin = '0 0 0 20px' 10 | self._password_length = widgets.IntSlider(description='Length of password', 11 | min=8, max=20, 12 | style={'description_width': 'initial'}) 13 | 14 | self._numbers = widgets.Checkbox(description='Include numbers in password', 15 | style={'description_width': 'initial'}) 16 | 17 | self._label = widgets.Label('Choose special characters to include') 18 | self._toggles = widgets.ToggleButtons(description='', 19 | options=[',./;[', '!@#~%', '^&*()'], 20 | style={'description_width': 'initial'}) 21 | 22 | # Set the margins to control the indentation. 23 | # The order is top right bottom left 24 | self._toggles.layout.margin = '0 0 0 20px' 25 | 26 | children = [self._helpful_title, self._password_text, self._password_length, 27 | self._label, self._toggles, self._numbers] 28 | self.children = children 29 | 30 | self.model = PassGen() 31 | 32 | traitlets.link((self.model, 'password'), (self._password_text, 'value')) 33 | traitlets.link((self.model, 'length'), (self._password_length, 'value')) 34 | traitlets.link((self.model, 'special_character_groups'), (self._toggles, 'value')) 35 | traitlets.link((self.model, 'include_numbers'), (self._numbers, 'value')) 36 | 37 | @property 38 | def value(self): 39 | return self._password_text.value 40 | -------------------------------------------------------------------------------- /notebooks/password_generator_example/gui.py: -------------------------------------------------------------------------------- 1 | from ipywidgets import widgets 2 | from model import PassGen 3 | import traitlets 4 | 5 | class PassGenGUI(widgets.VBox): 6 | def __init__(self): 7 | super(PassGenGUI, self).__init__() 8 | 9 | self._helpful_title = widgets.HTML('Generated password is:') 10 | self._password_text = widgets.HTML('', placeholder='No password generated yet') 11 | self._password_text.layout.margin = '0 0 0 20px' 12 | self._password_length = widgets.IntSlider(description='Length of password', 13 | min=8, max=20, 14 | style={'description_width': 'initial'}) 15 | 16 | self._numbers = widgets.Checkbox(description='Include numbers in password', 17 | style={'description_width': 'initial'}) 18 | 19 | self._label = widgets.Label('Choose special characters to include') 20 | self._toggles = widgets.ToggleButtons(description='', 21 | options=[',./;[', '!@#~%', '^&*()'], 22 | style={'description_width': 'initial'}) 23 | 24 | # Set the margins to control the indentation. 25 | # The order is top right bottom left 26 | self._toggles.layout.margin = '0 0 0 20px' 27 | 28 | children = [self._helpful_title, self._password_text, self._password_length, 29 | self._label, self._toggles, self._numbers] 30 | self.children = children 31 | 32 | self.model = PassGen() 33 | 34 | traitlets.link((self.model, 'password'), (self._password_text, 'value')) 35 | traitlets.link((self.model, 'length'), (self._password_length, 'value')) 36 | traitlets.link((self.model, 'special_character_groups'), (self._toggles, 'value')) 37 | traitlets.link((self.model, 'include_numbers'), (self._numbers, 'value')) 38 | 39 | @property 40 | def value(self): 41 | return self._password_text.value 42 | -------------------------------------------------------------------------------- /notebooks/password_generator_example/Widget example.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Pure-python widget example\n", 8 | "\n", 9 | "This notebook demonstrates an example of a pure-python widget that is composed of several subwidgets. Most of the code is (deliberately) not in the notebook itself.\n", 10 | "\n", 11 | "The user interface is laid out in the file `gui.py`, and the model, which represents the state of the interface and which responds to changes in the interface made by the user, is in `model.py`.\n", 12 | "\n", 13 | "The widget is a simple password generator that allows you to change the length of the password, which group of special characters it includes, and whether it includes digits. \n", 14 | "\n", 15 | "The styling is deliberately simple, and little attempt has been made to make the widget look good. " 16 | ] 17 | }, 18 | { 19 | "cell_type": "code", 20 | "execution_count": null, 21 | "metadata": { 22 | "collapsed": true 23 | }, 24 | "outputs": [], 25 | "source": [ 26 | "from gui import PassGenGUI" 27 | ] 28 | }, 29 | { 30 | "cell_type": "code", 31 | "execution_count": null, 32 | "metadata": {}, 33 | "outputs": [], 34 | "source": [ 35 | "g = PassGenGUI()\n", 36 | "g" 37 | ] 38 | } 39 | ], 40 | "metadata": { 41 | "anaconda-cloud": {}, 42 | "kernelspec": { 43 | "display_name": "widgets-tutorial", 44 | "language": "python", 45 | "name": "widgets-tutorial" 46 | }, 47 | "language_info": { 48 | "codemirror_mode": { 49 | "name": "ipython", 50 | "version": 3 51 | }, 52 | "file_extension": ".py", 53 | "mimetype": "text/x-python", 54 | "name": "python", 55 | "nbconvert_exporter": "python", 56 | "pygments_lexer": "ipython3", 57 | "version": "3.6.1" 58 | }, 59 | "widgets": { 60 | "state": { 61 | "813b98e764ce4cae853f60dce54e9920": { 62 | "views": [ 63 | { 64 | "cell_index": 2 65 | } 66 | ] 67 | } 68 | }, 69 | "version": "1.2.0" 70 | } 71 | }, 72 | "nbformat": 4, 73 | "nbformat_minor": 1 74 | } 75 | -------------------------------------------------------------------------------- /tools/add_navigation.py: -------------------------------------------------------------------------------- 1 | import os 2 | import itertools 3 | 4 | import nbformat 5 | from nbformat.v4.nbbase import new_markdown_cell 6 | 7 | from generate_contents import (NOTEBOOK_DIR, REG, 8 | iter_notebooks, get_notebook_title, 9 | is_title) 10 | 11 | 12 | def prev_this_next(it): 13 | a, b, c = itertools.tee(it, 3) 14 | next(c) 15 | return zip(itertools.chain([None], a), b, itertools.chain(c, [None])) 16 | 17 | 18 | PREV_TEMPLATE = "< [{title}]({url}) " 19 | CONTENTS = "| [Contents](Index.ipynb) |" 20 | NEXT_TEMPLATE = " [{title}]({url}) >" 21 | NAV_COMMENT = "\n" 22 | 23 | 24 | def iter_navbars(): 25 | for prev_nb, nb, next_nb in prev_this_next(iter_notebooks(NOTEBOOK_DIR)): 26 | navbar = NAV_COMMENT 27 | if prev_nb: 28 | navbar += PREV_TEMPLATE.format(title=get_notebook_title(prev_nb), 29 | url=prev_nb) 30 | navbar += CONTENTS 31 | if next_nb: 32 | navbar += NEXT_TEMPLATE.format(title=get_notebook_title(next_nb), 33 | url=next_nb) 34 | yield os.path.join(NOTEBOOK_DIR, nb), navbar 35 | 36 | 37 | def write_navbars(): 38 | for nb_name, navbar in iter_navbars(): 39 | nb = nbformat.read(nb_name, as_version=4) 40 | nb_file = os.path.basename(nb_name) 41 | is_comment = lambda cell: cell.source.startswith(NAV_COMMENT) 42 | 43 | for idx, cell in enumerate(nb.cells): 44 | if is_comment(cell): 45 | print("- amending navbar for {0}".format(nb_file)) 46 | cell.source = navbar 47 | break 48 | elif is_title(cell): 49 | print("- inserting navbar for {0}".format(nb_file)) 50 | nb.cells.insert(idx, new_markdown_cell(source=navbar)) 51 | break 52 | 53 | if is_comment(nb.cells[-1]): 54 | nb.cells[-1].source = navbar 55 | else: 56 | nb.cells.append(new_markdown_cell(source=navbar)) 57 | nbformat.write(nb, nb_name) 58 | 59 | 60 | if __name__ == '__main__': 61 | write_navbars() 62 | -------------------------------------------------------------------------------- /notebooks/solutions/bad-password-final/bad-pass-model.py: -------------------------------------------------------------------------------- 1 | import string 2 | import random 3 | 4 | import traitlets 5 | 6 | SPECIAL_GROUPS = [',./;[', '!@#~%', '^&*()'] 7 | 8 | 9 | class PassGen(traitlets.HasTraits): 10 | """ 11 | Class to represent state of the password generator and handle generation 12 | of password. 13 | """ 14 | length = traitlets.Integer() 15 | password = traitlets.Unicode("password") 16 | 17 | # ↓↓↓↓↓↓↓↓ New traitlets ↓↓↓↓↓↓↓↓ 18 | include_numbers = traitlets.Bool() 19 | special_character_groups = traitlets.Enum(SPECIAL_GROUPS, 20 | default_value=SPECIAL_GROUPS[0]) 21 | 22 | def __init__(self): 23 | super(PassGen, self).__init__() 24 | pass 25 | 26 | @traitlets.observe('length', 'include_numbers', 'special_character_groups') 27 | def generate_password(self, change): 28 | """ 29 | Generate a password of the desired length including the user's chosen 30 | set of special characters and, if desired, including some numerals. 31 | """ 32 | 33 | # Generate an initial password composed only of letters. 34 | new_pass = [] 35 | for _ in range(self.length): 36 | new_pass.append(random.choice(string.ascii_letters)) 37 | 38 | # Generate a list of indices for choosing which characters in the 39 | # initial password to replace, then shuffle it. We'll pop 40 | # elements off the list as we need them. 41 | order_for_replacements = list(range(self.length)) 42 | random.shuffle(order_for_replacements) 43 | 44 | # Replace some of the letters with special characters 45 | n_special = random.randint(1, 3) 46 | for _ in range(n_special): 47 | loc = order_for_replacements.pop(0) 48 | new_pass[loc] = random.choice(self.special_character_groups) 49 | 50 | if self.include_numbers: 51 | # Number of digits to include. 52 | n_digits = random.randint(1, 3) 53 | for _ in range(n_digits): 54 | loc = order_for_replacements.pop(0) 55 | new_pass[loc] = random.choice(string.digits) 56 | 57 | self.password = ''.join(new_pass) 58 | -------------------------------------------------------------------------------- /notebooks/password_generator_example/model.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function, division 2 | 3 | import string 4 | import random 5 | 6 | import traitlets 7 | 8 | SPECIAL_GROUPS = [',./;[', '!@#~%', '^&*()'] 9 | 10 | 11 | class PassGen(traitlets.HasTraits): 12 | """ 13 | Class to represent state of the password generator and handle generation 14 | of password. 15 | """ 16 | length = traitlets.Integer() 17 | password = traitlets.Unicode("password") 18 | 19 | include_numbers = traitlets.Bool() 20 | special_character_groups = traitlets.Enum(SPECIAL_GROUPS, 21 | default_value=SPECIAL_GROUPS[0]) 22 | 23 | def __init__(self): 24 | super(PassGen, self).__init__() 25 | pass 26 | 27 | @traitlets.observe('length', 'include_numbers', 'special_character_groups') 28 | def generate_password(self, change): 29 | """ 30 | Generate a password of the desired length including the user's chosen 31 | set of special characters and, if desired, including some numerals. 32 | """ 33 | 34 | # Generate an initial password composed only of letters. 35 | new_pass = [] 36 | for _ in range(self.length): 37 | new_pass.append(random.choice(string.ascii_letters)) 38 | 39 | # Generate a list of indices for choosing which characters in the 40 | # initial password to replace, then shuffle it. We'll pop 41 | # elements off the list as we need them. 42 | order_for_replacements = list(range(self.length)) 43 | random.shuffle(order_for_replacements) 44 | 45 | # Replace some of the letters with special characters 46 | n_special = random.randint(1, 3) 47 | for _ in range(n_special): 48 | loc = order_for_replacements.pop(0) 49 | new_pass[loc] = random.choice(self.special_character_groups) 50 | 51 | if self.include_numbers: 52 | # Number of digits to include. 53 | n_digits = random.randint(1, 3) 54 | for _ in range(n_digits): 55 | loc = order_for_replacements.pop(0) 56 | new_pass[loc] = random.choice(string.digits) 57 | 58 | self.password = ''.join(new_pass) 59 | 60 | -------------------------------------------------------------------------------- /notebooks/images/flex-direction1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /notebooks/images/justify-content.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | flex-start 15 | flex-end 16 | 17 | 18 | 19 | 20 | center 21 | 22 | 23 | 24 | 25 | space-between 26 | 27 | 28 | 29 | 30 | space-around 31 | 32 | -------------------------------------------------------------------------------- /tools/kernel_names.py: -------------------------------------------------------------------------------- 1 | from argparse import ArgumentParser 2 | from pathlib import Path 3 | 4 | import nbformat 5 | 6 | NB_VERSION = 4 7 | 8 | 9 | def change_kernel_name(notebook_name, kernel_name, display_name=None): 10 | """ 11 | Change the name of the notebook kernel. 12 | """ 13 | dname = display_name if display_name else kernel_name 14 | notebook = nbformat.read(notebook_name, NB_VERSION) 15 | current_kname = notebook['metadata']['kernelspec']['name'] 16 | current_dname = notebook['metadata']['kernelspec']['display_name'] 17 | if current_kname == kernel_name and current_dname == dname: 18 | print('not changing kernel of {}'.format(notebook_name)) 19 | return 20 | 21 | notebook['metadata']['kernelspec']['name'] = kernel_name 22 | notebook['metadata']['kernelspec']['display_name'] = dname 23 | # print('\nHad this been a real operation, would have changed {}'.format(notebook_name)) 24 | # print('\t\tcurrent: {} to new: {}\n'.format(current_kname, kernel_name)) 25 | nbformat.write(notebook, notebook_name) 26 | 27 | 28 | def get_kernel_name(notebook_name): 29 | """ 30 | Return the name of the kernel in the notebook. 31 | """ 32 | notebook = nbformat.read(notebook_name, NB_VERSION) 33 | kname = notebook['metadata']['kernelspec']['name'] 34 | return kname 35 | 36 | 37 | if __name__ == '__main__': 38 | parser = ArgumentParser(description='Get or set kernel names for all ' 39 | 'notebooks in a directory.') 40 | parser.add_argument('-d', '--directory', default='.', 41 | help='Directory in which to look for notebooks.') 42 | parser.add_argument('-s', '--set', 43 | dest='kernel_name', 44 | metavar='kernel_name', 45 | help="Set the kernel to this name for each notebook.") 46 | parser.add_argument('--display-name', 47 | help="Display name of the kernel (default is same as " 48 | "kernel name).") 49 | 50 | args = parser.parse_args() 51 | 52 | directory = args.directory if args.directory else '.' 53 | p = Path(directory) 54 | notebooks = list(p.glob('**/*.ipynb')) 55 | 56 | if not notebooks: 57 | raise RuntimeError('No notebooks found at path {}'.format(directory)) 58 | 59 | for notebook in notebooks: 60 | nb_str = str(notebook) 61 | if args.kernel_name: 62 | change_kernel_name(nb_str, args.kernel_name, 63 | display_name=args.display_name) 64 | else: 65 | kname = get_kernel_name(nb_str) 66 | print('{}\t\t\t\t{}'.format(nb_str, kname)) 67 | -------------------------------------------------------------------------------- /notebooks/images/order-2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 1 19 | -1 20 | 1 21 | 2 22 | 5 23 | 1 24 | 1 25 | 2 26 | 3 27 | 28 | 29 | 30 | 2 31 | 2 32 | 99 33 | 34 | -------------------------------------------------------------------------------- /notebooks/HTML-widget.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "import requests\n", 10 | "import ipywidgets as widgets" 11 | ] 12 | }, 13 | { 14 | "cell_type": "code", 15 | "execution_count": 2, 16 | "metadata": {}, 17 | "outputs": [ 18 | { 19 | "data": { 20 | "application/vnd.jupyter.widget-view+json": { 21 | "model_id": "0c12284c4d704f0ba4da5419cd57aaa0", 22 | "version_major": "2", 23 | "version_minor": "0" 24 | }, 25 | "text/plain": [ 26 | "A Jupyter Widget" 27 | ] 28 | }, 29 | "metadata": {}, 30 | "output_type": "display_data" 31 | } 32 | ], 33 | "source": [ 34 | "h = widgets.HTML()\n", 35 | "h" 36 | ] 37 | }, 38 | { 39 | "cell_type": "code", 40 | "execution_count": 6, 41 | "metadata": {}, 42 | "outputs": [], 43 | "source": [ 44 | "h.value = requests.get('http://physics.mnstate.edu').text\n", 45 | "h.layout.height = '200px'" 46 | ] 47 | }, 48 | { 49 | "cell_type": "code", 50 | "execution_count": 4, 51 | "metadata": {}, 52 | "outputs": [], 53 | "source": [ 54 | "h.value = 'http://physics.mnstate.edu'" 55 | ] 56 | }, 57 | { 58 | "cell_type": "code", 59 | "execution_count": 7, 60 | "metadata": {}, 61 | "outputs": [ 62 | { 63 | "data": { 64 | "application/vnd.jupyter.widget-view+json": { 65 | "model_id": "4f179ead40b9454fb77009beac49fbe9", 66 | "version_major": "2", 67 | "version_minor": "0" 68 | }, 69 | "text/plain": [ 70 | "A Jupyter Widget" 71 | ] 72 | }, 73 | "metadata": {}, 74 | "output_type": "display_data" 75 | } 76 | ], 77 | "source": [ 78 | "l = widgets.Label(h.value)\n", 79 | "l" 80 | ] 81 | }, 82 | { 83 | "cell_type": "code", 84 | "execution_count": 8, 85 | "metadata": {}, 86 | "outputs": [], 87 | "source": [ 88 | "l.layout.height = '300px'" 89 | ] 90 | }, 91 | { 92 | "cell_type": "code", 93 | "execution_count": 9, 94 | "metadata": {}, 95 | "outputs": [], 96 | "source": [ 97 | "h.disabled = True" 98 | ] 99 | }, 100 | { 101 | "cell_type": "code", 102 | "execution_count": null, 103 | "metadata": {}, 104 | "outputs": [], 105 | "source": [] 106 | } 107 | ], 108 | "metadata": { 109 | "kernelspec": { 110 | "display_name": "widgets-tutorial", 111 | "language": "python", 112 | "name": "widgets-tutorial" 113 | }, 114 | "language_info": { 115 | "codemirror_mode": { 116 | "name": "ipython", 117 | "version": 3 118 | }, 119 | "file_extension": ".py", 120 | "mimetype": "text/x-python", 121 | "name": "python", 122 | "nbconvert_exporter": "python", 123 | "pygments_lexer": "ipython3", 124 | "version": "3.6.1" 125 | } 126 | }, 127 | "nbformat": 4, 128 | "nbformat_minor": 2 129 | } 130 | -------------------------------------------------------------------------------- /install_check.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | 3 | FIX_PREFIX = '----->' 4 | 5 | tutorial_name = 'Jupyter widget ecosystem' 6 | 7 | requirements = [ 8 | 'notebook', 9 | 'ipywidgets', 10 | 'bqplot', 11 | 'ipyleaflet', 12 | 'ipyvolume', 13 | 'pythreejs', 14 | 'pywwt' 15 | ] 16 | 17 | import_result = {p: False for p in requirements} 18 | 19 | print("Checking requirements for {}".format(tutorial_name), end='') 20 | 21 | for package in requirements: 22 | try: 23 | __import__(package) 24 | import_result[package] = True 25 | except ImportError: 26 | pass 27 | print('.', end='', flush=True) 28 | 29 | print() 30 | success = all(import_result.values()) 31 | 32 | version_check_packages = {'ipywidgets': '7.2', 33 | 'notebook': '5.5', 34 | } 35 | 36 | 37 | if success: 38 | print('\tAll required packages installed') 39 | else: 40 | print(FIX_PREFIX, 'Please install these missing packages ' 41 | 'for the tutorial "{}":'.format(tutorial_name)) 42 | missing = [k for k, v in import_result.items() if not v] 43 | print('\t' + '\n\t'.join(missing)) 44 | 45 | print('Checking version numbers of these packages: ', 46 | ', '.join(version_check_packages.keys())) 47 | 48 | 49 | def version_checker(package_name, version, nbextension=None): 50 | good_version = version.startswith(version_check_packages[package_name]) 51 | if nbextension is None: 52 | nbextension = package_name 53 | if not good_version: 54 | print('\n**** Please upgrade {} to version {} by running:'.format(package_name, 55 | version_check_packages[package_name])) 56 | print(' conda remove --force {} # if you use conda'.format(package_name)) 57 | print(' pip install --pre --upgrade {}'.format(package_name)) 58 | print(' jupyter nbextension enable --py {}'.format(nbextension)) 59 | else: 60 | print('\t{} version is good!'.format(package_name)) 61 | 62 | 63 | # Check as many packages as we can... 64 | 65 | 66 | try: 67 | import ipywidgets 68 | except ImportError: 69 | pass 70 | else: 71 | ipywidgets_version = ipywidgets.__version__ 72 | version_checker('ipywidgets', ipywidgets_version) 73 | 74 | try: 75 | import notebook 76 | except ImportError: 77 | pass 78 | else: 79 | notebook_version = notebook.__version__ 80 | version_checker('notebook', notebook_version) 81 | 82 | # Check that the appropriate kernel has been created 83 | 84 | required_kernel = 'widgets-tutorial' 85 | 86 | print('Checking whether kernel {} exists'.format(required_kernel)) 87 | 88 | import jupyter_client 89 | 90 | known_kernels = list(jupyter_client.kernelspec.find_kernel_specs().keys()) 91 | if required_kernel not in known_kernels: 92 | print(FIX_PREFIX, 'Please create custom kernel with: ', 93 | 'ipython kernel install --name widgets-tutorial --display-name widgets-tutorial --sys-prefix') 94 | else: 95 | print('\tCustom kernel is correctly installed') 96 | -------------------------------------------------------------------------------- /notebooks/01.00-overview.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Overview\n", 8 | "\n", 9 | "Behind the scenes, even pure Python widgets are composed of two pieces:\n", 10 | "\n", 11 | "+ Python, which runs in the notebook kernel.\n", 12 | "+ Javascript, which runs in the browser.\n", 13 | "\n", 14 | "When writing your own widgets, that means making a choice: write only in Python or write in both Python and Javascript.\n", 15 | "\n", 16 | "Which to choose depends on what you are trying to accomplish. This tutorial will focus on writing your own widgets in pure Python. An example of a pure-Python package that includes some interesting interfaces is [reducer](http://reducer.readthedocs.io), a package for calibrating astronomical data.\n", 17 | "\n", 18 | "What you can accomplish with just Python has increased quite a bit in the last year or two as more sophisticated tools that plug in to the Jupyter widget ecosystem have been written.\n", 19 | "\n", 20 | "One of those tools is [bqplot](https://github.com/bloomberg/bqplot/blob/master/examples/Index.ipynb), which provides a plotting tools in which the plot, and the lines, markers, labels and legend, all act as widgets. That required both Python *and* javascript. On the javacsript side bqplot uses [d3](https://d3js.org/) to do the drawing in the browser. \n", 21 | "\n", 22 | "Another example is [ipyvolume](https://ipyvolume.readthedocs.io/en/latest/), which does three-dimensional renderings of point or volumetric data in the browser. It has both Python and javascript pieces.\n", 23 | "\n", 24 | "One last addition is in `ipywidgets` itself: the new `Output` widget can display any content which can be rendered in a Jupyter notebook. That means that anything you can show in a notebook you can include in a widget using only Python." 25 | ] 26 | }, 27 | { 28 | "cell_type": "markdown", 29 | "metadata": {}, 30 | "source": [ 31 | "## Our destination, in a nutshell\n", 32 | "\n", 33 | "+ Green: [pythreejs](https://github.com/jupyter-widgets/pythreejs)\n", 34 | "+ Blue: [bqplot](https://github.com/bloomberg/bqplot/blob/master/examples/Index.ipynb)\n", 35 | "+ Everything else: [ipywidgets](https://github.com/jupyter-widgets/ipywidgets)\n", 36 | "+ Serving it up to users during development on [mybinder.org](https://mybinder.org/)\n", 37 | "\n", 38 | "![Binary Star Simulator](images/Binary_Star_Sim.png)\n", 39 | "\n", 40 | "### Source for this example (including links to binder): https://github.com/JuanCab/AstroInteractives\n", 41 | "\n", 42 | "[Video](https://youtu.be/kbgST0uifvM)" 43 | ] 44 | } 45 | ], 46 | "metadata": { 47 | "kernelspec": { 48 | "display_name": "Python 3", 49 | "language": "python", 50 | "name": "python3" 51 | }, 52 | "language_info": { 53 | "codemirror_mode": { 54 | "name": "ipython", 55 | "version": 3 56 | }, 57 | "file_extension": ".py", 58 | "mimetype": "text/x-python", 59 | "name": "python", 60 | "nbconvert_exporter": "python", 61 | "pygments_lexer": "ipython3", 62 | "version": "3.6.4" 63 | } 64 | }, 65 | "nbformat": 4, 66 | "nbformat_minor": 2 67 | } 68 | -------------------------------------------------------------------------------- /notebooks/images/align-items.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | 11 | flex-start 12 | 13 | 14 | 15 | 16 | 17 | center 18 | 19 | 20 | 21 | 22 | 23 | baseline 24 | 25 | 26 | 27 | 28 | 29 | stretch 30 | 31 | 32 | 33 | 34 | 35 | flex-end 36 | text text 37 | text text 38 | text text 39 | text text 40 | 41 | 42 | -------------------------------------------------------------------------------- /notebooks/_version_compatibility_checks/pythreejs-trial.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "from pythreejs import *\n", 10 | "import numpy as np\n", 11 | "from IPython.display import display\n", 12 | "from ipywidgets import HTML, Text\n", 13 | "from traitlets import link, dlink" 14 | ] 15 | }, 16 | { 17 | "cell_type": "code", 18 | "execution_count": 2, 19 | "metadata": {}, 20 | "outputs": [ 21 | { 22 | "data": { 23 | "application/vnd.jupyter.widget-view+json": { 24 | "model_id": "f236ed6bb07044b8a411345bc2968bfe", 25 | "version_major": "2", 26 | "version_minor": "0" 27 | }, 28 | "text/plain": [ 29 | "A Jupyter Widget" 30 | ] 31 | }, 32 | "metadata": {}, 33 | "output_type": "display_data" 34 | } 35 | ], 36 | "source": [ 37 | "ball = Mesh(geometry=SphereGeometry(radius=1), \n", 38 | " material=LambertMaterial(color='red'),\n", 39 | " position=[2, 1, 0])\n", 40 | "\n", 41 | "scene = Scene(children=[ball, AmbientLight(color='#777777')])\n", 42 | "\n", 43 | "c = PerspectiveCamera(position=[0, 5, 5], up=[0, 0, 1],\n", 44 | " children=[DirectionalLight(color='white', \n", 45 | " position=[3, 5, 1], \n", 46 | " intensity=0.5)])\n", 47 | "renderer = Renderer(camera=c, \n", 48 | " scene=scene, \n", 49 | " controls=[OrbitControls(controlling=c)])\n", 50 | "display(renderer)" 51 | ] 52 | }, 53 | { 54 | "cell_type": "code", 55 | "execution_count": 3, 56 | "metadata": {}, 57 | "outputs": [], 58 | "source": [ 59 | "ball.geometry.radius = 0.5" 60 | ] 61 | }, 62 | { 63 | "cell_type": "code", 64 | "execution_count": 4, 65 | "metadata": {}, 66 | "outputs": [ 67 | { 68 | "data": { 69 | "application/vnd.jupyter.widget-view+json": { 70 | "model_id": "6e9a62c07bcc4089987fb0588ff64149", 71 | "version_major": "2", 72 | "version_minor": "0" 73 | }, 74 | "text/plain": [ 75 | "A Jupyter Widget" 76 | ] 77 | }, 78 | "metadata": {}, 79 | "output_type": "display_data" 80 | } 81 | ], 82 | "source": [ 83 | "# On windows, linewidth of the material has no effect\n", 84 | "size = 4\n", 85 | "linesgeom = PlainGeometry(vertices=[[0, 0, 0],\n", 86 | " [size, 0, 0],\n", 87 | " [0, 0, 0],\n", 88 | " [0, size, 0],\n", 89 | " [0, 0, 0],\n", 90 | " [0, 0, size]],\n", 91 | " colors = ['red', 'red', 'green', 'green', 'white', 'orange'])\n", 92 | "lines = Line(geometry=linesgeom, \n", 93 | " material=LineBasicMaterial(linewidth=5, vertexColors='VertexColors'), \n", 94 | " type='LinePieces')\n", 95 | "scene = Scene(children=[lines, DirectionalLight(color='#ccaabb', position=[0,10,0]),AmbientLight(color='#cccccc')])\n", 96 | "c = PerspectiveCamera(position=[0, 10, 10])\n", 97 | "renderer = Renderer(camera=c, background='black', background_opacity=1, scene = scene, controls=[OrbitControls(controlling=c)])\n", 98 | "display(renderer)" 99 | ] 100 | }, 101 | { 102 | "cell_type": "code", 103 | "execution_count": null, 104 | "metadata": {}, 105 | "outputs": [], 106 | "source": [] 107 | } 108 | ], 109 | "metadata": { 110 | "kernelspec": { 111 | "display_name": "widgets-tutorial", 112 | "language": "python", 113 | "name": "widgets-tutorial" 114 | }, 115 | "language_info": { 116 | "codemirror_mode": { 117 | "name": "ipython", 118 | "version": 3 119 | }, 120 | "file_extension": ".py", 121 | "mimetype": "text/x-python", 122 | "name": "python", 123 | "nbconvert_exporter": "python", 124 | "pygments_lexer": "ipython3", 125 | "version": "3.6.1" 126 | } 127 | }, 128 | "nbformat": 4, 129 | "nbformat_minor": 2 130 | } 131 | -------------------------------------------------------------------------------- /notebooks/index.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# The Jupyter Interactive Widget Ecosystem \n", 8 | "\n", 9 | "## SciPy 2018\n", 10 | "## Matt Craig, Jason Grout, Sylvain Corlay and Maarten Breddels\n", 11 | "\n", 12 | "This tutorial will introduce you to the widgets in the Jupyter notebook, walk through a few approaches to writing widgets, and introduce some relatively new widget packages.\n", 13 | "\n", 14 | "We are using ipywidgets 7.2." 15 | ] 16 | }, 17 | { 18 | "cell_type": "markdown", 19 | "metadata": {}, 20 | "source": [ 21 | "00. [Introduction](00.00-introduction.ipynb)\n", 22 | "01. [Overview](01.00-overview.ipynb)\n", 23 | "02. [Widgets without writing widgets: interact](02.00-Using Interact.ipynb)\n", 24 | "03. [Simple Widget Introduction](03.00-Widget_Basics.ipynb)\n", 25 | "04. [Widget List](04.00-widget-list.ipynb)\n", 26 | "05. [Exercises](05.00-interact and widget basics exercises.ipynb)\n", 27 | "06. [Layout and Styling of Jupyter widgets](06.00-Widget_Styling.ipynb)\n", 28 | "07. [Widget layout exercises](07.00-container-exercises.ipynb)\n", 29 | "08. [Widget Events](08.00-Widget_Events.ipynb)\n", 30 | "09. [Three approaches to events](09.00-Widget Events 2.ipynb)\n", 31 | " 01. [Password generator: `observe`](09.01-Widget Events 2 -- bad password generator, version 1.ipynb)\n", 32 | " 02. [Separating the logic from the widgets](09.02-Widget Events 2 -- Separating Concerns.ipynb)\n", 33 | " 03. [Separating the logic from the widgets using classes](09.03-Widget Events 2--Separating concerns, object oriented.ipynb)\n", 34 | "10. [Building blocks II: more widget libraries](10.00-More widget libraries.ipynb)\n", 35 | " 01. [bqplot: complex interactive visualizations](10.01-bqplot.ipynb)\n", 36 | " 02. [pythreejs: 3D rendering in the browser](10.02-pythreejs.ipynb)\n", 37 | " 03. [ipyvolume: 3D plotting in the notebook](10.03-ipyvolume.ipynb)\n", 38 | " 04. [ipyleaflet: Maps in the notebook](10.04-ipyleaflet.ipynb)\n", 39 | " 05. [Embedding widgets](10.05-embed.ipynb)\n", 40 | " 06. [Widget library exercise: Link some widgets up](10.06-widget-library-exercises.ipynb)\n", 41 | " 07. [Exercise: Using one plot as a control for another](10.07-bqplot--A plot as a control in a widget.ipynb)\n", 42 | " 08. [Demo: Reactive plot with multiple regressions](10.08-bqplot--A--Penalized regression.ipynb)\n", 43 | " 09. [Input Widgets and Geospatial Data Analysis](10.09-flight-sim.ipynb)" 44 | ] 45 | }, 46 | { 47 | "cell_type": "markdown", 48 | "metadata": {}, 49 | "source": [ 50 | "## [Table of widget and style properties](Table of widget keys and style keys.ipynb)" 51 | ] 52 | }, 53 | { 54 | "cell_type": "markdown", 55 | "metadata": { 56 | "collapsed": true 57 | }, 58 | "source": [ 59 | "# Acknowledgements\n", 60 | "\n", 61 | "+ Special thanks to the dozens of [ipywidgets developers](https://github.com/jupyter-widgets/ipywidgets/graphs/contributors), including Jonathan Frederic who wrote much of the code in the early years of ipywidgets.\n", 62 | "+ Several of the notebooks in this tutorial were originally developed as part of [ipywidgets](http://ipywidgets.readthedocs.io/en/latest/) by Brian E. Granger ([@ellisonbg](https://github.com/ellisonbg)) and Jonathan Frederic ([@jdfreder](https://github.com/jdfreder)).\n", 63 | "+ Thanks to Doug Redden ([@DougRzz](https://github.com/DougRzz))\n", 64 | "+ Project Jupyter core developer Carol Willing ([@willingc](https://github.com/willingc)) and [Minnesota State University Moorhead](http://physics.mnstate.edu) students Andrew Block ([@ACBlock](https://github.com/ACBlock)) and Jane Glanzer ([@janeglanzer](https://github.com/janeglanzer)) provided very useful feedback on early drafts of this tutorial.\n" 65 | ] 66 | } 67 | ], 68 | "metadata": { 69 | "kernelspec": { 70 | "display_name": "widgets-tutorial", 71 | "language": "python", 72 | "name": "widgets-tutorial" 73 | }, 74 | "language_info": { 75 | "codemirror_mode": { 76 | "name": "ipython", 77 | "version": 3 78 | }, 79 | "file_extension": ".py", 80 | "mimetype": "text/x-python", 81 | "name": "python", 82 | "nbconvert_exporter": "python", 83 | "pygments_lexer": "ipython3", 84 | "version": "3.6.5" 85 | } 86 | }, 87 | "nbformat": 4, 88 | "nbformat_minor": 2 89 | } 90 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # The Jupyter Widget Ecosystem 2 | 3 | ## Tutorial, SciPy 2018 4 | 5 | # https://github.com/jupyter-widgets/tutorial 6 | 7 | # Installation 8 | 9 | The code in the tutorial has been written using Python 3; though most of it may also work with Python 2.7. 10 | 11 | We **strongly recommend** using the Anaconda Python distribution. You can install either the full [anaconda distribution](https://www.continuum.io/downloads) (very extensive, but large) or [miniconda](https://conda.io/miniconda.html) (much smaller, only essential packages). 12 | 13 | Almost all of the examples will work in either the regular Jupyter notebook or in JupyterLab; a couple of esoteric corner cases may not work in JupyterLab. The instructions below explain the additional installation steps needed for JupyerLab. 14 | 15 | *If you are familiar with Jupyter notebooks but have never used JupyterLab, you should either spend some time practicing with JupyterLab before this tutorial or use a plain notebook.* 16 | 17 | There are download instructions below for installation using pip, which should work with any Python distribution. 18 | 19 | ## anaconda/miniconda installation instructions 20 | ### Last update: 29 Jun 2018, 1800CDT 21 | 22 | The steps below will get you a working environment. 23 | 24 | ### Windows users planning to use JupyterLab 25 | 26 | The instructions below need one of two modification to work on Windows if you are planning to do the tutorial in JupyterLab. Please see the section [Windows/nodejs workarounds](#windowsnodejs-workarounds) below the installation instructions. 27 | 28 | 29 | 30 | ### Installation instructions 31 | ``` 32 | conda create -n widgets-tutorial -c conda-forge python=3.6 pip notebook=5.5 numpy scikit-image scipy pandas=0.23 requests 33 | 34 | conda activate widgets-tutorial 35 | 36 | # Install widgets from conda-forge 37 | conda install -c conda-forge ipywidgets=7.2 bqplot ipyvolume ipyleaflet pythreejs ipyevents 38 | 39 | # Install one more package from a different channel 40 | conda install -c wwt pywwt 41 | 42 | # Create a kernel for this environment 43 | ipython kernel install --name widgets-tutorial --display-name widgets-tutorial --sys-prefix 44 | 45 | # The remaining steps are necessary only if using JupyterLab: 46 | 47 | # Install JupyterLab 48 | conda install -c conda-forge jupyterlab nodejs=9.11 49 | 50 | # Enable JupyterLab extensions, which may take several minutes 51 | jupyter labextension install @jupyter-widgets/jupyterlab-manager bqplot ipyvolume jupyter-threejs jupyter-leaflet 52 | ``` 53 | 54 | ## Windows/nodejs workarounds 55 | 56 | Both of the fixes come from [this open conda issue](https://github.com/conda/conda/issues/7203). 57 | 58 | #### Fix for Windows 10 59 | 60 | Thanks to the tutorial participant who pointed this out on slack! The fix from the issue is: 61 | 62 | > enable Win32 long paths. [On] windows10 pro, you can open Local Group Policy Editor, and open item Computer Configuration -> Administrative Templates -> System -> Filesystem -> Enable Win32 long paths and click it to make it enable. 63 | 64 | #### Fix for Windows 7 65 | 66 | This might also work for earlier versions of Windows, but has only been tested on Windows 7. 67 | 68 | + Edit the `.condarc` file in your home directory, adding this to it: 69 | 70 | ``` 71 | pkgs_dirs: 72 | - c:\conda-pkgs 73 | ``` 74 | 75 | + Create a new environment this way: `conda create -p c:\widgets-tutorial` 76 | + Activate the environment: `conda activate c:\widgets-tutorial` 77 | + Replace the first line in the instructions below with: 78 | 79 | `conda install -c conda-forge python=3.6 pip notebook=5.5 numpy scikit-image scipy pandas=0.23 requests` 80 | 81 | ## pip installation instructions 82 | 83 | If you are not using the anaconda python distribution, please use the instructions below. 84 | 85 | ``` 86 | pip install notebook==5.5 ipywidgets numpy scipy scikit-image traitlets requests bqplot ipywidgets==7.2 ipyvolume matplotlib pandas==0.23 ipyleaflet pythreejs ipyevents pywwt 87 | 88 | # If you are using JupyterLab, install with 89 | pip install jupyterlab 90 | 91 | # If you are using JupyerLab, also run the series of labextension install command in 92 | # the conda instructions. 93 | ``` 94 | 95 | ## Check your installation 96 | 97 | To check your installation, please download the script [install_check.py](https://raw.githubusercontent.com/jupyter-widgets/tutorial/master/install_check.py) and run it: 98 | 99 | ``` 100 | python install_check.py 101 | ``` 102 | 103 | ## Tutorial materials 104 | 105 | To get the tutorial materials, clone this repository. *We anticipate making changes to the tutorial content through the end of June, 2018.* 106 | 107 | ## Using binder 108 | 109 | [![Binder](https://mybinder.org/badge.svg)](https://mybinder.org/v2/gh/jupyter-widgets/tutorial/master) 110 | 111 | Follow [mybinder.org](https://mybinder.org/v2/gh/jupyter-widgets/tutorial/master) to run the tutorial online. 112 | 113 | 114 | ## Running into trouble? 115 | 116 | Please let us know! You can open an issue on this repository by clicking "Issues" under the repo name on GitHub, then the "New Issue" button in the upper right. 117 | -------------------------------------------------------------------------------- /tools/generate_contents.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | from argparse import ArgumentParser 4 | 5 | import nbformat 6 | 7 | NOTEBOOK_DIR = os.path.join(os.path.dirname(__file__), '..', 'notebooks') 8 | 9 | NBVERSION = 4 10 | 11 | REG = re.compile(r'(\d\d)\.(\d\d)-(.*)\.ipynb') 12 | 13 | NO_NUMBER = ['00'] 14 | 15 | TOC_STYLES = ['header_ulist', 'nested_list'] 16 | 17 | 18 | def iter_notebooks(directory): 19 | return sorted(nb for nb in os.listdir(NOTEBOOK_DIR) if REG.match(nb)) 20 | 21 | 22 | def is_title(cell): 23 | return cell.source.startswith('# ') 24 | 25 | 26 | def get_notebook_title(nb_file): 27 | nb = nbformat.read(os.path.join(NOTEBOOK_DIR, nb_file), 28 | as_version=NBVERSION) 29 | for cell in nb.cells: 30 | if is_title(cell): 31 | return cell.source[1:].splitlines()[0].strip() 32 | else: 33 | # Apparently there was no heading, raise an error 34 | raise ValueError('No title found for {}.'.format(nb_file)) 35 | 36 | 37 | def gen_contents(directory=None, path_prefix=None, 38 | auto_number=False, toc_style=None): 39 | 40 | current_chapter = -1 41 | for nb in iter_notebooks(directory): 42 | if path_prefix: 43 | nb_url = os.path.join(path_prefix, nb) 44 | else: 45 | nb_url = nb 46 | chapter, section, title = REG.match(nb).groups() 47 | 48 | # Generate auto chapter and section numbers even if 49 | # we do not end up using them 50 | if chapter != current_chapter: 51 | current_chapter = chapter 52 | current_section = 1 53 | else: 54 | current_section += 1 55 | 56 | title = get_notebook_title(nb) 57 | 58 | if section == '00': 59 | if toc_style == 'nested_list': 60 | yield f'{chapter}. [{title}]({nb_url})' 61 | else: 62 | if chapter in NO_NUMBER: 63 | yield '\n### [{0}]({1})'.format(title, nb_url) 64 | else: 65 | yield '\n### [{0}. {1}]({2})'.format(int(chapter), 66 | title, nb_url) 67 | else: 68 | if toc_style == 'nested_list': 69 | yield f' {section}. [{title}]({nb_url})' 70 | else: 71 | yield "- [{0}]({1})".format(title, nb_url) 72 | 73 | 74 | def contents(directory=None, **kwd): 75 | """ 76 | Generate the table of contents as a string. 77 | """ 78 | return '\n'.join(gen_contents(directory, **kwd)) 79 | 80 | 81 | def write_notebook(toc, filename, toc_name="Table of Contents"): 82 | notebook = nbformat.v4.new_notebook() 83 | toc_cell_contents = '\n'.join(['# {}'.format(toc_name), toc]) 84 | toc_cell = nbformat.v4.new_markdown_cell(toc_cell_contents) 85 | notebook['cells'] = [toc_cell] 86 | nbformat.write(notebook, filename) 87 | 88 | 89 | if __name__ == '__main__': 90 | parser = ArgumentParser(description='Generate a table of contents from a ' 91 | 'directory of notebooks. The notebooks ' 92 | 'should be named so that when sorted ' 93 | 'by name they are in the order they ' 94 | 'should appear in the table of contents.') 95 | parser.add_argument('-o', '--output', 96 | help='Destination for the table of contents. If the ' 97 | 'extension is .ipynb then the output is a ' 98 | 'Jupyter notebook, otherwise the content is ' 99 | 'plain markdown. Default is to print to stdout.') 100 | parser.add_argument('-d', '--directory', 101 | help='Directory containing the notebooks from which ' 102 | 'to generate contents. Default value is ' 103 | '{}'.format(NOTEBOOK_DIR)) 104 | parser.add_argument('-p', '--path-prefix', 105 | help='If set, this string will be prepended to the ' 106 | 'links to the notebook in the generated table ' 107 | 'of contents.') 108 | parser.add_argument('--auto-number', action='store_true', 109 | help='Automatically number table of contents entries.') 110 | parser.add_argument('--toc-style', choices=TOC_STYLES, 111 | default='nested_list', 112 | help='Set the style for the table of contents.') 113 | args = parser.parse_args() 114 | directory = args.directory or NOTEBOOK_DIR 115 | toc = contents(directory, path_prefix=args.path_prefix, 116 | auto_number=args.auto_number, 117 | toc_style=args.toc_style) 118 | if not args.output: 119 | print(toc) 120 | elif args.output.endswith('.ipynb'): 121 | write_notebook(toc, args.output) 122 | else: 123 | with open(args.output, 'w') as f: 124 | f.write(toc) 125 | print('\n', 70 * '#', '\n') 126 | -------------------------------------------------------------------------------- /notebooks/09.01-Widget Events 2 -- bad password generator, version 1.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": { 7 | "collapsed": true 8 | }, 9 | "outputs": [], 10 | "source": [ 11 | "import ipywidgets as widgets" 12 | ] 13 | }, 14 | { 15 | "cell_type": "markdown", 16 | "metadata": {}, 17 | "source": [ 18 | "# Password generator: `observe`\n", 19 | "\n", 20 | "Consider a super-simple (and super-bad) password generator widget: given a password length, represented by a slider in the interface, it constructs a sequence of random letters of that length and displays it. \n", 21 | "\n", 22 | "\n", 23 | "This notebook illustrates how to connect the function that calculates the password to the length slider using `observe` but mixes together the code to calculate the password and the code to handle the events generated by the interface" 24 | ] 25 | }, 26 | { 27 | "cell_type": "markdown", 28 | "metadata": {}, 29 | "source": [ 30 | "## Construct the interface (widget)\n", 31 | "\n", 32 | "The widget should look like this once constructed:\n", 33 | "\n", 34 | "![Password generator](images/bad-pass-gen-v1.png)\n", 35 | "\n", 36 | "Compose the widget out of three basic widgets, one each for the title, the (currently not set) password, and one for the slider. \n", 37 | "\n", 38 | "In the cell below construct each of the basic widgets. " 39 | ] 40 | }, 41 | { 42 | "cell_type": "code", 43 | "execution_count": null, 44 | "metadata": { 45 | "collapsed": true 46 | }, 47 | "outputs": [], 48 | "source": [ 49 | "helpful_title = 0 # Replace with some that displays \"Generated password is:\"\n", 50 | "password_text = 0 # Replace with something that displays \"No password set\"\n", 51 | "password_length = 0 # Replace with slider\n" 52 | ] 53 | }, 54 | { 55 | "cell_type": "markdown", 56 | "metadata": {}, 57 | "source": [ 58 | "Combine these three into a single widget...the output should look like the image above." 59 | ] 60 | }, 61 | { 62 | "cell_type": "code", 63 | "execution_count": null, 64 | "metadata": { 65 | "collapsed": true 66 | }, 67 | "outputs": [], 68 | "source": [ 69 | "password_widget = widgets.VBox(children=[helpful_title, password_text, password_length])\n", 70 | "password_widget" 71 | ] 72 | }, 73 | { 74 | "cell_type": "code", 75 | "execution_count": null, 76 | "metadata": { 77 | "collapsed": true 78 | }, 79 | "outputs": [], 80 | "source": [ 81 | "# %load solutions/bad-password-generator/bad-pass-pass1-widgets.py\n" 82 | ] 83 | }, 84 | { 85 | "cell_type": "markdown", 86 | "metadata": {}, 87 | "source": [ 88 | "## Calculate the password...\n", 89 | "\n", 90 | "The function below calculates the password and should set the value of the `password_text` widget. The first part has been done, you just need to add the line that sets the widget value." 91 | ] 92 | }, 93 | { 94 | "cell_type": "code", 95 | "execution_count": null, 96 | "metadata": { 97 | "collapsed": true 98 | }, 99 | "outputs": [], 100 | "source": [ 101 | "def calculate_password(change):\n", 102 | " import string\n", 103 | " from secrets import choice\n", 104 | " length = change.new\n", 105 | " # Generate a list of random letters of the correct length.\n", 106 | " password = ''.join(choice(string.ascii_letters) for _ in range(length))\n", 107 | " # Add a line below to set the value of the widget password_text\n" 108 | ] 109 | }, 110 | { 111 | "cell_type": "code", 112 | "execution_count": null, 113 | "metadata": { 114 | "collapsed": true 115 | }, 116 | "outputs": [], 117 | "source": [ 118 | "# %load solutions/bad-password-generator/bad-pass-pass1-passgen.py\n" 119 | ] 120 | }, 121 | { 122 | "cell_type": "markdown", 123 | "metadata": {}, 124 | "source": [ 125 | "## ...and link password to widgets\n", 126 | "\n", 127 | "Fill in the line below. You want `calculate_password` to be called when the value of `password_length` changes. Here is a link to [Widget Events](06-Widget_Events.ipynb) in case you need it." 128 | ] 129 | }, 130 | { 131 | "cell_type": "code", 132 | "execution_count": null, 133 | "metadata": { 134 | "collapsed": true 135 | }, 136 | "outputs": [], 137 | "source": [ 138 | "# call calculate_password whenever the password length changes\n" 139 | ] 140 | }, 141 | { 142 | "cell_type": "code", 143 | "execution_count": null, 144 | "metadata": { 145 | "collapsed": true 146 | }, 147 | "outputs": [], 148 | "source": [ 149 | "# %load solutions/bad-password-generator/bad-pass-pass1-observe.py" 150 | ] 151 | }, 152 | { 153 | "cell_type": "markdown", 154 | "metadata": {}, 155 | "source": [ 156 | "Now that the connection is made, try moving the slider and you should see the password update." 157 | ] 158 | } 159 | ], 160 | "metadata": { 161 | "kernelspec": { 162 | "display_name": "widgets-tutorial", 163 | "language": "python", 164 | "name": "widgets-tutorial" 165 | }, 166 | "language_info": { 167 | "codemirror_mode": { 168 | "name": "ipython", 169 | "version": 3 170 | }, 171 | "file_extension": ".py", 172 | "mimetype": "text/x-python", 173 | "name": "python", 174 | "nbconvert_exporter": "python", 175 | "pygments_lexer": "ipython3", 176 | "version": "3.6.5" 177 | } 178 | }, 179 | "nbformat": 4, 180 | "nbformat_minor": 2 181 | } 182 | -------------------------------------------------------------------------------- /notebooks/05.00-interact and widget basics exercises.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": { 6 | "collapsed": true 7 | }, 8 | "source": [ 9 | "# Exercises\n", 10 | "\n", 11 | "This collection of exercises can be done in any order, or even skipped entirely; none of the rest of the tutorial depends on it. Choose the ones that are of interest to you. Sample solutions are provided for all of them.\n", 12 | "\n", 13 | "## We do not anticipate that you will have time to complete all of the exercises\n", 14 | "\n", 15 | "That is why we provide solutions!" 16 | ] 17 | }, 18 | { 19 | "cell_type": "code", 20 | "execution_count": null, 21 | "metadata": { 22 | "collapsed": true 23 | }, 24 | "outputs": [], 25 | "source": [ 26 | "%matplotlib inline\n", 27 | "import matplotlib.pyplot as plt\n", 28 | "\n", 29 | "import numpy as np\n", 30 | "\n", 31 | "import ipywidgets as widgets\n", 32 | "from ipywidgets import interact, interactive " 33 | ] 34 | }, 35 | { 36 | "cell_type": "markdown", 37 | "metadata": {}, 38 | "source": [ 39 | "## interact/interactive only" 40 | ] 41 | }, 42 | { 43 | "cell_type": "markdown", 44 | "metadata": {}, 45 | "source": [ 46 | "### Reverse some text\n", 47 | "\n", 48 | "Write a function that takes text as an input and reverses the text, and uses `interact` or `interactive` to generate the input field. " 49 | ] 50 | }, 51 | { 52 | "cell_type": "code", 53 | "execution_count": null, 54 | "metadata": {}, 55 | "outputs": [], 56 | "source": [ 57 | "# %load solutions/interact-basic-list/reverse-text.py" 58 | ] 59 | }, 60 | { 61 | "cell_type": "markdown", 62 | "metadata": {}, 63 | "source": [ 64 | "### Make a plot\n", 65 | "\n", 66 | "Pick a function of interest to you that depends on a couple of parameters and use `interact` to generate controls for the parameters. \n", 67 | "\n", 68 | "If you don't have a favorite function, try graphing $f(x) = \\sin(k x + p)$ over the $0 < x < 4\\pi$, with $0.5 \\le k \\le 2$ and $0 \\le p < 2\\pi$.\n", 69 | "\n", 70 | "Use either the `interact` function or the `@interact` decorator." 71 | ] 72 | }, 73 | { 74 | "cell_type": "code", 75 | "execution_count": null, 76 | "metadata": {}, 77 | "outputs": [], 78 | "source": [ 79 | "# %load solutions/interact-basic-list/plot-function.py" 80 | ] 81 | }, 82 | { 83 | "cell_type": "markdown", 84 | "metadata": {}, 85 | "source": [ 86 | "### A more extended example of interact\n", 87 | "\n", 88 | "A notebook I used in a cosmology course this past spring is [here](https://github.com/mwcraig/jupyter-notebook-intro/blob/master/supernova-data.ipynb). It used interact to allow students to fit cosmological models to high redshift supernova data." 89 | ] 90 | }, 91 | { 92 | "cell_type": "markdown", 93 | "metadata": {}, 94 | "source": [ 95 | "## interact/interactive and/or other widgets\n", 96 | "\n", 97 | "Some of the exercises below might be possible only with interact and interactive." 98 | ] 99 | }, 100 | { 101 | "cell_type": "markdown", 102 | "metadata": {}, 103 | "source": [ 104 | "### Choose two widgets and link their values\n", 105 | "\n", 106 | "Choose any two widgets from the widget list, link their values using `widgets.jslink`, and display both widgets (the `display` function can take more than one argument).\n", 107 | "\n", 108 | "Note: for all widgets except the section widgets below you should link the `value` of the widgets. For the selection widgets, link the `index` instead.\n", 109 | "\n", 110 | "Selection widgets:\n", 111 | "\n", 112 | "+ `Dropdown`\n", 113 | "+ `RadioButtons`\n", 114 | "+ `Select`\n", 115 | "+ `SelectionSlider`\n", 116 | "+ `SelectionRangeSlider`\n", 117 | "+ `ToggleButtons`\n", 118 | "+ `SelectMultiple`" 119 | ] 120 | }, 121 | { 122 | "cell_type": "code", 123 | "execution_count": null, 124 | "metadata": {}, 125 | "outputs": [], 126 | "source": [ 127 | "# %load solutions/interact-basic-list/sample-linked-widgets.py" 128 | ] 129 | }, 130 | { 131 | "cell_type": "markdown", 132 | "metadata": {}, 133 | "source": [ 134 | "### Boxes\n", 135 | "\n", 136 | "Create any three widgets, place them in a `VBox`, and display it." 137 | ] 138 | }, 139 | { 140 | "cell_type": "code", 141 | "execution_count": null, 142 | "metadata": {}, 143 | "outputs": [], 144 | "source": [ 145 | "# %load solutions/interact-basic-list/widgets-in-a-box.py" 146 | ] 147 | }, 148 | { 149 | "cell_type": "markdown", 150 | "metadata": {}, 151 | "source": [ 152 | "## Blank canvas\n", 153 | "\n", 154 | "The cell below is for you to try out antyhing you would like. Be creative!" 155 | ] 156 | }, 157 | { 158 | "cell_type": "code", 159 | "execution_count": null, 160 | "metadata": { 161 | "collapsed": true 162 | }, 163 | "outputs": [], 164 | "source": [] 165 | } 166 | ], 167 | "metadata": { 168 | "kernelspec": { 169 | "display_name": "widgets-tutorial", 170 | "language": "python", 171 | "name": "widgets-tutorial" 172 | }, 173 | "language_info": { 174 | "codemirror_mode": { 175 | "name": "ipython", 176 | "version": 3 177 | }, 178 | "file_extension": ".py", 179 | "mimetype": "text/x-python", 180 | "name": "python", 181 | "nbconvert_exporter": "python", 182 | "pygments_lexer": "ipython3", 183 | "version": "3.6.1" 184 | } 185 | }, 186 | "nbformat": 4, 187 | "nbformat_minor": 2 188 | } 189 | -------------------------------------------------------------------------------- /notebooks/images/state_sync.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
comm
[Not supported by viewer]
comm
[Not supported by viewer]
Widget
[Not supported by viewer]
WidgetModel
[Not supported by viewer]
-------------------------------------------------------------------------------- /notebooks/00.00-introduction.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Introduction\n", 8 | "\n", 9 | "The notebook comes alive with the interactive widgets" 10 | ] 11 | }, 12 | { 13 | "cell_type": "markdown", 14 | "metadata": {}, 15 | "source": [ 16 | "## Speeding up the bottleneck in the REPL\n", 17 | "\n", 18 | "" 19 | ] 20 | }, 21 | { 22 | "cell_type": "code", 23 | "execution_count": null, 24 | "metadata": {}, 25 | "outputs": [], 26 | "source": [ 27 | "9*9" 28 | ] 29 | }, 30 | { 31 | "cell_type": "code", 32 | "execution_count": null, 33 | "metadata": { 34 | "collapsed": true 35 | }, 36 | "outputs": [], 37 | "source": [ 38 | "def f(x):\n", 39 | " print(x * x)" 40 | ] 41 | }, 42 | { 43 | "cell_type": "code", 44 | "execution_count": null, 45 | "metadata": {}, 46 | "outputs": [], 47 | "source": [ 48 | "f(9)" 49 | ] 50 | }, 51 | { 52 | "cell_type": "code", 53 | "execution_count": null, 54 | "metadata": { 55 | "collapsed": true 56 | }, 57 | "outputs": [], 58 | "source": [ 59 | "from ipywidgets import *" 60 | ] 61 | }, 62 | { 63 | "cell_type": "code", 64 | "execution_count": null, 65 | "metadata": {}, 66 | "outputs": [], 67 | "source": [ 68 | "interact(f, x=(0, 100));" 69 | ] 70 | }, 71 | { 72 | "cell_type": "markdown", 73 | "metadata": {}, 74 | "source": [ 75 | "# Interactive Jupyter widgets\n", 76 | "\n", 77 | "A Python widget is an object that represents a control on the front end, like a slider. A single control can be displayed multiple times - they all represent the same python object." 78 | ] 79 | }, 80 | { 81 | "cell_type": "code", 82 | "execution_count": null, 83 | "metadata": {}, 84 | "outputs": [], 85 | "source": [ 86 | "slider = FloatSlider(\n", 87 | " value=7.5,\n", 88 | " min=5.0,\n", 89 | " max=10.0,\n", 90 | " step=0.1,\n", 91 | " description='Input:',\n", 92 | ")\n", 93 | "\n", 94 | "slider" 95 | ] 96 | }, 97 | { 98 | "cell_type": "code", 99 | "execution_count": null, 100 | "metadata": {}, 101 | "outputs": [], 102 | "source": [ 103 | "slider" 104 | ] 105 | }, 106 | { 107 | "cell_type": "markdown", 108 | "metadata": {}, 109 | "source": [ 110 | "The control attributes, like its value, are automatically synced between the frontend and the kernel." 111 | ] 112 | }, 113 | { 114 | "cell_type": "code", 115 | "execution_count": null, 116 | "metadata": {}, 117 | "outputs": [], 118 | "source": [ 119 | "slider.value" 120 | ] 121 | }, 122 | { 123 | "cell_type": "code", 124 | "execution_count": null, 125 | "metadata": { 126 | "collapsed": true 127 | }, 128 | "outputs": [], 129 | "source": [ 130 | "slider.value = 8" 131 | ] 132 | }, 133 | { 134 | "cell_type": "markdown", 135 | "metadata": {}, 136 | "source": [ 137 | "You can trigger actions in the kernel when a control value changes by \"observing\" the value. Here we set a global variable when the slider value changes." 138 | ] 139 | }, 140 | { 141 | "cell_type": "code", 142 | "execution_count": null, 143 | "metadata": { 144 | "collapsed": true, 145 | "scrolled": true 146 | }, 147 | "outputs": [], 148 | "source": [ 149 | "square = slider.value * slider.value\n", 150 | "def handle_change(change):\n", 151 | " global square\n", 152 | " square = change.new * change.new\n", 153 | "slider.observe(handle_change, 'value')" 154 | ] 155 | }, 156 | { 157 | "cell_type": "code", 158 | "execution_count": null, 159 | "metadata": {}, 160 | "outputs": [], 161 | "source": [ 162 | "square" 163 | ] 164 | }, 165 | { 166 | "cell_type": "markdown", 167 | "metadata": {}, 168 | "source": [ 169 | "You can link control attributes and lay them out together." 170 | ] 171 | }, 172 | { 173 | "cell_type": "code", 174 | "execution_count": null, 175 | "metadata": {}, 176 | "outputs": [], 177 | "source": [ 178 | "text = FloatText(description='Value')\n", 179 | "link((slider, 'value'), (text, 'value'))\n", 180 | "VBox([slider, text])" 181 | ] 182 | }, 183 | { 184 | "cell_type": "markdown", 185 | "metadata": {}, 186 | "source": [ 187 | "# Jupyter widgets as a framework\n", 188 | "\n", 189 | "Jupyter widgets forms a framework for representing python objects interactively. Some large open-source interactive controls based on Jupyter widgets include:\n", 190 | "\n", 191 | " - bqplot - 2d plotting library\n", 192 | " - pythreejs - low-level 3d graphics library\n", 193 | " - ipyvolume - high-level 3d graphics library\n", 194 | " - ipyleaflet - maps" 195 | ] 196 | } 197 | ], 198 | "metadata": { 199 | "kernelspec": { 200 | "display_name": "widgets-tutorial", 201 | "language": "python", 202 | "name": "widgets-tutorial" 203 | }, 204 | "language_info": { 205 | "codemirror_mode": { 206 | "name": "ipython", 207 | "version": 3 208 | }, 209 | "file_extension": ".py", 210 | "mimetype": "text/x-python", 211 | "name": "python", 212 | "nbconvert_exporter": "python", 213 | "pygments_lexer": "ipython3", 214 | "version": "3.6.5" 215 | }, 216 | "widgets": { 217 | "state": { 218 | "0cb2d81d826b4e25aba3b1d39711ef3f": { 219 | "views": [ 220 | { 221 | "cell_index": 6 222 | } 223 | ] 224 | }, 225 | "2cfe6521f9834ca69e54518716104cd2": { 226 | "views": [ 227 | { 228 | "cell_index": 8 229 | }, 230 | { 231 | "cell_index": 9 232 | }, 233 | { 234 | "cell_index": 12 235 | } 236 | ] 237 | }, 238 | "889356d59ac543a49da33d134d791c3f": { 239 | "views": [ 240 | { 241 | "cell_index": 11 242 | } 243 | ] 244 | } 245 | }, 246 | "version": "2.0.0-dev" 247 | } 248 | }, 249 | "nbformat": 4, 250 | "nbformat_minor": 1 251 | } 252 | -------------------------------------------------------------------------------- /notebooks/images/align-content.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | flex-start 8 | 9 | center 10 | 11 | space-between 12 | 13 | stretch 14 | 15 | space-around 16 | 17 | flex-end 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /notebooks/Flow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 |
You
[Not supported by viewer]
Code
[Not supported by viewer]
Jupyter
Notebook
[Not supported by viewer]
Kernel
[Not supported by viewer]
Execute
[Not supported by viewer]
Output
[Not supported by viewer]
Display
[Not supported by viewer]
-------------------------------------------------------------------------------- /notebooks/10.04-ipyleaflet.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# ipyleaflet: Maps in the notebook\n", 8 | "\n", 9 | "## https://github.com/jupyter-widgets/ipyleaflet\n", 10 | "\n", 11 | "From the ipyleaflet [examples](https://github.com/jupyter-widgets/ipyleaflet/blob/master/examples/Primitives.ipynb)" 12 | ] 13 | }, 14 | { 15 | "cell_type": "code", 16 | "execution_count": null, 17 | "metadata": {}, 18 | "outputs": [], 19 | "source": [ 20 | "from __future__ import print_function\n", 21 | "\n", 22 | "from ipyleaflet import (\n", 23 | " Map,\n", 24 | " Marker, MarkerCluster,\n", 25 | " TileLayer, ImageOverlay,\n", 26 | " Polyline, Polygon, Rectangle, Circle, CircleMarker,\n", 27 | " Popup,\n", 28 | " GeoJSON,\n", 29 | " DrawControl,\n", 30 | " basemaps\n", 31 | ")\n", 32 | "\n", 33 | "from ipywidgets import HTML" 34 | ] 35 | }, 36 | { 37 | "cell_type": "code", 38 | "execution_count": null, 39 | "metadata": {}, 40 | "outputs": [], 41 | "source": [ 42 | "center = [34.6252978589571, -77.34580993652344]\n", 43 | "zoom = 10" 44 | ] 45 | }, 46 | { 47 | "cell_type": "code", 48 | "execution_count": null, 49 | "metadata": {}, 50 | "outputs": [], 51 | "source": [ 52 | "m = Map(center=center, zoom=zoom)\n", 53 | "m" 54 | ] 55 | }, 56 | { 57 | "cell_type": "code", 58 | "execution_count": null, 59 | "metadata": {}, 60 | "outputs": [], 61 | "source": [ 62 | "m.interact(zoom=(5,10,1))" 63 | ] 64 | }, 65 | { 66 | "cell_type": "code", 67 | "execution_count": null, 68 | "metadata": {}, 69 | "outputs": [], 70 | "source": [ 71 | "m.clear_layers()" 72 | ] 73 | }, 74 | { 75 | "cell_type": "code", 76 | "execution_count": null, 77 | "metadata": {}, 78 | "outputs": [], 79 | "source": [ 80 | "m.add_layer(basemaps.Esri.DeLorme)" 81 | ] 82 | }, 83 | { 84 | "cell_type": "code", 85 | "execution_count": null, 86 | "metadata": {}, 87 | "outputs": [], 88 | "source": [ 89 | "print(m.center)\n", 90 | "print(m.zoom)\n", 91 | "print(m.bounds)" 92 | ] 93 | }, 94 | { 95 | "cell_type": "markdown", 96 | "metadata": {}, 97 | "source": [ 98 | "## Marker" 99 | ] 100 | }, 101 | { 102 | "cell_type": "code", 103 | "execution_count": null, 104 | "metadata": {}, 105 | "outputs": [], 106 | "source": [ 107 | "mark = Marker(location=m.center)" 108 | ] 109 | }, 110 | { 111 | "cell_type": "code", 112 | "execution_count": null, 113 | "metadata": {}, 114 | "outputs": [], 115 | "source": [ 116 | "mark.visible" 117 | ] 118 | }, 119 | { 120 | "cell_type": "code", 121 | "execution_count": null, 122 | "metadata": {}, 123 | "outputs": [], 124 | "source": [ 125 | "m += mark" 126 | ] 127 | }, 128 | { 129 | "cell_type": "code", 130 | "execution_count": null, 131 | "metadata": {}, 132 | "outputs": [], 133 | "source": [ 134 | "mark.interact(opacity=(0.0,1.0,0.01))" 135 | ] 136 | }, 137 | { 138 | "cell_type": "code", 139 | "execution_count": null, 140 | "metadata": {}, 141 | "outputs": [], 142 | "source": [ 143 | "m" 144 | ] 145 | }, 146 | { 147 | "cell_type": "code", 148 | "execution_count": null, 149 | "metadata": {}, 150 | "outputs": [], 151 | "source": [ 152 | "mark.visible" 153 | ] 154 | }, 155 | { 156 | "cell_type": "code", 157 | "execution_count": null, 158 | "metadata": {}, 159 | "outputs": [], 160 | "source": [ 161 | "mark.visible = False" 162 | ] 163 | }, 164 | { 165 | "cell_type": "markdown", 166 | "metadata": {}, 167 | "source": [ 168 | "## Popup\n", 169 | "\n", 170 | "The popup is displayed when you click on the marker." 171 | ] 172 | }, 173 | { 174 | "cell_type": "code", 175 | "execution_count": null, 176 | "metadata": {}, 177 | "outputs": [], 178 | "source": [ 179 | "mark.visible = True" 180 | ] 181 | }, 182 | { 183 | "cell_type": "code", 184 | "execution_count": null, 185 | "metadata": {}, 186 | "outputs": [], 187 | "source": [ 188 | "html_widget = HTML(\n", 189 | " value=\"\"\"\n", 190 | "
Some html with a list of items\n", 191 | "
\"\"\",\n", 196 | " placeholder='',\n", 197 | " description='',\n", 198 | ")" 199 | ] 200 | }, 201 | { 202 | "cell_type": "code", 203 | "execution_count": null, 204 | "metadata": {}, 205 | "outputs": [], 206 | "source": [ 207 | "mark.popup = html_widget" 208 | ] 209 | }, 210 | { 211 | "cell_type": "markdown", 212 | "metadata": {}, 213 | "source": [ 214 | "## Marker Cluster\n", 215 | "\n", 216 | "Markers can be clustered depending on the zoom level." 217 | ] 218 | }, 219 | { 220 | "cell_type": "code", 221 | "execution_count": null, 222 | "metadata": {}, 223 | "outputs": [], 224 | "source": [ 225 | "xscale = 5\n", 226 | "yscale = 10\n", 227 | "\n", 228 | "x = [m.center[0] + i * xscale * .05 for i in (-1,0,1)]\n", 229 | "y = [m.center[1] + i * yscale * .05 for i in (-1,0,1)]\n", 230 | "\n", 231 | "from itertools import product\n", 232 | "locations = product(x, y)\n", 233 | "markers = [Marker(location=loc) for loc in locations]" 234 | ] 235 | }, 236 | { 237 | "cell_type": "markdown", 238 | "metadata": {}, 239 | "source": [ 240 | "The `MarkerCluster` will automatically handle clustering and the zoom level changes." 241 | ] 242 | }, 243 | { 244 | "cell_type": "code", 245 | "execution_count": null, 246 | "metadata": {}, 247 | "outputs": [], 248 | "source": [ 249 | "marker_cluster = MarkerCluster(markers=markers)\n", 250 | "m += marker_cluster\n", 251 | "m" 252 | ] 253 | }, 254 | { 255 | "cell_type": "markdown", 256 | "metadata": {}, 257 | "source": [ 258 | "## Image Overlay" 259 | ] 260 | }, 261 | { 262 | "cell_type": "code", 263 | "execution_count": null, 264 | "metadata": {}, 265 | "outputs": [], 266 | "source": [ 267 | "io = ImageOverlay(url='http://ipython.org/_static/IPy_header.png', bounds=m.bounds)\n", 268 | "m.add_layer(io)" 269 | ] 270 | }, 271 | { 272 | "cell_type": "code", 273 | "execution_count": null, 274 | "metadata": {}, 275 | "outputs": [], 276 | "source": [ 277 | "m" 278 | ] 279 | }, 280 | { 281 | "cell_type": "code", 282 | "execution_count": null, 283 | "metadata": {}, 284 | "outputs": [], 285 | "source": [ 286 | "m.remove_layer(io)" 287 | ] 288 | } 289 | ], 290 | "metadata": { 291 | "kernelspec": { 292 | "display_name": "widgets-tutorial", 293 | "language": "python", 294 | "name": "widgets-tutorial" 295 | }, 296 | "language_info": { 297 | "codemirror_mode": { 298 | "name": "ipython", 299 | "version": 3 300 | }, 301 | "file_extension": ".py", 302 | "mimetype": "text/x-python", 303 | "name": "python", 304 | "nbconvert_exporter": "python", 305 | "pygments_lexer": "ipython3", 306 | "version": "3.6.5" 307 | } 308 | }, 309 | "nbformat": 4, 310 | "nbformat_minor": 2 311 | } 312 | -------------------------------------------------------------------------------- /tools/dependency_finder.py: -------------------------------------------------------------------------------- 1 | import re 2 | import itertools 3 | from pathlib import Path 4 | 5 | import nbformat 6 | from stdlib_list import stdlib_list 7 | 8 | VALID_PACKAGE_CHARS = '[a-zA-Z0-9_]' 9 | REG = re.compile(f'^\s*import ({VALID_PACKAGE_CHARS}+)|^\s*from ({VALID_PACKAGE_CHARS}+)\b+\simport', re.ASCII) 10 | 11 | 12 | def import_statements(code_source): 13 | """ 14 | Find and return all lines in the code cells of a notebook that contain 15 | import statements. 16 | 17 | Parameters 18 | ---------- 19 | 20 | code_source : an nbformat NotebookNode object or str 21 | Notebook whose cells will be checked for import statements or the 22 | contents of a Python file (as a string). 23 | 24 | Returns 25 | ------- 26 | 27 | list 28 | List of strings, each an import statement from the notebook. 29 | """ 30 | if isinstance(code_source, str): 31 | import_code = [code_source] 32 | else: 33 | import_cells = [c for c in code_source['cells'] if 34 | c['cell_type'] == 'code' and 'import' in c['source']] 35 | import_code = [c['source'] for c in import_cells] 36 | 37 | imports = [line for code in import_code 38 | for line in code.split('\n') if REG.match(line)] 39 | 40 | return imports 41 | 42 | 43 | def dependency_names_from_import_statements(imports, unique=True): 44 | """ 45 | Extract the package names from a list of import statements. 46 | 47 | Parameters 48 | ---------- 49 | 50 | imports : list 51 | List of import statements from which package names will be extracted. 52 | 53 | unique : bool, optional 54 | If ``True``, return list of unique package names, otherwise return 55 | package names in same order as input. 56 | """ 57 | packages = [] 58 | for i in imports: 59 | imp = REG.search(i) 60 | for g in imp.groups(): 61 | if g is not None: 62 | packages.append(g) 63 | continue 64 | 65 | if unique: 66 | packages = list(set(packages)) 67 | 68 | return packages 69 | 70 | 71 | def bad_imports(imports, as_written=False): 72 | """ 73 | Try a bunch of imports and report whether each was successful. 74 | 75 | Parameters 76 | ---------- 77 | imports : list 78 | List of import statements (e.g. ``import numpy`` or 79 | ``from astropy import units``) to try. Leading whitespace in the 80 | imports list is fine; it will be stripped before trying the import. 81 | 82 | as_written : bool, optional 83 | If ``True``, test the import statements exactly as they are passed in. 84 | Otherwise, just test the package name (i.e. the top-level import). 85 | 86 | Returns 87 | ------- 88 | 89 | list 90 | List of bool, ``True`` if the import fails, ``False`` if it succeeds. 91 | """ 92 | 93 | result = [] 94 | for imp in imports: 95 | if as_written: 96 | test = imp.strip() 97 | else: 98 | test = dependency_names_from_import_statements([imp])[0] 99 | test = 'import ' + test 100 | try: 101 | exec(test) 102 | except ModuleNotFoundError: 103 | result.append(True) 104 | else: 105 | result.append(False) 106 | 107 | return result 108 | 109 | 110 | def identify_dependencies(directory, nb_version=4, 111 | exclude_hidden=True, skip=None, 112 | notebooks=True, python_files=True, 113 | verbose=False): 114 | """ 115 | Find all notebooks in or below a directory, grab their import 116 | statements, and translate that to a list of dependencies. 117 | 118 | Parameters 119 | ---------- 120 | 121 | directory : str 122 | Path to directory to be searched for notebook. All subdirectories of 123 | this path will be searched. 124 | 125 | nb_version : int, optional 126 | Notebook version to assume when reading notebooks. 127 | 128 | exclude_hidden : bool, optional 129 | Exclude hidden directories or files (i.e. those whose name begins 130 | with ``.``). 131 | 132 | skip : list of str, optional 133 | List of notebook or directory names to skip. If a directory name is 134 | part of the list then all notebooks below that directory will be 135 | skipped. The name must match exactly to cause a skip. 136 | 137 | notebooks : bool, optional 138 | If ``True``, check for imports in notebooks. 139 | 140 | python_files : bool, optional 141 | If ``True``, check for imports in python files (i.e. files that 142 | end ``.py``). 143 | 144 | verbose: bool, optional 145 | If ``True``, print summary of progress while working. 146 | """ 147 | p = Path(directory) 148 | 149 | notebook_paths = p.glob('**/*.ipynb') if notebooks else [] 150 | python_paths = p.glob('**/*.py') if python_files else [] 151 | 152 | dep_info = { 153 | 'path': [], 154 | 'imports': [], 155 | 'packages': [], 156 | 'missing': [], 157 | } 158 | 159 | for path in itertools.chain(notebook_paths, python_paths): 160 | # Skip any directories or files that start with a dot... 161 | hidden = [part.startswith('.') and part != '..' for part in path.parts] 162 | 163 | skips = any(part in skip for part in path.parts) if skip else False 164 | 165 | if any(hidden) or skips: 166 | if verbose: 167 | print(f'...Skipping {path}', path.parts[-1]) 168 | continue 169 | 170 | if path.suffix == '.ipynb': 171 | nbnode = nbformat.read(str(path), nb_version) 172 | else: 173 | with path.open() as f: 174 | nbnode = f.read() 175 | imports = import_statements(nbnode) 176 | 177 | bads = bad_imports(imports) 178 | any_bad = any(bads) 179 | deps = dependency_names_from_import_statements(imports, unique=False) 180 | 181 | # print(f'Checked file { path } found { "SOME" if any_bad else "no" } bad imports') 182 | if any_bad: 183 | bad_list = [p for p, b in zip(deps, bads) if b] 184 | if verbose: 185 | print(f' Missing packages: {bad_list}') 186 | 187 | dep_info['path'].extend([str(path)] * len(imports)) 188 | dep_info['imports'].extend(imports) 189 | dep_info['packages'].extend(deps) 190 | dep_info['missing'].extend(bads) 191 | 192 | return dep_info 193 | 194 | 195 | def packages_to_install(dep_info, exclude=None): 196 | """ 197 | Produce a list of packages that need to be installed to use this set of 198 | materials. Python standard library modules are excluded. 199 | 200 | Parameters 201 | ---------- 202 | 203 | dep_info : dict 204 | Dictionary of dependency information, generated by 205 | `identify_dependencies`. 206 | 207 | exclude : list, optional 208 | List of packages to exclude from the results. 209 | 210 | Returns 211 | ------- 212 | 213 | list 214 | List of packages needed for whatever set of files this was run on. 215 | """ 216 | 217 | if exclude is None: 218 | exclude = [] 219 | 220 | packages = list(set(dep_info['packages'])) 221 | standard = stdlib_list("3.6") 222 | 223 | packages = [p for p in packages if p not in standard and p not in exclude] 224 | return packages 225 | 226 | 227 | if __name__ == '__main__': 228 | # Some day add options... 229 | directory = '.' 230 | dep_info = identify_dependencies(directory, skip=['setup.py']) 231 | to_install = packages_to_install(dep_info) 232 | print(' '.join(to_install)) 233 | -------------------------------------------------------------------------------- /notebooks/10.03-ipyvolume.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# ipyvolume: 3D plotting in the notebook\n", 8 | "\n", 9 | "## https://github.com/maartenbreddels/ipyvolume\n", 10 | "\n", 11 | "IPyvolume is a Python library to visualize 3d volumes and glyphs (e.g. 3d scatter plots), in the Jupyter notebook, with minimal configuration and effort. It is currently pre-1.0, so use at own risk. IPyvolume’s volshow is to 3d arrays what matplotlib’s imshow is to 2d arrays.\n", 12 | "\n", 13 | "- MIT Licensed\n", 14 | "\n", 15 | "By Maarten Breddels\n", 16 | "\n", 17 | "**Installation:**\n", 18 | "\n", 19 | "```bash\n", 20 | "conda install -c conda-forge ipyvolume\n", 21 | "```" 22 | ] 23 | }, 24 | { 25 | "cell_type": "markdown", 26 | "metadata": {}, 27 | "source": [ 28 | "# 3-D Scatter Plots" 29 | ] 30 | }, 31 | { 32 | "cell_type": "code", 33 | "execution_count": 1, 34 | "metadata": {}, 35 | "outputs": [ 36 | { 37 | "data": { 38 | "application/vnd.jupyter.widget-view+json": { 39 | "model_id": "0f87ac98ee46410f8d0a87d372ddd37f", 40 | "version_major": "2", 41 | "version_minor": "0" 42 | }, 43 | "text/plain": [ 44 | "A Jupyter Widget" 45 | ] 46 | }, 47 | "metadata": {}, 48 | "output_type": "display_data" 49 | } 50 | ], 51 | "source": [ 52 | "import ipyvolume\n", 53 | "import numpy as np\n", 54 | "x, y, z = np.random.random((3, 10000))\n", 55 | "ipyvolume.quickscatter(x, y, z, size=1, marker=\"sphere\")" 56 | ] 57 | }, 58 | { 59 | "cell_type": "code", 60 | "execution_count": 2, 61 | "metadata": {}, 62 | "outputs": [ 63 | { 64 | "data": { 65 | "application/vnd.jupyter.widget-view+json": { 66 | "model_id": "1f0f9542c77144ea93c8e8dea02ecac9", 67 | "version_major": "2", 68 | "version_minor": "0" 69 | }, 70 | "text/plain": [ 71 | "A Jupyter Widget" 72 | ] 73 | }, 74 | "metadata": {}, 75 | "output_type": "display_data" 76 | } 77 | ], 78 | "source": [ 79 | "import ipyvolume.pylab as p3\n", 80 | "import numpy as np\n", 81 | "x, y, z, u, v, w = np.random.random((6, 1000))*2-1\n", 82 | "selected = np.random.randint(0, 1000, 100)\n", 83 | "p3.figure()\n", 84 | "quiver = p3.quiver(x, y, z, u, v, w, size=5, size_selected=8, selected=selected)\n", 85 | "\n", 86 | "from ipywidgets import FloatSlider, ColorPicker, VBox, jslink\n", 87 | "size = FloatSlider(min=0, max=30, step=0.1)\n", 88 | "size_selected = FloatSlider(min=0, max=30, step=0.1)\n", 89 | "color = ColorPicker()\n", 90 | "color_selected = ColorPicker()\n", 91 | "jslink((quiver, 'size'), (size, 'value'))\n", 92 | "jslink((quiver, 'size_selected'), (size_selected, 'value'))\n", 93 | "jslink((quiver, 'color'), (color, 'value'))\n", 94 | "jslink((quiver, 'color_selected'), (color_selected, 'value'))\n", 95 | "VBox([p3.gcc(), size, size_selected, color, color_selected])" 96 | ] 97 | }, 98 | { 99 | "cell_type": "markdown", 100 | "metadata": {}, 101 | "source": [ 102 | "# Animations" 103 | ] 104 | }, 105 | { 106 | "cell_type": "code", 107 | "execution_count": 5, 108 | "metadata": { 109 | "collapsed": true 110 | }, 111 | "outputs": [], 112 | "source": [ 113 | "import ipyvolume.pylab as p3\n", 114 | "import numpy as np" 115 | ] 116 | }, 117 | { 118 | "cell_type": "code", 119 | "execution_count": 6, 120 | "metadata": {}, 121 | "outputs": [ 122 | { 123 | "name": "stdout", 124 | "output_type": "stream", 125 | "text": [ 126 | "x,y and z are of shape (25, 25)\n", 127 | "and flattened of shape (625,)\n" 128 | ] 129 | } 130 | ], 131 | "source": [ 132 | "# create 2d grids: x, y, and r\n", 133 | "u = np.linspace(-10, 10, 25)\n", 134 | "x, y = np.meshgrid(u, u)\n", 135 | "r = np.sqrt(x**2+y**2)\n", 136 | "print(\"x,y and z are of shape\", x.shape)\n", 137 | "# and turn them into 1d\n", 138 | "x = x.flatten()\n", 139 | "y = y.flatten()\n", 140 | "r = r.flatten()\n", 141 | "print(\"and flattened of shape\", x.shape)" 142 | ] 143 | }, 144 | { 145 | "cell_type": "code", 146 | "execution_count": 8, 147 | "metadata": {}, 148 | "outputs": [ 149 | { 150 | "name": "stdout", 151 | "output_type": "stream", 152 | "text": [ 153 | "z is of shape (15, 625)\n" 154 | ] 155 | } 156 | ], 157 | "source": [ 158 | "# create a sequence of 15 time elements\n", 159 | "time = np.linspace(0, np.pi*2, 15)\n", 160 | "z = np.array([(np.cos(r + t) * np.exp(-r/5)) for t in time])\n", 161 | "print(\"z is of shape\", z.shape)" 162 | ] 163 | }, 164 | { 165 | "cell_type": "code", 166 | "execution_count": 10, 167 | "metadata": {}, 168 | "outputs": [ 169 | { 170 | "data": { 171 | "application/vnd.jupyter.widget-view+json": { 172 | "model_id": "b8d6ba58e0fa4a98ad5216ef833e2406", 173 | "version_major": "2", 174 | "version_minor": "0" 175 | }, 176 | "text/plain": [ 177 | "A Jupyter Widget" 178 | ] 179 | }, 180 | "metadata": {}, 181 | "output_type": "display_data" 182 | } 183 | ], 184 | "source": [ 185 | "# draw the scatter plot, and add controls with animate_glyphs\n", 186 | "p3.figure()\n", 187 | "s = p3.scatter(x, z, y, marker=\"sphere\")\n", 188 | "p3.animate_glyphs(s, interval=200)\n", 189 | "p3.ylim(-3,3)\n", 190 | "p3.show()" 191 | ] 192 | }, 193 | { 194 | "cell_type": "code", 195 | "execution_count": 11, 196 | "metadata": {}, 197 | "outputs": [ 198 | { 199 | "name": "stdout", 200 | "output_type": "stream", 201 | "text": [ 202 | "color is of shape (15, 3, 625)\n" 203 | ] 204 | } 205 | ], 206 | "source": [ 207 | "# Now also include, color, which containts rgb values\n", 208 | "color = np.array([[np.cos(r + t), 1-np.abs(z[i]), 0.1+z[i]*0] for i, t in enumerate(time)])\n", 209 | "size = (z+1)\n", 210 | "print(\"color is of shape\", color.shape)" 211 | ] 212 | }, 213 | { 214 | "cell_type": "code", 215 | "execution_count": 12, 216 | "metadata": { 217 | "collapsed": true 218 | }, 219 | "outputs": [], 220 | "source": [ 221 | "color = np.transpose(color, (0, 2, 1)) # flip the last axes" 222 | ] 223 | }, 224 | { 225 | "cell_type": "code", 226 | "execution_count": 14, 227 | "metadata": {}, 228 | "outputs": [ 229 | { 230 | "name": "stderr", 231 | "output_type": "stream", 232 | "text": [ 233 | "/Users/scorlay/miniconda3/lib/python3.6/site-packages/traitlets/traitlets.py:567: FutureWarning: elementwise comparison failed; returning scalar instead, but in the future will perform elementwise comparison\n", 234 | " silent = bool(old_value == new_value)\n" 235 | ] 236 | }, 237 | { 238 | "data": { 239 | "application/vnd.jupyter.widget-view+json": { 240 | "model_id": "bf38100a9381460c9828cb7ac1aa5eb8", 241 | "version_major": "2", 242 | "version_minor": "0" 243 | }, 244 | "text/plain": [ 245 | "A Jupyter Widget" 246 | ] 247 | }, 248 | "metadata": {}, 249 | "output_type": "display_data" 250 | } 251 | ], 252 | "source": [ 253 | "p3.figure()\n", 254 | "s = p3.scatter(x, z, y, color=color, size=size, marker=\"sphere\")\n", 255 | "p3.animate_glyphs(s, interval=200)\n", 256 | "p3.ylim(-3, 3)\n", 257 | "p3.show()" 258 | ] 259 | }, 260 | { 261 | "cell_type": "code", 262 | "execution_count": null, 263 | "metadata": { 264 | "collapsed": true 265 | }, 266 | "outputs": [], 267 | "source": [] 268 | } 269 | ], 270 | "metadata": { 271 | "kernelspec": { 272 | "display_name": "widgets-tutorial", 273 | "language": "python", 274 | "name": "widgets-tutorial" 275 | }, 276 | "language_info": { 277 | "codemirror_mode": { 278 | "name": "ipython", 279 | "version": 3 280 | }, 281 | "file_extension": ".py", 282 | "mimetype": "text/x-python", 283 | "name": "python", 284 | "nbconvert_exporter": "python", 285 | "pygments_lexer": "ipython3", 286 | "version": "3.6.5" 287 | } 288 | }, 289 | "nbformat": 4, 290 | "nbformat_minor": 2 291 | } 292 | -------------------------------------------------------------------------------- /notebooks/images/assoc.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
Notebook
[Not supported by viewer]
Cell
[Not supported by viewer]
Cell
[Not supported by viewer]
WidgetModel
[Not supported by viewer]
WidgetView
[Not supported by viewer]
WidgetView
[Not supported by viewer]
WidgetView
[Not supported by viewer]
-------------------------------------------------------------------------------- /notebooks/images/transport.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 |
kernel
[Not supported by viewer]
web server
[Not supported by viewer]
front-end
[Not supported by viewer]
ZMQ
[Not supported by viewer]
WebSockets
[Not supported by viewer]
comm
[Not supported by viewer]
comm
[Not supported by viewer]
-------------------------------------------------------------------------------- /notebooks/images/busy.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
kernel
[Not supported by viewer]
front end
[Not supported by viewer]
notebook
[Not supported by viewer]
cell 0
[Not supported by viewer]
cell 1
[Not supported by viewer]
busy msg
[Not supported by viewer]
busy msg
[Not supported by viewer]
-------------------------------------------------------------------------------- /notebooks/images/WidgetArch.graffle: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ActiveLayerIndex 6 | 0 7 | ApplicationVersion 8 | 9 | com.omnigroup.OmniGraffle 10 | 139.18.0.187838 11 | 12 | AutoAdjust 13 | 14 | BackgroundGraphic 15 | 16 | Bounds 17 | {{0, 0}, {576, 733}} 18 | Class 19 | SolidGraphic 20 | ID 21 | 2 22 | Style 23 | 24 | shadow 25 | 26 | Draws 27 | NO 28 | 29 | stroke 30 | 31 | Draws 32 | NO 33 | 34 | 35 | 36 | BaseZoom 37 | 0 38 | CanvasOrigin 39 | {0, 0} 40 | ColumnAlign 41 | 1 42 | ColumnSpacing 43 | 36 44 | CreationDate 45 | 2013-11-09 20:06:39 +0000 46 | Creator 47 | bgranger 48 | DisplayScale 49 | 1 0/72 in = 1.0000 in 50 | GraphDocumentVersion 51 | 8 52 | GraphicsList 53 | 54 | 55 | Bounds 56 | {{212.5, 269.5}, {124.5, 40}} 57 | Class 58 | ShapedGraphic 59 | ID 60 | 5 61 | Shape 62 | Rectangle 63 | Style 64 | 65 | shadow 66 | 67 | Draws 68 | NO 69 | 70 | 71 | Text 72 | 73 | Text 74 | {\rtf1\ansi\ansicpg1252\cocoartf1265\cocoasubrtf190 75 | \cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 xkcd-Regular;} 76 | {\colortbl;\red255\green255\blue255;} 77 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc 78 | 79 | \f0\fs24 \cf0 Interact} 80 | 81 | 82 | 83 | Bounds 84 | {{212.5, 318}, {124.5, 40}} 85 | Class 86 | ShapedGraphic 87 | ID 88 | 4 89 | Shape 90 | Rectangle 91 | Style 92 | 93 | shadow 94 | 95 | Draws 96 | NO 97 | 98 | 99 | Text 100 | 101 | Text 102 | {\rtf1\ansi\ansicpg1252\cocoartf1265\cocoasubrtf190 103 | \cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 xkcd-Regular;} 104 | {\colortbl;\red255\green255\blue255;} 105 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc 106 | 107 | \f0\fs24 \cf0 Widgets} 108 | 109 | 110 | 111 | Bounds 112 | {{212.5, 366.5}, {124.5, 40}} 113 | Class 114 | ShapedGraphic 115 | ID 116 | 3 117 | Shape 118 | Rectangle 119 | Style 120 | 121 | shadow 122 | 123 | Draws 124 | NO 125 | 126 | 127 | Text 128 | 129 | Text 130 | {\rtf1\ansi\ansicpg1252\cocoartf1265\cocoasubrtf190 131 | \cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 xkcd-Regular;} 132 | {\colortbl;\red255\green255\blue255;} 133 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc 134 | 135 | \f0\fs24 \cf0 Comm} 136 | 137 | 138 | 139 | Bounds 140 | {{212.5, 415}, {124.5, 40}} 141 | Class 142 | ShapedGraphic 143 | ID 144 | 1 145 | Shape 146 | Rectangle 147 | Style 148 | 149 | shadow 150 | 151 | Draws 152 | NO 153 | 154 | 155 | Text 156 | 157 | Text 158 | {\rtf1\ansi\ansicpg1252\cocoartf1265\cocoasubrtf190 159 | \cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 xkcd-Regular;} 160 | {\colortbl;\red255\green255\blue255;} 161 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc 162 | 163 | \f0\fs24 \cf0 WebSockets/ZeroMQ} 164 | 165 | 166 | 167 | GridInfo 168 | 169 | GuidesLocked 170 | NO 171 | GuidesVisible 172 | YES 173 | HPages 174 | 1 175 | ImageCounter 176 | 1 177 | KeepToScale 178 | 179 | Layers 180 | 181 | 182 | Lock 183 | NO 184 | Name 185 | Layer 1 186 | Print 187 | YES 188 | View 189 | YES 190 | 191 | 192 | LayoutInfo 193 | 194 | Animate 195 | NO 196 | circoMinDist 197 | 18 198 | circoSeparation 199 | 0.0 200 | layoutEngine 201 | dot 202 | neatoSeparation 203 | 0.0 204 | twopiSeparation 205 | 0.0 206 | 207 | LinksVisible 208 | NO 209 | MagnetsVisible 210 | NO 211 | MasterSheets 212 | 213 | ModificationDate 214 | 2014-05-28 16:53:16 +0000 215 | Modifier 216 | bgranger 217 | NotesVisible 218 | NO 219 | Orientation 220 | 2 221 | OriginVisible 222 | NO 223 | PageBreaks 224 | YES 225 | PrintInfo 226 | 227 | NSBottomMargin 228 | 229 | float 230 | 41 231 | 232 | NSHorizonalPagination 233 | 234 | coded 235 | BAtzdHJlYW10eXBlZIHoA4QBQISEhAhOU051bWJlcgCEhAdOU1ZhbHVlAISECE5TT2JqZWN0AIWEASqEhAFxlwCG 236 | 237 | NSLeftMargin 238 | 239 | float 240 | 18 241 | 242 | NSPaperSize 243 | 244 | size 245 | {612, 792} 246 | 247 | NSPrintReverseOrientation 248 | 249 | int 250 | 0 251 | 252 | NSRightMargin 253 | 254 | float 255 | 18 256 | 257 | NSTopMargin 258 | 259 | float 260 | 18 261 | 262 | 263 | PrintOnePage 264 | 265 | ReadOnly 266 | NO 267 | RowAlign 268 | 1 269 | RowSpacing 270 | 36 271 | SheetTitle 272 | Canvas 1 273 | SmartAlignmentGuidesActive 274 | YES 275 | SmartDistanceGuidesActive 276 | YES 277 | UniqueID 278 | 1 279 | UseEntirePage 280 | 281 | VPages 282 | 1 283 | WindowInfo 284 | 285 | CurrentSheet 286 | 0 287 | ExpandedCanvases 288 | 289 | 290 | name 291 | Canvas 1 292 | 293 | 294 | Frame 295 | {{367, 6}, {710, 872}} 296 | ListView 297 | 298 | OutlineWidth 299 | 142 300 | RightSidebar 301 | 302 | ShowRuler 303 | 304 | Sidebar 305 | 306 | SidebarWidth 307 | 120 308 | VisibleRegion 309 | {{143.5, 183}, {287.5, 366.5}} 310 | Zoom 311 | 2 312 | ZoomValues 313 | 314 | 315 | Canvas 1 316 | 2 317 | 1 318 | 319 | 320 | 321 | 322 | 323 | -------------------------------------------------------------------------------- /notebooks/images/execute.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
kernel
[Not supported by viewer]
front end
[Not supported by viewer]
notebook
[Not supported by viewer]
cell 0
[Not supported by viewer]
cell 1
[Not supported by viewer]
execute event
[Not supported by viewer]
execute msg
[Not supported by viewer]
execute msg
[Not supported by viewer]
msg assigned GUID,
GUID associated with cell 1
[Not supported by viewer]
-------------------------------------------------------------------------------- /notebooks/09.02-Widget Events 2 -- Separating Concerns.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": { 7 | "collapsed": true 8 | }, 9 | "outputs": [], 10 | "source": [ 11 | "import ipywidgets as widgets" 12 | ] 13 | }, 14 | { 15 | "cell_type": "markdown", 16 | "metadata": {}, 17 | "source": [ 18 | "# Separating the logic from the widgets\n", 19 | "\n", 20 | "A key principle in designing a graphical user interface is to separate the logic of an application from the graphical widgets the user sees. For example, in the super-simple password generator widget, the basic logic is to construct a sequence of random letters given the length. Let's isolate that logic in a function, without any widgets. This function takes a password length and returns a generated password string." 21 | ] 22 | }, 23 | { 24 | "cell_type": "code", 25 | "execution_count": 6, 26 | "metadata": { 27 | "collapsed": true 28 | }, 29 | "outputs": [], 30 | "source": [ 31 | "def calculate_password(length):\n", 32 | " import string\n", 33 | " import secrets\n", 34 | " \n", 35 | " # Gaenerate a list of random letters of the correct length.\n", 36 | " password = ''.join(secrets.choice(string.ascii_letters) for _ in range(length))\n", 37 | "\n", 38 | " return password" 39 | ] 40 | }, 41 | { 42 | "cell_type": "markdown", 43 | "metadata": {}, 44 | "source": [ 45 | "Test out the function a couple times in the cell below with different lengths. Note that unlike our first pass through this, you can test this function without defining any widgets. This means you can write tests for just the logic, use the function as part of a library, etc." 46 | ] 47 | }, 48 | { 49 | "cell_type": "code", 50 | "execution_count": 7, 51 | "metadata": {}, 52 | "outputs": [ 53 | { 54 | "data": { 55 | "text/plain": [ 56 | "'ppcdOhADeC'" 57 | ] 58 | }, 59 | "execution_count": 7, 60 | "metadata": {}, 61 | "output_type": "execute_result" 62 | } 63 | ], 64 | "source": [ 65 | "calculate_password(10)" 66 | ] 67 | }, 68 | { 69 | "cell_type": "markdown", 70 | "metadata": {}, 71 | "source": [ 72 | "## The Graphical Controls\n", 73 | "\n", 74 | "The code to build the graphical user interface widgets is the same as the previous iteration." 75 | ] 76 | }, 77 | { 78 | "cell_type": "code", 79 | "execution_count": 8, 80 | "metadata": {}, 81 | "outputs": [ 82 | { 83 | "data": { 84 | "application/vnd.jupyter.widget-view+json": { 85 | "model_id": "4f6597e4ec5d43c1984e61987fa0a49b", 86 | "version_major": 2, 87 | "version_minor": 0 88 | }, 89 | "text/plain": [ 90 | "A Jupyter Widget" 91 | ] 92 | }, 93 | "metadata": {}, 94 | "output_type": "display_data" 95 | } 96 | ], 97 | "source": [ 98 | "helpful_title = widgets.HTML('Generated password is:')\n", 99 | "password_text = widgets.HTML('No password yet')\n", 100 | "password_text.layout.margin = '0 0 0 20px'\n", 101 | "password_length = widgets.IntSlider(description='Length of password',\n", 102 | " min=8, max=20,\n", 103 | " style={'description_width': 'initial'})\n", 104 | "\n", 105 | "password_widget = widgets.VBox(children=[helpful_title, password_text, password_length])\n", 106 | "password_widget" 107 | ] 108 | }, 109 | { 110 | "cell_type": "code", 111 | "execution_count": null, 112 | "metadata": { 113 | "collapsed": true 114 | }, 115 | "outputs": [], 116 | "source": [] 117 | }, 118 | { 119 | "cell_type": "markdown", 120 | "metadata": {}, 121 | "source": [ 122 | "## Connecting the logic to the widgets\n", 123 | "\n", 124 | "When the slider `password_length` changes, we want to call `calculate_password` to come up with a new password, and set the value of the widget `password` to the return value of the function call.\n", 125 | "\n", 126 | "`update_password` takes the change from the `password_length` as its argument and sets the `password_text` with the result of `calculate_password`." 127 | ] 128 | }, 129 | { 130 | "cell_type": "code", 131 | "execution_count": 10, 132 | "metadata": { 133 | "collapsed": true 134 | }, 135 | "outputs": [], 136 | "source": [ 137 | "def update_password(change):\n", 138 | " length = int(change.new)\n", 139 | " new_password = calculate_password(length)\n", 140 | " \n", 141 | " # NOTE THE LINE BELOW: it relies on the password widget already being defined.\n", 142 | " password_text.value = new_password\n", 143 | " \n", 144 | "password_length.observe(update_password, names='value')" 145 | ] 146 | }, 147 | { 148 | "cell_type": "markdown", 149 | "metadata": {}, 150 | "source": [ 151 | "Now that the connection is made, try moving the slider and you should see the password update." 152 | ] 153 | }, 154 | { 155 | "cell_type": "code", 156 | "execution_count": 9, 157 | "metadata": {}, 158 | "outputs": [ 159 | { 160 | "data": { 161 | "application/vnd.jupyter.widget-view+json": { 162 | "model_id": "4f6597e4ec5d43c1984e61987fa0a49b", 163 | "version_major": 2, 164 | "version_minor": 0 165 | }, 166 | "text/plain": [ 167 | "A Jupyter Widget" 168 | ] 169 | }, 170 | "metadata": {}, 171 | "output_type": "display_data" 172 | } 173 | ], 174 | "source": [ 175 | "password_widget" 176 | ] 177 | }, 178 | { 179 | "cell_type": "markdown", 180 | "metadata": {}, 181 | "source": [ 182 | "## Benefits of separating concerns\n", 183 | "\n", 184 | "Some advantages of this approach are:\n", 185 | "\n", 186 | "+ Changes in `ipywidgets` only affect your controls setup.\n", 187 | "+ Changes in functional logic only affect your password generation function. If you decide that a password with only letters isn't secure enough and decide to add some numbers and/or special characters, the only code you need to change is in the `calculate_password` function.\n", 188 | "+ You can write unit tests for your `calculate_password` function -- which is where the important work is being done -- without doing in-browser testing of the graphical controls." 189 | ] 190 | }, 191 | { 192 | "cell_type": "markdown", 193 | "metadata": {}, 194 | "source": [ 195 | "## Using interact\n", 196 | "\n", 197 | "Note that using interact to build this GUI also emphasizes the separation between the logic and the controls. However, interact also is much more opinionated about how the controls are laid out: controls are in a vbox above the output of the function. Often this is great for a quick initial GUI, but is restrictive for more complex GUIs." 198 | ] 199 | }, 200 | { 201 | "cell_type": "code", 202 | "execution_count": 21, 203 | "metadata": {}, 204 | "outputs": [ 205 | { 206 | "data": { 207 | "application/vnd.jupyter.widget-view+json": { 208 | "model_id": "8a10e24d23af40c38aac7af707c35929", 209 | "version_major": 2, 210 | "version_minor": 0 211 | }, 212 | "text/plain": [ 213 | "A Jupyter Widget" 214 | ] 215 | }, 216 | "metadata": {}, 217 | "output_type": "display_data" 218 | } 219 | ], 220 | "source": [ 221 | "from ipywidgets import interact\n", 222 | "from IPython.display import display\n", 223 | "interact(calculate_password, length=(8, 20));" 224 | ] 225 | }, 226 | { 227 | "cell_type": "markdown", 228 | "metadata": {}, 229 | "source": [ 230 | "We can make the interact a bit nicer by printing the result, rather than just returning the string. This time we use `interact` as a decorator." 231 | ] 232 | }, 233 | { 234 | "cell_type": "code", 235 | "execution_count": 22, 236 | "metadata": {}, 237 | "outputs": [ 238 | { 239 | "data": { 240 | "application/vnd.jupyter.widget-view+json": { 241 | "model_id": "f12a8bf7afde4ac1a158b3ee188e785e", 242 | "version_major": 2, 243 | "version_minor": 0 244 | }, 245 | "text/plain": [ 246 | "A Jupyter Widget" 247 | ] 248 | }, 249 | "metadata": {}, 250 | "output_type": "display_data" 251 | } 252 | ], 253 | "source": [ 254 | "@interact(length=(8, 20))\n", 255 | "def print_password(length):\n", 256 | " print(calculate_password(length))" 257 | ] 258 | }, 259 | { 260 | "cell_type": "code", 261 | "execution_count": null, 262 | "metadata": { 263 | "collapsed": true 264 | }, 265 | "outputs": [], 266 | "source": [] 267 | } 268 | ], 269 | "metadata": { 270 | "kernelspec": { 271 | "display_name": "widgets-tutorial", 272 | "language": "python", 273 | "name": "widgets-tutorial" 274 | }, 275 | "language_info": { 276 | "codemirror_mode": { 277 | "name": "ipython", 278 | "version": 3 279 | }, 280 | "file_extension": ".py", 281 | "mimetype": "text/x-python", 282 | "name": "python", 283 | "nbconvert_exporter": "python", 284 | "pygments_lexer": "ipython3", 285 | "version": "3.6.5" 286 | } 287 | }, 288 | "nbformat": 4, 289 | "nbformat_minor": 2 290 | } 291 | -------------------------------------------------------------------------------- /notebooks/07.00-container-exercises.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Widget layout exercises\n", 8 | "\n", 9 | "Earlier notebooks listed the container widgets in ipywidgets and how the widgets contained in them are laid out. As a reminder, the contents of the container are its `children`, a tuple of widgets. The distribution and alignment of the children are determined by the flex-box properties of the `layout` described in [Widget Styling](06-Widget-Styling.ipynb#The-Flexbox-layout).\n", 10 | "\n", 11 | "This set of exercises leads up to a password generator widget that will be completed after discussing widget events. The generator allows the user to set the length of the password, choose a set of special characters to include, and decide whether to include any digits. \n", 12 | "\n", 13 | "**Eventually** the widget will look like this:\n", 14 | "\n", 15 | "\"animated\n", 16 | "\n", 17 | "**At the end of this notebook** we will have laid out the controls shown below. We'll come back to the generator in later notebooks after we have discussed widget events.\n", 18 | "\n", 19 | "\"part\n" 20 | ] 21 | }, 22 | { 23 | "cell_type": "code", 24 | "execution_count": null, 25 | "metadata": { 26 | "collapsed": true 27 | }, 28 | "outputs": [], 29 | "source": [ 30 | "import ipywidgets as widgets" 31 | ] 32 | }, 33 | { 34 | "cell_type": "markdown", 35 | "metadata": {}, 36 | "source": [ 37 | "## 1. Alignment of children\n", 38 | "\n", 39 | "The cell below defines three children that are different sizes and lays them out in a horizontal box. Adjust the two layout properties in the code cell below so that the displayed hbox matches the image below.\n", 40 | "\n", 41 | "![final layout of widgets](images/container-exercises1-final-layout.png)\n", 42 | "\n", 43 | "You may need to look back at the [styling notebook](06-Widget_Styling.ipynb)." 44 | ] 45 | }, 46 | { 47 | "cell_type": "code", 48 | "execution_count": null, 49 | "metadata": { 50 | "collapsed": true 51 | }, 52 | "outputs": [], 53 | "source": [ 54 | "button = widgets.Button(description='Click me')\n", 55 | "text = widgets.Textarea(description='Words here:', rows=10)\n", 56 | "valid = widgets.Valid(description='check', value=True)\n", 57 | "\n", 58 | "container = widgets.HBox()\n", 59 | "container.children = [button, text, valid]\n", 60 | "container.layout.width = '100%'\n", 61 | "\n", 62 | "# The border is set here just to make it easier to see the position of \n", 63 | "# the children with respect to the box.\n", 64 | "container.layout.border = '2px solid grey'\n", 65 | "container.layout.height = '250px'\n", 66 | "\n", 67 | "# ↓↓↓↓↓↓↓ Adjust these properties ↓↓↓↓↓↓↓↓↓\n", 68 | "container.layout.justify_content = 'flex-start'\n", 69 | "container.layout.align_items = 'flex-start'\n", 70 | "# ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑\n", 71 | "\n", 72 | "container" 73 | ] 74 | }, 75 | { 76 | "cell_type": "markdown", 77 | "metadata": {}, 78 | "source": [ 79 | "## 2. Layout from scratch\n", 80 | "\n", 81 | "Three child widgets are defined in the cell below. Compose them into a vertical box laid out as shown in this image: \n", 82 | "\n", 83 | "![layout of vertical box](images/container-exercises2-final-layout.png)\n", 84 | "\n", 85 | "You should be able to accomplish that layout by setting the appropriate `layout` attribute(s) on `vbox` (don't forget to add the children first).\n", 86 | "\n", 87 | "A box is drawn around the container to make it easier to see where the children are placed" 88 | ] 89 | }, 90 | { 91 | "cell_type": "code", 92 | "execution_count": null, 93 | "metadata": { 94 | "collapsed": true 95 | }, 96 | "outputs": [], 97 | "source": [ 98 | "# %load solutions/container-exercises/password-ex2.py\n", 99 | "\n", 100 | "numbers = widgets.Checkbox(description='Include numbers in password')\n", 101 | "words = widgets.Label('The generated password is:')\n", 102 | "toggles = widgets.ToggleButtons(description='Type of special characters to include',\n", 103 | " options=[',./;[', '!@#~%', '^&*()'],\n", 104 | " style={'description_width': 'initial'})\n", 105 | "vbox = widgets.VBox()\n", 106 | "\n", 107 | "# The border is set here just to make it easier to see the position of \n", 108 | "# the children with respect to the box.\n", 109 | "vbox.layout.border = '2px solid grey'\n", 110 | "vbox.layout.height = '250px'\n", 111 | "\n", 112 | "# ↓↓↓↓↓↓↓ Insert your layout settings here ↓↓↓↓↓↓↓ \n", 113 | "\n", 114 | "# Don't forget to add the children...\n", 115 | "\n", 116 | "vbox" 117 | ] 118 | }, 119 | { 120 | "cell_type": "markdown", 121 | "metadata": {}, 122 | "source": [ 123 | "## 3. Improve the look of the children\n", 124 | "\n", 125 | "The \"special character\" toggle buttons would really look better if the label was above the buttons, and the checkbox would look better without the whitespace to its left.\n", 126 | "\n", 127 | "### i. A better special character control\n", 128 | "\n", 129 | "In the cell below, construct a widget with the text \"Type of special characters to include\" above the `ToggleButtons`, with all of the content left-aligned, and the toggle buttons slightly indented. \n", 130 | "\n", 131 | "Use the `margin` property of the layout to indent.\n", 132 | "\n", 133 | "It should look like this when you are done:\n", 134 | "\n", 135 | "\n", 136 | "
\n", 137 | "![special characters widget](images/container-exercises-special-chars.png)\n", 138 | "
\n", 139 | "\n", 140 | "This is the second time we've needed a vbox with all the items left-aligned, so let's start out with a `Layout` widget that defines that format" 141 | ] 142 | }, 143 | { 144 | "cell_type": "code", 145 | "execution_count": null, 146 | "metadata": { 147 | "collapsed": true 148 | }, 149 | "outputs": [], 150 | "source": [ 151 | "# %load solutions/container-exercises/password-ex3i.py\n", 152 | "\n", 153 | "vbox_left_layout = widgets.Layout(align_items='flex-start')\n", 154 | "\n", 155 | "label = widgets.Label('Choose special characters to include')\n", 156 | "toggles = widgets.ToggleButtons(description='',\n", 157 | " options=[',./;[', '!@#~%', '^&*()'],\n", 158 | " style={'description_width': 'initial'})\n", 159 | "\n", 160 | "# Set the margins to control the indentation. \n", 161 | "# The order is top right bottom left\n", 162 | "toggles.layout.margin = '0 0 0 20px'\n", 163 | "\n", 164 | "better_toggles = widgets.VBox([label, toggles])\n", 165 | "better_toggles.layout = vbox_left_layout\n", 166 | "better_toggles" 167 | ] 168 | }, 169 | { 170 | "cell_type": "markdown", 171 | "metadata": {}, 172 | "source": [ 173 | "### ii. Checkbox whitespace issues\n", 174 | "\n", 175 | "The checkbox in the example above has unnecessary whitespace to the left of the box. Setting the `description_width` to `initial` removes it, so do that below." 176 | ] 177 | }, 178 | { 179 | "cell_type": "code", 180 | "execution_count": null, 181 | "metadata": { 182 | "collapsed": true 183 | }, 184 | "outputs": [], 185 | "source": [ 186 | "# %load solutions/container-exercises/password-ex3ii.py\n", 187 | "\n", 188 | "numbers = widgets.Checkbox(description='Include numbers in password',\n", 189 | " style={'description_width': 'initial'})\n", 190 | "numbers" 191 | ] 192 | }, 193 | { 194 | "cell_type": "markdown", 195 | "metadata": {}, 196 | "source": [ 197 | "## 4 Put the pieces together\n", 198 | "\n", 199 | "Use your improved toggles and number checkbox to re-do the password generator interface from exercise 2, above.\n", 200 | "\n", 201 | "When you are done it should look like this:\n", 202 | "\n", 203 | "![part of password generator](images/05-container-exercises-ex4.png)\n" 204 | ] 205 | }, 206 | { 207 | "cell_type": "code", 208 | "execution_count": null, 209 | "metadata": { 210 | "collapsed": true 211 | }, 212 | "outputs": [], 213 | "source": [ 214 | "# %load solutions/container-exercises/password-ex4.py\n" 215 | ] 216 | }, 217 | { 218 | "cell_type": "markdown", 219 | "metadata": {}, 220 | "source": [ 221 | "### Alignment of children: additional (optional) challenges\n", 222 | "\n", 223 | "Using only `layout` attributes make the widget above:\n", 224 | "\n", 225 | "+ Display the children in reverse order (**not** by just reversing the list of children).\n", 226 | "+ Display the children vertically instead of horzontally." 227 | ] 228 | } 229 | ], 230 | "metadata": { 231 | "kernelspec": { 232 | "display_name": "widgets-tutorial", 233 | "language": "python", 234 | "name": "widgets-tutorial" 235 | }, 236 | "language_info": { 237 | "codemirror_mode": { 238 | "name": "ipython", 239 | "version": 3 240 | }, 241 | "file_extension": ".py", 242 | "mimetype": "text/x-python", 243 | "name": "python", 244 | "nbconvert_exporter": "python", 245 | "pygments_lexer": "ipython3", 246 | "version": "3.6.5" 247 | } 248 | }, 249 | "nbformat": 4, 250 | "nbformat_minor": 2 251 | } 252 | -------------------------------------------------------------------------------- /notebooks/10.07-bqplot--A plot as a control in a widget.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": { 7 | "collapsed": true 8 | }, 9 | "outputs": [], 10 | "source": [ 11 | "from __future__ import print_function\n", 12 | "import numpy as np\n", 13 | "\n", 14 | "from bqplot import (\n", 15 | " Axis, ColorAxis, LinearScale, DateScale, DateColorScale, OrdinalScale,\n", 16 | " OrdinalColorScale, ColorScale, Scatter, Lines, Figure, Tooltip\n", 17 | ")\n", 18 | "\n", 19 | "import ipywidgets as widgets" 20 | ] 21 | }, 22 | { 23 | "cell_type": "markdown", 24 | "metadata": {}, 25 | "source": [ 26 | "# Exercise: Using one plot as a control for another\n", 27 | "\n", 28 | "Although [bqplot](https://github.com/bloomberg/bqplot) is a versatile plotting package, one of its unique strengths is that the plot is a widget. Plot elements, like the position of points on the graph, can be monitored for changes like any other widget trait. \n", 29 | "\n", 30 | "The example in this notebook graphs a Fourier sine series using [bqplot](https://github.com/bloomberg/bqplot) to graph the series and a separate [bqplot](https://github.com/bloomberg/bqplot) to allow users to sest the amplitude of the terms in the series.\n", 31 | "\n", 32 | "## Final widget will look like this:\n", 33 | "\n", 34 | "\n", 35 | "" 36 | ] 37 | }, 38 | { 39 | "cell_type": "markdown", 40 | "metadata": {}, 41 | "source": [ 42 | "## Define the function that calculates the Fourier series\n", 43 | "\n", 44 | "Though in a real application the plot range and number of points should perhaps be configurable via a widget, for this example they are hard coded." 45 | ] 46 | }, 47 | { 48 | "cell_type": "code", 49 | "execution_count": null, 50 | "metadata": { 51 | "collapsed": true 52 | }, 53 | "outputs": [], 54 | "source": [ 55 | "def fourier_series(amplitudes):\n", 56 | " \"\"\"\n", 57 | " Compute the fourier sine series given a set of amplitudes. The \n", 58 | " period of the fundamental of the series is 1 and the series is \n", 59 | " generated for two periods.\n", 60 | " \"\"\"\n", 61 | " period = 1.0\n", 62 | " x = np.linspace(0, 2 * period, num=1000)\n", 63 | " y = np.sum(a * np.sin(2 * np.pi * (n + 1) * x / period) \n", 64 | " for n, a in enumerate(amplitudes))\n", 65 | "\n", 66 | " return x, y" 67 | ] 68 | }, 69 | { 70 | "cell_type": "markdown", 71 | "metadata": {}, 72 | "source": [ 73 | "The number of Fourier components in the series should probably also be user-configurable; for the sake of simplicity it is hard coded here. We also define some test data so we can look at plot for setting the amplitudes before we connect it up to the plot of the Fourier series sum." 74 | ] 75 | }, 76 | { 77 | "cell_type": "code", 78 | "execution_count": null, 79 | "metadata": { 80 | "collapsed": true 81 | }, 82 | "outputs": [], 83 | "source": [ 84 | "N_fourier_components = 10\n", 85 | "x_data = np.arange(N_fourier_components) + 1\n", 86 | "y_data = np.random.uniform(low=-1, high=1, size=N_fourier_components)" 87 | ] 88 | }, 89 | { 90 | "cell_type": "markdown", 91 | "metadata": {}, 92 | "source": [ 93 | "## Create the amplitude control\n", 94 | "\n", 95 | "We will create both this and the plot of the series using the \"Grammar of Graphics\" interface to bqplot. That makes it easier to treat the plot as a widget.\n", 96 | "\n", 97 | "Please read through and execute the example below." 98 | ] 99 | }, 100 | { 101 | "cell_type": "code", 102 | "execution_count": null, 103 | "metadata": {}, 104 | "outputs": [], 105 | "source": [ 106 | "# Start by defining a scale for each axis\n", 107 | "sc_x = LinearScale()\n", 108 | "\n", 109 | "# The amplitudes are limited to ±1 for this example...\n", 110 | "sc_y = LinearScale(min=-1.0, max=1.0)\n", 111 | "\n", 112 | "# You can create a Scatter object without supplying the data at this\n", 113 | "# point. It is here so we can see how the control looks.\n", 114 | "scat = Scatter(x=x_data, y=y_data, \n", 115 | " scales={'x': sc_x, 'y': sc_y}, \n", 116 | " colors=['orange'],\n", 117 | " # This is what makes this plot interactive\n", 118 | " enable_move=True)\n", 119 | "\n", 120 | "# Only allow points to be moved vertically...\n", 121 | "scat.restrict_y = True\n", 122 | "\n", 123 | "# Define the axes themselves\n", 124 | "ax_x = Axis(scale=sc_x)\n", 125 | "ax_y = Axis(scale=sc_y, tick_format='0.2f', orientation='vertical')\n", 126 | "\n", 127 | "# The graph itself...\n", 128 | "amplitude_control = Figure(marks=[scat], axes=[ax_x, ax_y], \n", 129 | " title='Fourier amplitudes')\n", 130 | "\n", 131 | "# This width is chosen just to make the plot fit nicely with \n", 132 | "# another. Change it if you want.\n", 133 | "amplitude_control.layout.width = '400px'\n", 134 | "\n", 135 | "# Let's see what this looks like...\n", 136 | "amplitude_control" 137 | ] 138 | }, 139 | { 140 | "cell_type": "markdown", 141 | "metadata": {}, 142 | "source": [ 143 | "Try dragging the points on the graph around. Print the $y$-data from the plot (`scat.y`) and the original data below you they should be different." 144 | ] 145 | }, 146 | { 147 | "cell_type": "markdown", 148 | "metadata": {}, 149 | "source": [ 150 | "## Set up some initial conditions\n", 151 | "\n", 152 | "To test our sine series plot it is helpful to start with a simple-to-understand series: just the fundmental, with amplitude 1. " 153 | ] 154 | }, 155 | { 156 | "cell_type": "code", 157 | "execution_count": null, 158 | "metadata": { 159 | "collapsed": true 160 | }, 161 | "outputs": [], 162 | "source": [ 163 | "# Add some test data to make view the result\n", 164 | "initial_amplitudes = np.zeros(10)\n", 165 | "initial_amplitudes[0] = 1.0" 166 | ] 167 | }, 168 | { 169 | "cell_type": "markdown", 170 | "metadata": {}, 171 | "source": [ 172 | "## Create the plot of the Fourier series\n", 173 | "\n", 174 | "As above, we create the plot using the Grammar of Graphics interface." 175 | ] 176 | }, 177 | { 178 | "cell_type": "code", 179 | "execution_count": null, 180 | "metadata": {}, 181 | "outputs": [], 182 | "source": [ 183 | "lin_x = LinearScale()\n", 184 | "lin_y = LinearScale()\n", 185 | "\n", 186 | "# Note that here, unlike above, we do not set the initial data.\n", 187 | "line = Lines(scales={'x': lin_x, 'y': lin_y}, colors=['orange'],\n", 188 | " enable_move=False)\n", 189 | "\n", 190 | "ax_x = Axis(scale=lin_x)\n", 191 | "ax_y = Axis(scale=lin_y, tick_format='0.2f', orientation='vertical')\n", 192 | "\n", 193 | "result = Figure(marks=[line], axes=[ax_x, ax_y], \n", 194 | " title='Fourier sine series',\n", 195 | " # Honestly, I just like the way the animation looks.\n", 196 | " # Value is in milliseconds.\n", 197 | " animation_duration=500)\n", 198 | "\n", 199 | "# Size as you wish...\n", 200 | "result.layout.width = '400px'\n", 201 | "\n", 202 | "# Calculate the fourier series....\n", 203 | "line.x, line.y = fourier_series(initial_amplitudes)\n", 204 | "\n", 205 | "# Let's take a look!\n", 206 | "result" 207 | ] 208 | }, 209 | { 210 | "cell_type": "markdown", 211 | "metadata": {}, 212 | "source": [ 213 | "### Set the amplitude control to match this initial case\n", 214 | "\n", 215 | "Note that you can access the `scat` object from the `Figure` widget. Each line, scatter or other mark on the plot is in the list at `.marks`." 216 | ] 217 | }, 218 | { 219 | "cell_type": "code", 220 | "execution_count": null, 221 | "metadata": { 222 | "collapsed": true 223 | }, 224 | "outputs": [], 225 | "source": [ 226 | "amplitude_control.marks[0].y = initial_amplitudes" 227 | ] 228 | }, 229 | { 230 | "cell_type": "markdown", 231 | "metadata": {}, 232 | "source": [ 233 | "## You make the widget out of `amplitude_control` and `result`\n", 234 | "\n", 235 | "See the animation above for a reminder of the target. Dragging the amplitudes will *not* change the sine series yet." 236 | ] 237 | }, 238 | { 239 | "cell_type": "code", 240 | "execution_count": null, 241 | "metadata": { 242 | "collapsed": true 243 | }, 244 | "outputs": [], 245 | "source": [ 246 | "# %load solutions/bqplot-as-control/box-widget.py" 247 | ] 248 | }, 249 | { 250 | "cell_type": "markdown", 251 | "metadata": {}, 252 | "source": [ 253 | "## Looks good, connect things up\n", 254 | "\n", 255 | "Fill in the body of the function below, which will be observed by `amplitude_control`. You should call `fourier_series` and set the appropriate line properties." 256 | ] 257 | }, 258 | { 259 | "cell_type": "code", 260 | "execution_count": null, 261 | "metadata": { 262 | "collapsed": true 263 | }, 264 | "outputs": [], 265 | "source": [ 266 | "# %load solutions/bqplot-as-control/update_line.py\n", 267 | "def update_line(change):\n", 268 | " pass\n" 269 | ] 270 | }, 271 | { 272 | "cell_type": "code", 273 | "execution_count": null, 274 | "metadata": { 275 | "collapsed": true 276 | }, 277 | "outputs": [], 278 | "source": [ 279 | "# React to changes in the y value....\n", 280 | "amplitude_control.marks[0].observe(update_line, names=['y'])" 281 | ] 282 | } 283 | ], 284 | "metadata": { 285 | "kernelspec": { 286 | "display_name": "widgets-tutorial", 287 | "language": "python", 288 | "name": "widgets-tutorial" 289 | }, 290 | "language_info": { 291 | "codemirror_mode": { 292 | "name": "ipython", 293 | "version": 3 294 | }, 295 | "file_extension": ".py", 296 | "mimetype": "text/x-python", 297 | "name": "python", 298 | "nbconvert_exporter": "python", 299 | "pygments_lexer": "ipython3", 300 | "version": "3.6.5" 301 | } 302 | }, 303 | "nbformat": 4, 304 | "nbformat_minor": 2 305 | } 306 | --------------------------------------------------------------------------------