Josh Redmond

by Josh Redmond

Lesson

4. Layouts and widgets

After this lesson, you will be able to:

  1. Describe how layout widgets can be used to create interfaces
  2. Arrange widgets together using layout widgets
  3. Nest layout widgets together to create groups of display or input widgets

In the previous lesson, you created some visualisations and some widgets which could control them, but it didn't look that amazing. The layout of the buttons, widgets, sliders, and other elements your design can affect usability, readability, and the experience of those using the app. Luckily, ipywidgets makes it pretty easy to design layouts.

Lets start by re-creating the application we built last time:

#First, we'll load the data and functions from the last lesson. 

import numpy as np
import matplotlib.pyplot as plt
import ipywidgets as widgets
plot_display = widgets.Output()

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:')


x = np.random.normal(5, 2, 100)
def generate_y(x, m, c):
    return m*x + 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()

We will now draw all of the widgets in sequence, as they were before:

with plot_display:
    y = generate_y(x, 2, 3)
    fig, ax = plt.subplots()
    chart = ax.scatter(x, y)
    plt.show()


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()

display(plot_display)
display(m_slider)
display(c_slider)

m_slider.observe(update_chart, 'value')
c_slider.observe(update_chart, 'value')

This kind of layout works for very simple functionality, where there aren't many choice to make or a lot of things to do, but when you have multiple controls and options to select, it might make sense to group them together in order to make interacting with them a bit more intuitive and less confusing.

Luckily there are a good number of ipywidgets which allow you to control the layouts of other widgets. For instance, by using box widgets it is possible to put different widgets and displays together in order to create cohesive and pleasing interfaces. Because these are all python objects, you simply have to put them together in lists and structures as per your preference. These boxes are known as containers, and can contain widgets, visualisations, or other containers.

Using these containers, it is possible and relatively easy to construct user interfaces based on the relative position of your widgets, containers, and visualisations. By nesting containers within other containers, you can create button or control groups on the page, panels with sub-sections, and so on. This is similar to how many websites construct their user interfaces, with nested divs and containers.

Lets see an example with our widgets.

widget_container = widgets.HBox([m_slider, c_slider])
display(widget_container)

Similarly, we can use the VBox widget to display the widgets vertically:

widget_container = widgets.VBox([m_slider, c_slider])
display(widget_container)

Notice how putting these widgets in the container doesn't affect their output or relationship to the rest of the widgets and visualisations, they still interact with the charts and callbacks you have defined.

Thinking back to the last lesson where we covered some basics of UI/Ux design, you might be able to see how using a series of containers can allow you to build interfaces up from relatively simple parts to more cohesive experiences for the user.

There are a large number of different layout widgets that you can use, which are available in detail on the ipywidgets library documentation.

The best way to get experience with designing layouts is to have a go yourself, so I have included some exercises and solutions below. Feel free to get creative and try some alternative approaches to learn how the layout widgets work, and how they can be used to create quite complex interfaces.

Exercise 1

Create a layout with the chart on top, the sliders below on the left, and the dropdown menu on the right.

Exercise 2

Create a layout with two charts, one showing each of the two results from the dropdown menu (e.g. linear and exponential) on top, and #the sliders below on the left. Do not show the dropdown menu.

Exercise 1 Solution

modal_dropdown = widgets.Dropdown(options=['Linear', 'Exponential'])
widget_container_v = widgets.VBox([m_slider, c_slider])
options_container = widgets.HBox([widget_container_v, modal_dropdown])

ui_container = widgets.VBox([plot_display, options_container])
display(ui_container)

Exercise 2 Solution

def generate_y(x, m, c):
    return m*x + c


def generate_y_exp(x, m, c):
    return x**m + c


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:')
widget_container = widgets.HBox([m_slider, c_slider])


exp_plot_display = widgets.Output()
lin_plot_display = widgets.Output()

with exp_plot_display:
    y = generate_y_exp(x, 2, 3)
    e_fig, e_ax = plt.subplots(figsize=(3, 3))
    exp_chart = e_ax.scatter(x, y)
    plt.show()

with lin_plot_display:
    y = generate_y(x, 2, 3)
    l_fig, l_ax = plt.subplots(figsize=(3, 3))
    lin_chart = l_ax.scatter(x, y)
    plt.show()

def update_chart_new(change):

    exp_plot_display.clear_output(wait=True)

    with exp_plot_display:
        y = generate_y_exp(x, m_slider.value, c_slider.value)
        e_fig, e_ax = plt.subplots(figsize=(3, 3))
        exp_chart = e_ax.scatter(x, y)
        plt.show()

    lin_plot_display.clear_output(wait=True)
    with lin_plot_display:
        y = generate_y(x, m_slider.value, c_slider.value)
        l_fig, l_ax = plt.subplots(figsize=(3, 3))
        lin_chart = l_ax.scatter(x, y)
        plt.show()



c_slider.observe(update_chart_new, 'value') 
m_slider.observe(update_chart_new, 'value')


charts_container = widgets.HBox([exp_plot_display, lin_plot_display])
two_charts_layout = widgets.VBox([charts_container, widget_container])
display(two_charts_layout)