Share @ LinkedIn Facebook  ipywidgets, notebook
ipywidgets  - An In-depth Guide about Interactive Widgets in Jupyter Notebook [Python]

Interactive Widgets in Jupyter Notebook using ipywidgets

Jupyter notebook has become very famous nowadays and has been used by data scientists, researchers, students, developers worldwide for doing data analysis. Interactive dashboards and applications are getting quite common day by day. It has become a need of an hour to create interactive apps and dashboards so that others can analyze further using interactive widgets. Old ways of creating static applications and dashboards won't help much in this fierce competition of being everything interactive. Applications and dashboards designed as web app quite commonly use javascript for interactivity purposes which requires quite a good amount of javascript learning.

But what if you are a python developer and do not want to invest time in learning another programming language but still want to build interactive apps and dashboards?

Python has a library called ipywidgets which can help you with that. It provides a list of widgets quite common in web apps and dashboards like dropdown, checkbox, radio buttons, and many more. It'll let you code in pure python and will generate an interactive widget for you using javascript underneath. This way you won't need to learn javascript and continue to use python by just learning ipywidgets.

Every widget generated by ipywidgets consists of two components behind the scene:

  • Python - It runs in jupyter notebook kernel.
  • Javascript - It runs in browser.

ipywidgets can be easily integrated with a lot of other Python libraries like matplotlib, holoviews, bokeh, bqplot, ipyvolume, ipyleaflet, ipywebrtc, ipythreejs and many more.

Getting with Basic Widgets

ipywidgets provides a list of functions which can let us create widgets UI for any of our existing function. It'll create widgets by itself by looking at parameters of functions and create widgets UI with all parameters represented as one widget. It's a very good way to start using ipywidgets. We'll then learn in the future about customizing these widgets further.

We'll start by importing necessary functions which will help us creates widgets UI.

In [1]:
from ipywidgets import interact, interactive, fixed, interact_manual

Widgets using interact()

We can pass any of our functions as input to interact() function and it'll create widgets UI by looking at parameters of function with proper widgets for each parameter. We'll start converting a few basic functions.

In [2]:
def func1(x):
    return 5*x
In [3]:
interact(func1, x=10);

We can see that interact() function detects from our value x=10 that it should create a slider for this parameter. We can modify slider values and it'll recall function with new value and return new output.

In [4]:
interact(func1, x=2.2);
In [5]:
interact(func1, x=True);
In [6]:
interact(func1, x="Hello !!");

We can see from the above examples that based on the type of argument value passed to a parameter, it'll create a widget that is best suitable to represent that type of argument values. It created slider for float & integer, a checkbox for boolean, text box for a string.

We can even pass interact as a decorator and it'll just work the same.

In [7]:
@interact(x=10)
def func1(x):
    return 10 * x

fixed() to Prevent Widgets from Getting Created

Sometimes we have functions with more than one parameters and we might want to explore just a few parameters fixing values of other parameters. We can do that using fixed() which will prevent interact() from creating a widget.

In [8]:
def func2(a,b,c):
    return (a*b) + c
In [9]:
interact(func2, a=5, b=5, c=fixed(10));

We can see that the slider is only produced for parameters a and b only.

When we pass an integer value to a widget, it creates an integer slider in range(-value, +value *3) with a step value of 1. We can pass more than one value to a parameter in an interactive method to generate a widget according to our min, max values.

In [10]:
interact(func1, x=(1,5));
In [11]:
interact(func1, x=(1,5, 0.5));

Below table gives an overview of different widget abbreviations:

Keyword argumentWidget
`True` or `False`Checkbox
`'Hi there'`Text
`value` or `(min,max)` or `(min,max,step)` if integers are passedIntSlider
`value` or `(min,max)` or `(min,max,step)` if floats are passedFloatSlider
`['good','bad']` or `[('one', 1), ('two', 2)]`Dropdown
In [12]:
interact(func1, x=['good ','bad ']);
In [13]:
interact(func1, x=[('first', 100), ('second', 200)]);

Creating Basic Widget Objects

We can also create widget objects and pass them as a parameter to interact function according to our needs. It'll prevent interact from taking decision by itself and create an object which we might not need exactly. Let's start by creating a few widget objects.

