
by Josh Redmond
3. Basics of interaction
After this lesson, you will be able to:
- Use widgets to create visual tools that allow users to set parameters for functions.
- Dynamically run code according to user input.
- Display the results of this code, and update these results automatically.
In this lesson, you will explore how to load in data and interact with it visually using widgets in a notebook format. These widgets will form the basis of the apps that you will create in this class. Widgets are a powerful tool and can be used for a wide range of different tasks, and can even be customised with javascript if you so desire.
Before we create maps and geospatial models like we discussed in the introductory lesson, it is important to understand how the basics of interaction work in Jupyter Notebooks, and how you can use widgets to
Firstly, you need to open a Jupyter Notebook. If you are using Anaconda, and have installed all of the required files, then simply open Anaconda Navigator, activate your environment, and then launch Jupyter Notebook.
First, we'll load in some data. This lesson will provide all of the code you need to load in and manage data, so you can focus on building interactive interfaces rather than data management!
Each of these code-blocks can go into a separte cell in your Jupyter Notebook.
## We will start with some generated dummy data to learn the basics
## of interactive visualisation
#from utils.functions import *
import numpy as np
import matplotlib.pyplot as plt
x = np.random.normal(5, 2, 100)
def generate_y(x, m, c):
return m*x + c
y = generate_y(x, 2, 3)
plt.scatter(x, y)
In the above cell we have generated some data and plotted it - the Y part of the data is generated according to parameters set by the user. If you imagine this to be a model of some phenomena, then varying the input parameters lets a user or researcher explore what different scenarios might mean and how they affect outcomes important to them.
We are now going to see how we can add a visual tool which lets a user change the parameters of the function.
import ipywidgets as widgets
m_slider = widgets.IntSlider(
value=2,
min=0,
max=10,
step=1,
description='M:')
c_slider = widgets.IntSlider(
value=3,
min=0,
max=10,
step=1,
description='C:')
display(m_slider)
display(c_slider)
The above code creates two integer sliders which let the user select parameter values between limits that we have defined. This lets them visually select the values and interact with them. The sliders run on the web page rendered by your Jupyter Notebook, and you can interact with them in the same way you might use a form or slider on any other web app.
Within Python, it is easy to then extract the values from the widgets by using the .value property, as below.
print("M:", m_slider.value)
print("C", c_slider.value)
y = generate_y(x, m_slider.value, c_slider.value)
plt.scatter(x, y)
Updating the chart with user input is easy, but it doesn't happen automatically when the user updates a parameter, we can change this by using observers. Observers are an important concept in web-development generally, but in this case what you need to know is that they allow you to set a function which is called when the state of a widget changes. By using observers, we can run code to improve the interactive experience, and allow apps to respond to their users in real time.
Below, we will create two observers for the sliders which are called every time their value is changed, updating the chart. We will also use an 'output' widget which allows us to dynamically update the display of the data. Output widgets dont do anything by themselves, but are containers for displaying data to the user which can be updated easily to dynamically show new information to the user.
%matplotlib inline
plot_display = widgets.Output()
with plot_display:
y = generate_y(x, 2, 3)
fig, ax = plt.subplots()
chart = ax.scatter(x, y)
plt.show()
m_slider = widgets.IntSlider(
value=2,
min=0,
max=10,
step=1,
description='M:')
c_slider = widgets.IntSlider(
value=3,
min=0,
max=10,
step=1,
description='C:')
def update_chart(change):
y = generate_y(x, m_slider.value, c_slider.value)
plot_display.clear_output(wait=True)
with plot_display:
fig, ax = plt.subplots()
chart = ax.scatter(x, y)
plt.show()
m_slider.observe(update_chart, names='value')
c_slider.observe(update_chart, names='value')
display(plot_display)
display(m_slider)
display(c_slider)
There's a load of different ways to handle this kind of interaction, including using buttons and listening for mouseclicks. Depending on what the goals are of the application, and what computation you are doing, you might use a different approach. If you are generating data in a computationally intensive way, you might not want to re-run all your code every time the user updates something, instead using a button to start that process.
Now, it is your turn, in the following exercises, you will create some basic interactive tools and use them to generate and visualise data. Answers will be below, so if you get stuck feel free to take a look.
Exercise 1
Re-write the generation function so the data is exponential (x^m + c) rather than linear and plot the data
Exercise 2
Vary visualisation parameters with a dropdown menu so users can select between the linear and exponential functions
# Hint: Use the widgets.Dropdown() function
# Dropdown menus work in basically the same way as the sliders, but with categorical options which you specify by hand
# The goal is to create a dropdown which will allow the user to change from linear to exponential
example_dropdown = widgets.Dropdown(options=['Item 1', 'Item 2'])
display(example_dropdown)
Exercise 3
Add a button to reset the visualisation to some default parameters
# Hint: Use the widgets.Button() function
# Buttons are widgets which you can click to trigger an action
# You can specify the action by passing a function to the button's on_click() method
out = widgets.Output()
example_button = widgets.Button(description='Test')
def print_button(b):
with out:
print("Button clicked!")
example_button.on_click(print_button)
display(example_button)
display(out)
Exercise 1 Solution
def generate_y_exp(x, m=2, c=3):
return x**m + c
Exercise 2 Solution
# Exercise 2 solution
modal_dropdown = widgets.Dropdown(options=['Linear', 'Exponential'])
plot_display = widgets.Output()
with plot_display:
y = generate_y(x, 2, 3)
fig, ax = plt.subplots()
chart = ax.scatter(x, y)
plt.show()
m_slider = widgets.IntSlider(
value=2,
min=0,
max=10,
step=1,
description='M:')
c_slider = widgets.IntSlider(
value=3,
min=0,
max=10,
step=1,
description='C:')
def update_chart(change):
if modal_dropdown.value == 'Linear':
y = generate_y(x, m_slider.value, c_slider.value)
else:
y = generate_y_exp(x, m_slider.value, c_slider.value)
plot_display.clear_output(wait=True)
with plot_display:
fig, ax = plt.subplots()
chart = ax.scatter(x, y)
plt.show()
modal_dropdown.observe(update_chart, names='value')
c_slider.observe(update_chart, names='value')
m_slider.observe(update_chart, names='value')
display(plot_display)
display(m_slider)
display(c_slider)
display(modal_dropdown)
Exercise 3 Solution
def reset(b):
m_slider.value = 2
c_slider.value = 3
reset_button = widgets.Button(description='Reset')
reset_button.on_click(reset)
display(reset_button)