This section presents how to layout and style Jupyter interactive widgets to build rich and reactive widget-based applications.
Every Jupyter widget has two attributes to customize layout and styling of the widget. They are layout
and style
attributes.
style
attribute¶The style
attribute is used to expose non-layout related styling attributes of widgets. For most widgets, the only style that can be modified is description_width
, which is the width of the description label for the widget.
However, a few widgets have additional style settings, as described below.
from ipywidgets import Button, ButtonStyle
b2 = Button(description='Custom color', style=dict(button_color='lightgreen'))
b2
b2.style.button_color = 'yellow'
You can get a list of the style attributes for a widget with the keys
property.
b2.style.keys
Like the layout
attribute, widget styles can be assigned to other widgets.
b3 = Button(description='Another button', style=b2.style)
b3
Widget styling attributes are specific to each widget type.
from ipywidgets import IntSlider
s1 = IntSlider(description='Blue handle')
s1.style.handle_color = 'lightblue'
s1
There is a list of all style keys.
button_style
and bar_style
attributes¶These attributes let you style some widgets with pre-defined settings that are theme aware
. These properties affect both background color and text color of widgets. These attributes are available for the widgets listed below. Available options for these styles are success
, info
, warning
, danger
. Buttons also have option primary
b4 = Button(description='Yet another button')
b4
b4.button_style = 'warning'
Note that setting the style
of a button overrides the button_style
:
b4.style.button_color = 'red' # Makes the color red
b4.button_style = 'success' # Does not turn the color green because the color has been explicitly set in the style
layout
attribute: the foundation of widget layout¶Jupyter interactive widgets have a layout
attribute exposing a number of CSS properties that impact how widgets are laid out.
height
width
max_height
max_width
min_height
min_width
visibility
display
overflow
border
margin
padding
top
left
bottom
right
object_fit
object_position
order
flex_flow
align_items
flex
align_self
align_content
justify_content
justify_items
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
You may have noticed that certain CSS properties such as margin-[top/right/bottom/left]
seem to be missing. The same holds for padding-[top/right/bottom/left]
etc.
In fact, you can atomically specify [top/right/bottom/left]
margins via the margin
attribute alone by passing the string '100px 150px 100px 80px'
for a respectively top
, right
, bottom
and left
margins of 100
, 150
, 100
and 80
pixels.
Similarly, the flex
attribute can hold values for flex-grow
, flex-shrink
and flex-basis
. The border
attribute is a shorthand property for border-width
, border-style (required)
, and border-color
.
Layout
example¶The following example shows how to resize a Button
so that its views have a height of 80px
and a width of 50%
of the available space. It also includes an example of setting a CSS property that requires multiple values (a border, in this case):
from ipywidgets import Button, Layout
b1 = Button(description='(50% width, 80px height) button',
layout=Layout(width='50%', height='80px', border='2px dotted blue'))
b1
The layout
property can be shared between multiple widgets and assigned directly.
Button(description='Another button with the same layout', layout=b1.layout)
b1.layout.width = '30%'
In the cell below, make the button's border solid and green and make its width 70 pixels.
b1.layout. # fill this in, might take more than one line
If you want to learn more about CSS layout after this tutorial, take a look at this excellent set of articles on CSS layout at MDN. The Flexbox and Grid articles each have links to more extensive guides at the end of the article.
The HBox
and VBox
classes are special cases of the Box
widget.
The Box
widget enables the entire CSS flexbox spec as well as the Grid layout spec, enabling rich reactive layouts in the Jupyter notebook. It aims at providing an efficient way to lay out, align and distribute space among items in a container.
Again, the whole flexbox spec is exposed via the layout
attribute of the container widget (Box
) and the contained items. One may share the same layout
attribute among all the contained items.
We will revisit more of the flexbox spec later in this tutorial. For now, let's look at a couple of examples using VBox
and HBox
.
The VBox
and HBox
helper classes provide simple defaults to arrange child widgets in vertical and horizontal boxes.
Most of the core-widgets have default heights and widths that tile well together. This allows simple layouts based on the HBox
and VBox
helper functions to align naturally.
50%
of the available space.¶from ipywidgets import Layout, Button, VBox
items_layout = Layout(width='auto') # override the default width of the button to 'auto' to let the button grow
box_layout = Layout(border='solid',
width='50%')
words = ['correct', 'horse', 'battery', 'staple']
items = [Button(description=word, layout=items_layout, button_style='danger') for word in words]
box = VBox(children=items, layout=box_layout)
box
The form is a VBox
of width '50%'. Each row in the VBox is an HBox, that justifies the content with space between.
Note that the labels and interactive elements are nicely aligned.
from ipywidgets import Layout, HBox, VBox, Button, FloatText, Textarea, Dropdown, Label, IntSlider
# space-between divides the whitespace evenly between elements
form_item_layout = Layout(justify_content='space-between')
form_items = [
HBox([Label(value='Storage capacity'), IntSlider(min=4, max=512)], layout=form_item_layout),
HBox([Label(value='Egg style'),
Dropdown(options=['Scrambled', 'Sunny side up', 'Over easy'])], layout=form_item_layout),
HBox([Label(value='Ship size'),
FloatText()], layout=form_item_layout),
HBox([Label(value='Information'),
Textarea()], layout=form_item_layout)
]
form = VBox(form_items, layout=Layout(
border='2px solid gray', padding='10px',
align_items='stretch', width='50%')
)
form
The GridBox
class is a special case of the Box
widget.
The whole grid layout spec is exposed via the layout
attribute of the container widget (Box
) and the contained items. One may share the same layout
attribute among all the contained items.
This tutorial focuses on the higher-level layout options that are based on the grid spec:
A more detailed description of the Grid layout is available. The Grid layout guide on MDN is also excellent.
# Utils widgets
from ipywidgets import Button, Layout, jslink, IntText, IntSlider
def create_expanded_button(description, button_style):
return Button(description=description, button_style=button_style,
layout=Layout(height='auto', width='auto'))
top_left_button = create_expanded_button("Top left", 'info')
top_right_button = create_expanded_button("Top right", 'success')
bottom_left_button = create_expanded_button("Bottom left", 'danger')
bottom_right_button = create_expanded_button("Bottom right", 'warning')
top_left_text = IntText(description='Top left', layout=Layout(width='auto', height='auto'))
top_right_text = IntText(description='Top right', layout=Layout(width='auto', height='auto'))
bottom_left_slider = IntSlider(description='Bottom left', layout=Layout(width='auto', height='auto'))
bottom_right_slider = IntSlider(description='Bottom right', layout=Layout(width='auto', height='auto'))
You can easily create a layout with 4 widgets arranged in a 2x2 grid using the TwoByTwoLayout
widget:
2x2 Grid
from ipywidgets import TwoByTwoLayout
TwoByTwoLayout(top_left=top_left_button,
top_right=top_right_button,
bottom_left=bottom_left_button,
bottom_right=bottom_right_button)
If you don't define a widget for some of the slots, the layout will automatically re-configure itself by merging neighboring cells
TwoByTwoLayout(top_left=top_left_button,
bottom_left=bottom_left_button,
bottom_right=bottom_right_button)
You can pass merge=False
in the argument of the TwoByTwoLayout
constructor if you don't want this behavior
layout_2x2 = TwoByTwoLayout(top_left=top_left_button,
bottom_left=bottom_left_button,
bottom_right=bottom_right_button,
merge=False)
layout_2x2
You can access the widgets in the grid:
layout_2x2.bottom_right.button_style = 'primary'
You can add a missing widget even after the layout initialization:
layout_2x2.top_right = top_right_button
layout_2x2.grid_gap = '10px'
You can easily create more complex layouts with custom widgets. For example, you can use a bqplot Figure widget to add plots:
import bqplot as bq
import numpy as np
size = 100
np.random.seed(0)
x_data = range(size)
y_data = np.random.randn(size)
y_data_2 = np.random.randn(size)
y_data_3 = np.cumsum(np.random.randn(size) * 100.)
x_ord = bq.OrdinalScale()
y_sc = bq.LinearScale()
bar = bq.Bars(x=np.arange(10), y=np.random.rand(10), scales={'x': x_ord, 'y': y_sc})
ax_x = bq.Axis(scale=x_ord)
ax_y = bq.Axis(scale=y_sc, tick_format='0.2f', orientation='vertical')
fig = bq.Figure(marks=[bar], axes=[ax_x, ax_y], padding_x=0.025, padding_y=0.025,
layout=Layout(width='auto', height='90%'))
from ipywidgets import FloatSlider
max_slider = FloatSlider(min=0, max=10, default_value=2, description="Max: ",
layout=Layout(width='auto', height='auto'))
min_slider = FloatSlider(min=-1, max=10, description="Min: ",
layout=Layout(width='auto', height='auto'))
app = TwoByTwoLayout(top_left=min_slider,
bottom_left=max_slider,
bottom_right=fig,
align_items="center",
height='400px')
jslink((y_sc, 'max'), (max_slider, 'value'))
jslink((y_sc, 'min'), (min_slider, 'value'))
jslink((min_slider, 'max'), (max_slider, 'value'))
jslink((max_slider, 'min'), (min_slider, 'value'))
max_slider.value = 1.5
app
AppLayout
is a widget layout template that allows you to create an application-like widget arrangements. It consists of a header, a footer, two sidebars and a central pane:
from ipywidgets import AppLayout, Button, Layout
header_button = create_expanded_button('Header', 'success')
left_button = create_expanded_button('Left', 'info')
center_button = create_expanded_button('Center', 'warning')
right_button = create_expanded_button('Right', 'info')
footer_button = create_expanded_button('Footer', 'success')
AppLayout(header=header_button,
left_sidebar=left_button,
center=center_button,
right_sidebar=right_button,
footer=footer_button)
However with the automatic merging feature, it's possible to achieve many other layouts:
AppLayout(header=header_button,
left_sidebar=left_button,
center=center_button,
right_sidebar=right_button,
footer=None)
AppLayout(header=header_button,
left_sidebar=left_button,
center=center_button,
right_sidebar=None,
footer=footer_button)
In the cell below make an AppLayout
with no sidebars.
# %load solutions/layout/applayout-no-sides.py
You can also modify the relative and absolute widths and heights of the panes using pane_widths
and pane_heights
arguments. Both accept a sequence of three elements, each of which is either an integer (equivalent to the weight given to the row/column) or a string in the format '1fr'
(denoting one portion of the free space available) or '100px'
(absolute size).
app = AppLayout(header=header_button,
left_sidebar=left_button,
center=center_button,
right_sidebar=right_button,
footer=footer_button)
app
app.pane_widths = ['200px', 3, 1]
app.pane_widths = ['200px', '3fr', '1fr']
app.pane_heights = ['100px', 5, 1]
app.left_sidebar.description = 'New Left'
AppLayout(header=header_button,
left_sidebar=left_button,
center=center_button,
right_sidebar=right_button,
footer=footer_button,
pane_widths=[3, 3, 1],
pane_heights=[1, 5, '60px'])
Make an AppLayout
in which there is a header, but no footer or right sidebar. Make the center the bqplot with slider demo above.
# %load solutions/layout/slider-bqplot-sliders-app.py
For an additional challenge, make the header button reset the sliders to their original position by using an event handler.
GridspecLayout
is an M-by-N grid layout allowing for flexible layout definitions using an API similar to matplotlib's GridSpec.
You can use GridspecLayout
to define a simple regularly-spaced grid. For example, to create a 4x3 layout:
M x N grid of buttons
from ipywidgets import GridspecLayout
grid = GridspecLayout(4, 3)
for i in range(4):
for j in range(3):
grid[i, j] = create_expanded_button('Button {} - {}'.format(i, j), 'warning')
grid
Spanning range of columns and/or rows
To make a widget span several columns and/or rows, you can use slice notation:
grid = GridspecLayout(4, 3)
grid[:3, 1:] = create_expanded_button('One', 'success')
grid[:, 0] = create_expanded_button('Two', 'info')
grid[3, 1] = create_expanded_button('Three', 'warning')
grid[3, 2] = create_expanded_button('Four', 'danger')
grid
You can still change properties of the widgets stored in the grid, using the same indexing notation.
grid[0, 0].description = "I am the blue one"
Note: It's enough to pass an index of one of the grid cells occupied by the widget of interest.
grid[3, 1] = create_expanded_button('New button!!', 'danger')
grid[:3, 1:] = create_expanded_button('I am new too!!!!!', 'warning')
grid[2, 2].description = 'A better label'
In this example, we will demonstrate how to use GridspecLayout
and bqplot
widget to create a multipanel scatter plot. To run this example you will need to install the bqplot package.
import bqplot as bq
import numpy as np
from ipywidgets import GridspecLayout, Button, Layout
n_features = 3
data = np.random.randn(100, n_features)
data[:50, 2] += 4 * data[:50, 0] **2
data[50:, :] += 4
A = np.random.randn(n_features, n_features)/5
data = np.dot(data,A)
scales_x = [bq.LinearScale() for i in range(n_features)]
scales_y = [bq.LinearScale() for i in range(n_features)]
gs = GridspecLayout(n_features, n_features)
for i in range(n_features):
for j in range(n_features):
if i != j:
sc_x = scales_x[j]
sc_y = scales_y[i]
scatt = bq.Scatter(x=data[:, j], y=data[:, i], scales={'x': sc_x, 'y': sc_y})
gs[i, j] = bq.Figure(marks=[scatt], layout=Layout(width='auto', height='auto'),
fig_margin=dict(top=5, bottom=5, left=5, right=5), background_style={'fill':'#f5f5ff'})
else:
sc_x = scales_x[j]
sc_y = bq.LinearScale()
hist = bq.Hist(sample=data[:,i], scales={'sample': sc_x, 'count': sc_y})
gs[i, j] = bq.Figure(marks=[hist], layout=Layout(width='auto', height='auto'),
fig_margin=dict(top=5, bottom=5, left=5, right=5), background_style={'fill':'#f5f5ff'})
gs
from ipywidgets import Button, GridBox, Layout, ButtonStyle
GridBox(children=[Button(description=str(i), layout=Layout(width='auto', height='auto'),
style=ButtonStyle(button_color='darkseagreen')) for i in range(12)
],
layout=Layout(
width='50%',
grid_template_columns='100px 50px 100px',
grid_template_rows='80px auto 80px',
grid_gap='5px 10px')
)
Add more buttons
Modify the code above to place more buttons in the GridBox
(do not modify the layout). Any number of buttons larger than 9 is fine.
The grid template defines a 3x3 grid. If additional children are placed in the grid their properties are determined by the layout properties grid_auto_columns
, grid_auto_rows
and grid_auto_flow
properties.
Set grid_auto_rows="10px"
and rerun the example with more than 9 buttons.
Set grid_auto_rows
so that the automatically added rows have the same format as the templated rows.
The grid can also be set up using a description words. The layout below defines a grid with 4 columns and 3 rows. The first row is a header, the bottom row is a footer, and the middle row has content in the first two columns, then an empty cell, followed by a sidebar.
Widgets are assigned to each of these areas by setting the widgets's layout grid_area
to the name of the area.
"header header header header"
"main main . sidebar "
"footer footer footer footer"
header = Button(description='Header',
layout=Layout(width='auto', height='auto', grid_area='header'),
style=ButtonStyle(button_color='lightblue'))
main = Button(description='Main',
layout=Layout(width='auto', height='auto', grid_area='main'),
style=ButtonStyle(button_color='moccasin'))
sidebar = Button(description='Sidebar',
layout=Layout(width='auto', height='auto', grid_area='sidebar'),
style=ButtonStyle(button_color='salmon'))
footer = Button(description='Footer',
layout=Layout(width='auto', height='auto', grid_area='footer'),
style=ButtonStyle(button_color='olive'))
GridBox(children=[header, main, sidebar, footer],
layout=Layout(
width='50%',
align_items='stretch',
grid_template_rows='auto auto auto',
grid_template_columns='25% 25% 25% 25%',
grid_template_areas='''
"header header header header"
"main main . sidebar"
"footer footer footer footer"
''')
)
< Layout and Styling of Jupyter widgets | Contents | OPTIONAL - Widget label styling >