ipywidgets has list of objects for creating widgets like IntSlider, FloatSlider, Dropdown, Text, Checkbox, etc. We can pass description parameter with each widget as it'll create a label next to the widget. We can even pass LateX as a string to description parameter and it'll create a formula as well.

In [14]:
import ipywidgets as widgets
In [15]:
int_slider = widgets.IntSlider(min=10, max=50, value=25, step=2, description="Integer Slider")
int_slider
In [16]:
float_slider = widgets.FloatSlider(min=10.0, max=50.0, value=25.0, step=2.5, description="Float Slider")
float_slider

Each widget has a list of a parameter which can be accessed by following dot notation on it.

In [17]:
float_slider.value
Out[17]:
25.0
In [18]:
float_slider.value = 28

We can access all available attributes of a widget by calling keys() method on it.

In [19]:
print(float_slider.keys)
['_dom_classes', '_model_module', '_model_module_version', '_model_name', '_view_count', '_view_module', '_view_module_version', '_view_name', 'continuous_update', 'description', 'description_tooltip', 'disabled', 'layout', 'max', 'min', 'orientation', 'readout', 'readout_format', 'step', 'style', 'value']
In [20]:
widgets.Checkbox(value=True, description="Check")
In [21]:
widgets.Button(description="Check")
In [22]:
widgets.Dropdown(options=["A","B","C","D"], description="Select Right Option")
In [23]:
widgets.Textarea(value="Please enter text here..")

We can pass the above-created widgets as a parameter value to interact() and it'll use those widgets instead of creating widgets by itself. We can prevent the default behavior of interact() function this way and force it to use our designed widgets according to our needs.

In [24]:
interact(func1, x= widgets.IntSlider(min=10, max=50, value=25, step=2, description="Integer Slider"));
In [25]:
interact(func1, x= widgets.FloatSlider(min=10.0, max=50.0, value=25.0, step=2.5, description="Float Slider"));

Widgets using interactive()

ipywidgets provides another function called interactive() to create UI of widgets by passing a function to it. Unlike interact()function, interactive() returns objects which does not displays widgets automatically. We need to use IPython function display() to display widgets UI as well as the output of a function.

In [26]:
from IPython.display import display

def func3(a,b,c):
    display((a+b)^c)

w = interactive(func3,  a=widgets.IntSlider(min=10, max=50, value=25, step=2),
                        b=widgets.IntSlider(min=10, max=50, value=25, step=2),
                        c=widgets.IntSlider(min=10, max=50, value=25, step=2),
                       )
display(w)
In [27]:
print(type(w))
<class 'ipywidgets.widgets.interaction.interactive'>

The interactive object is of type VBox which is a container object of ipywidgets. VBox can layout various widgets according to vertical layout. We can access its children as well as arguments.

In [28]:
w.children
Out[28]:
(IntSlider(value=25, description='a', max=50, min=10, step=2),
 IntSlider(value=25, description='b', max=50, min=10, step=2),
 IntSlider(value=25, description='c', max=50, min=10, step=2),
 Output())
In [29]:
w.kwargs
Out[29]:
{'a': 25, 'b': 25, 'c': 25}

We'll below create a simple example that modifies matplotlib plot according to the values of widgets. We'll be plotting a simple line with equation y=m*x + c. Our method will have parametersandcwhilex` will be random numbers array.

In [30]:
import matplotlib.pyplot as plt
import numpy as np

def plot(m,c):
    x = np.random.rand(10)
    y = m *x + c
    plt.plot(x,y)
    plt.show()
In [31]:
interactive(plot, m=(-10,10, 0.5), c=(-5,5,0.5))

Organizing Layout with interactive_output

interactive_output lets us layout widgets according to our need. interactive_output does not generate output UI but it lets us create UI, organize them in a box and pass them to it. This gives us more control over the layout of widgets.

In [32]:
m = widgets.FloatSlider(min=-5,max=5,step=0.5, description="Slope")
c = widgets.FloatSlider(min=-5,max=5,step=0.5, description="Intercept")

# An HBox lays out its children horizontally
ui = widgets.HBox([m, c])

def plot(m, c):
    x = np.random.rand(10)
    y = m *x + c
    plt.plot(x,y)
    plt.show()

out = widgets.interactive_output(plot, {'m': m, 'c': c})

display(out, ui)

Preventing Fluctuations using interact_manual() and continous_update

We can use a function like interact_manual() for preventing UI updates after widget values are changed. interact_manual() provides us with button pressing which will run a function after widget value changes with this new value. This will prevent UI from immediately updating and creating fluctuations.

In [33]:
def cpu_intensive_func(i):
    from time import sleep
    print('Sleeping...')
    sleep(1)
    print(i)

interact_manual(cpu_intensive_func,i=widgets.FloatSlider(min=1e4, max=1e6, step=1e4));

Another way to delay the update of UI after a change in widget value is by setting the continuous_update parameter to False. This will prevent a call to function as long as widget value is changing. Once a person leaves the mouse button, it'll then call a function to update UI with a new widget value.

In [34]:
interact(cpu_intensive_func,i=widgets.IntSlider(min=1e4, max=1e6, step=1e4, continuous_update=False));

Output Widget to Direct Results

Output widget can capture stdout, stderr, and output generated by widgets UI as well. We can use the Output widget with with statement as well to direct output to it.

In [35]:
out = widgets.Output(layout={"border":"1px solid green"})
out
In [36]:
with out:
    display(widgets.IntSlider())

out
In [37]:
out2 = widgets.Output(layout={"border":"1px solid black"})

with out2:
    print("Testing String Output")

Output of function can also be directed to Output widget using Output widgets as decorator.

In [38]:
out3 = widgets.Output(layout={"border":"1px solid red"})

@out3.capture()
def func1():
    print("Prints Inside Function")

func1()

We can also clear output widgets using clear_output() function on widget.

In [39]:
out3.clear_output()

interactive_output() function also generates output widgets as a result. We can also organize layout by connecting output with widgets.

In [40]:
a = widgets.IntSlider(description='a')

def f(a):
    print("Square of a : %f is %f"%(a, a*a))

out = widgets.interactive_output(f, {'a': a})
out.layout = {"border":"1px solid red"}

widgets.VBox([a, out])

Linking Widgets

We can link more widgets as well using linking functionality of ipywidgets so that if the value of one of the widget changes then another one also changes and synchronize with it.

ipywidgets lets us link objects in 2 ways:

  • Python Linking - link() and dlink() links widgets using jupyter python kernel
  • Javascript Linking = jslink() and jsdlink() links widgets only using javascript and not jupyter kernel involvement.
In [41]:
x = widgets.IntText()
y = widgets.IntSlider()

display(x,y)

two_way_link_python = widgets.link((x, 'value'), (y, 'value'))

The above link created between x and y is two ways which means that changes in the value of x will change the value of y and vice versa. Please note that we are linking the value property of both objects.

Below we have created a link using only javascript. It'll not require jupyter kernel running to work whereas the above example based on python linking will require jupyter kernel running to see changes.

In [42]:
x = widgets.IntText()
y = widgets.IntSlider()

display(x,y)

two_way_link_javascript = widgets.jslink((x, 'value'), (y, 'value'))

Above both links are two ways. If you want to create only a one-way link than you can do it using dlink() and jsdlink().

In [43]:
x = widgets.IntText()
y = widgets.IntSlider()

display(x,y)

one_way_link_python = widgets.dlink((x, 'value'), (y, 'value'))
In [44]:
x = widgets.IntText()
y = widgets.IntSlider()

display(x,y)

one_way_link_javascript = widgets.jsdlink((x, 'value'), (y, 'value'))

Above created both links are one direction from x to y only. It means that if we change the value of x then the value of y will change but value change in y will not reflect a change in x because it's one-way links.

We can unlink widgets as well if linking is not needed anymore. we can do it by calling unlink() method on the link object.

In [45]:
two_way_link_javascript.unlink()
two_way_link_python.unlink()
one_way_link_python.unlink()
one_way_link_javascript.unlink()

Widget Events

ipywidgets also lets us execute callback functions based on events. Events can be considered as clicking Button, changing slider values, changing text area value, etc. we might want to execute particular process when any kind of event is performed. ipywidgets provide such functionality by calling observe() method and passing it function which you want to execute as callback event. we'll explain events with few examples below. A button allows us to execute the same functionality by passing the method to it's on_click() method.

In [46]:
button = widgets.Button(description="Click Me!")
output = widgets.Output()

display(button, output)

@output.capture()
def on_button_clicked(b):
    print(type(b))
    print("Button clicked.")
    b.icon="warning"

button.on_click(on_button_clicked)
In [47]:
label = widgets.Label(value='Text Captured : ')
text = widgets.Text(description="Text")

def text_change(change):
    print(change)
    label.value = "Text Captured : "+change["new"]

text.observe(text_change, names='value')

display(label, text)
{'name': 'value', 'old': '', 'new': 'a', 'owner': Text(value='a', description='Text'), 'type': 'change'}
{'name': 'value', 'old': 'a', 'new': 'ab', 'owner': Text(value='ab', description='Text'), 'type': 'change'}
{'name': 'value', 'old': 'ab', 'new': 'abc', 'owner': Text(value='abc', description='Text'), 'type': 'change'}

We are passing method named text_change to observe() method of text widget so that any change in text value calls text_change() method. We are also passing names=value which will inform observe() that we need to capture changes in value property of Text widget. We are also printing what observe is passing to method when values in text widget changes. Please take a look that it passes the old and new value of widget as state capture for that change.

In [48]:
caption = widgets.Label(value='The values of slider is : ')
slider = widgets.IntSlider(min=-5, max=5, value=0, description='Slider')

def handle_slider_change(change):
    print(change)
    caption.value = 'The values of slider is : ' + str(change.new)

slider.observe(handle_slider_change, names='value')

display(caption, slider)
{'name': 'value', 'old': 0, 'new': -1, 'owner': IntSlider(value=-1, description='Slider', max=5, min=-5), 'type': 'change'}
{'name': 'value', 'old': -1, 'new': -2, 'owner': IntSlider(value=-2, description='Slider', max=5, min=-5), 'type': 'change'}
{'name': 'value', 'old': -2, 'new': -1, 'owner': IntSlider(value=-1, description='Slider', max=5, min=-5), 'type': 'change'}
{'name': 'value', 'old': -1, 'new': 0, 'owner': IntSlider(value=0, description='Slider', max=5, min=-5), 'type': 'change'}
{'name': 'value', 'old': 0, 'new': 1, 'owner': IntSlider(value=1, description='Slider', max=5, min=-5), 'type': 'change'}
{'name': 'value', 'old': 1, 'new': 2, 'owner': IntSlider(value=2, description='Slider', max=5, min=-5), 'type': 'change'}

Widget Layout and Styling.

We'll now explain various layout and styling available with ipywidgets. Various layout methods will let us organize a list of widgets according to different ways whereas the styling attribute of a widget will let us take care of the styling of widgets like height, width, color, button icon, etc.

Individual Widget Layout using layout attribute

Each widget exposes layout attribute which can be given information about its layout. CSS properties quite commonly used in styling and layout can be passed to the layout attribute.

In [49]:
b = widgets.Button(description='Sample Button',
           layout=widgets.Layout(width='30%', height='50px', border='5px dashed blue'))
b

The above button takes 30% of space of the page and 50px as height.

VBox and HBox for arranging widgets

We can utilize VBox and HBox layout objects to layout objects as vertical and horizontal respectively. Below we are creating 4 buttons and all have a width of 30%.

In [50]:
b1 = widgets.Button(description="Button1", layout=widgets.Layout(width="30%"))
b2 = widgets.Button(description="Button2", layout=widgets.Layout(width="30%"))
b3 = widgets.Button(description="Button3", layout=widgets.Layout(width="30%"))
b4 = widgets.Button(description="Button4", layout=widgets.Layout(width="30%"))

h1 = widgets.HBox(children=[b1,b2])
h1
In [51]:
h2 = widgets.HBox(children=[b3,b4])
h2
In [52]:
widgets.VBox(children=[h1,h2])

Box Layout

We can layout elements by simply calling Box layout object. It'll lay out all elements next to each other in a row. We can force layout based on passing size information as layout to Box objects to enforce our layout. We are creating Box which takes 30% of available page space and organized four buttons into it as a column. We can see that buttons are taking 30% width of Box layout object which itself takes 30% of the whole page.

In [53]:
layout = widgets.Layout(display='flex',
         flex_flow='column',
         border='solid',
         width='30%')

widgets.Box(children=[b1,b2,b3,b4], layout=layout)
In [54]:
layout = widgets.Layout(display='flex',
         flex_flow='row',
         border='solid',
         width='30%')

widgets.Box(children=[b1,b2,b3,b4], layout=layout)
In [55]:
item_layout = widgets.Layout(height='100px', min_width='40px')

items = [widgets.Button(layout=item_layout, description=str(i), button_style='success') for i in range(40)]

box_layout = widgets.Layout(overflow_x='scroll',
                    border='3px solid black',
                    width='500px',
                    height='',
                    flex_flow='row',
                    display='flex')
carousel = widgets.Box(children=items, layout=box_layout)
widgets.VBox([widgets.Label('Scroll horizontally:'), carousel])

GridBox for layout

GridBox is another object provided by ipywidgets which lets us organize things as grids. Grids are like a table with a specified number of rows and columns. We can pass a list of objects to GridBox and then force layout further using Layout object. We need to pass values of parameters grid_template_columns, grid_template_rows and grid_gap for creation of grids. The parameter grid_template_columns helps us specify what should be sizes of various columns whereas parameter grid_template_rows helps us specify sizes of rows in the grid. The parameter grid_gap has 2 values representing the gap between row boxes and column boxes respectively.

In [56]:
widgets.GridBox(children=[widgets.Button(description=str(i), layout=widgets.Layout(width='auto', height='auto'),
                         button_style='danger') for i in range(19)],

                layout=widgets.Layout(
                                    width='60%',
                                    grid_template_columns='100px 50px 100px 50px',
                                    grid_template_rows='80px auto 80px',
                                    grid_gap='12px 2px')
       )
In [57]:
widgets.GridBox(children=[widgets.Button(description=str(i), layout=widgets.Layout(width='auto', height='auto'),
                         button_style='danger') for i in range(16)],

        layout=widgets.Layout(
                            width='60%',
                            grid_template_columns='20% 20% 20% 20%',
                            grid_template_rows='80px 40px 80px 40px',
                            grid_gap='12px 2px')
       )

Exposed CSS properties

Below is a list of common CSS properties that are available as parameter names in widget objects.

Sizes
  • height
  • width
  • max_height
  • max_width
  • min_height
  • min_width
Display
  • visibility
  • display
  • overflow
Box model
  • border
  • margin
  • padding
Positioning
  • top
  • left
  • bottom
  • right
Flexbox
  • order
  • flex_flow
  • align_items
  • flex
  • align_self
  • align_content
  • justify_content
  • justify_items
Grid layout
  • grid_auto_columns
  • grid_auto_flow
  • grid_auto_rows
  • grid_gap
  • grid_template_rows
  • grid_template_columns
  • grid_template_areas
  • grid_row
  • grid_column
  • grid_area

Please feel free to explore various properties of ipywidgets widget objects to get to know about layout and styling more.

TwoByTwoLayout Layout Object

Another option provided by ipywidgets to organize widgets is TwoByTwoLayout. It gives us four places to put objects and if we do not provide a widget for any place then it keeps that place empty. We are organizing 4 buttons using this layout below. Please make a note that we have created a button with height and width as auto which stretch element in available space.

In [58]:
b1 = widgets.Button(description="Button1",
                    layout=widgets.Layout(width="auto", height="auto"), button_style="success")
b2 = widgets.Button(description="Button2",
                    layout=widgets.Layout(width="auto", height="auto"), button_style="primary")
b3 = widgets.Button(description="Button3",
                    layout=widgets.Layout(width="auto", height="auto"), button_style="info")
b4 = widgets.Button(description="Button4",
                    layout=widgets.Layout(width="auto", height="auto"), button_style="warning")

widgets.TwoByTwoLayout(top_left=b1,
                       top_right=b2,
                       bottom_left=b3,
                       bottom_right=b4)

If we don't provide any element then it'll stretch other elements to take its space.

In [59]:
widgets.TwoByTwoLayout(top_left=b1,
                       top_right=b2,
                       bottom_right=b4)

We can prevent automatic stretching of the widget by passing False to merge parameter.

In [60]:
widgets.TwoByTwoLayout(top_left=b1,
                       top_right=b2,
                       bottom_right=b4,
                       merge=False
                      )

AppLayout

AppLayout is another layout strategy that lets us organize widgets like a web app or desktop application layout.

In [61]:
header = widgets.Button(description="Header",
                    layout=widgets.Layout(width="auto", height="auto"), button_style="success")
footer = widgets.Button(description="Footer",
                    layout=widgets.Layout(width="auto", height="auto"), button_style="primary")
left = widgets.Button(description="Left",
                    layout=widgets.Layout(width="auto", height="auto"), button_style="info")
right = widgets.Button(description="Right",
                    layout=widgets.Layout(width="auto", height="auto"), button_style="warning")
center = widgets.Button(description="Center",
                    layout=widgets.Layout(width="auto", height="auto"), button_style="danger")
In [62]:
widgets.AppLayout(header=header,
          left_sidebar=left,
          center=center,
          right_sidebar=right,
          footer=footer)

It'll merge widgets if widget for any place is not provided

In [63]:
widgets.AppLayout(header=header,
          left_sidebar=left,
          center=center,
          footer=footer)
In [64]:
widgets.AppLayout(header=header,
          left_sidebar=left,
          center=center,
          right_sidebar=right,)
In [65]:
widgets.AppLayout(header=header,
          left_sidebar=left,
          right_sidebar=right,
          footer=footer)

GridspecLayout

It's another way of laying out widgets which is the same as matplotlib gridspec. It lets us define a grid with a number of rows and columns. We can then put widgets by selecting a single row & column or span them to more rows and columns as well. We'll explain it with a few examples below.

In [66]:
grid = widgets.GridspecLayout(5, 4)

for i in range(5):
    for j in range(4):
        grid[i, j] = widgets.Button(description="[%d, %d]"%(i,j), button_style="primary")
grid

We can span widgets to several rows and columns as well.

In [67]:
grid = widgets.GridspecLayout(6, 4)

grid[0,1:] = widgets.Button(description="Button1", layout=widgets.Layout(height="auto", width="auto"), button_style="success")

grid[-1,1:] = widgets.Button(description="Button2", layout=widgets.Layout(height="auto", width="auto"), button_style="danger")

grid[:,0] = widgets.Button(description="Button3", layout=widgets.Layout(height="auto", width="auto"), button_style="primary")

grid[1:-1,-1] = widgets.Button(description="Button4", layout=widgets.Layout(height="auto", width="auto"), button_style="primary")

grid[3:-1,1:-1] = widgets.Button(description="Button5", layout=widgets.Layout(height="auto", width="auto"), button_style="warning")

grid[1:3,1:-1] = widgets.Button(description="Button6", layout=widgets.Layout(height="auto", width="auto"), button_style="info")

grid

style attribute

We can pass CSS styles to the style attribute of a widget and it'll apply that CSS style to a widget. We already applied button_style attribute in the above examples. We can check a list of available style attributes in a widget by calling keys on its style attribute.

In [68]:
b = widgets.Button(description='Stylish Button')
b.style.button_color = 'tomato'
b
In [69]:
b.style.keys
Out[69]:
['_model_module',
 '_model_module_version',
 '_model_name',
 '_view_count',
 '_view_module',
 '_view_module_version',
 '_view_name',
 'button_color',
 'font_weight']
In [70]:
slider= widgets.FloatSlider(description="Stylish Slider")
slider.style.handle_color="lawngreen"
slider
In [71]:
slider.style.keys
Out[71]:
['_model_module',
 '_model_module_version',
 '_model_name',
 '_view_count',
 '_view_module',
 '_view_module_version',
 '_view_name',
 'description_width',
 'handle_color']
In [72]:
progress_bar = widgets.FloatProgress(value=3, min=0, max=10)
progress_bar.style.bar_color = "purple"
progress_bar
In [73]:
progress_bar.style.keys
Out[73]:
['_model_module',
 '_model_module_version',
 '_model_name',
 '_view_count',
 '_view_module',
 '_view_module_version',
 '_view_name',
 'bar_color',
 'description_width']

This ends our tutorial on giving an introduction on how to use interactive widgets in a jupyter notebook using ipywidgets. Please feel free to let us know your views. We have tried to cover as many topics as possible but the library is quite big and needs further exploration to better understand things further. We'll be creating more tutorials on using ipywidgets with other python libraries.

References