As we showed in containers exercises, multiple widgets can be arranged together using HBox
and VBox
. It is also possible to use the flexible GridBox specification. However, use of the specification involves some understanding of CSS properties and may impose a steep learning curve. Here, we will describe layout templates built on top of GridBox
that simplify creation of common widget layouts.
In [ ]:
# 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 matrix using the TwoByTwoLayout
widget:
In [ ]:
from ipywidgets import TwoByTwoLayout
layout = dict(height='300px')
TwoByTwoLayout(top_left=top_left_button,
top_right=top_right_button,
bottom_left=bottom_left_button,
bottom_right=bottom_right_button,
layout=layout)
If you don't define a widget for some of the slots, the layout will automatically re-configure itself by merging neighbouring cells
In [ ]:
TwoByTwoLayout(top_left=top_left_button,
bottom_left=bottom_left_button,
bottom_right=bottom_right_button,
layout=layout)
You can pass merge=False
in the argument of the TwoByTwoLayout
constructor if you don't want this behavior
In [ ]:
TwoByTwoLayout(top_left=top_left_button,
bottom_left=bottom_left_button,
bottom_right=bottom_right_button,
merge=False,
layout=layout)
You can add a missing widget even after the layout initialization:
In [ ]:
layout_2x2 = TwoByTwoLayout(top_left=top_left_button,
bottom_left=bottom_left_button,
bottom_right=bottom_right_button,
layout=layout)
layout_2x2
In [ ]:
layout_2x2.top_right = top_right_button
You can also use the linking feature of widgets to update some property of a widget based on another widget:
In [ ]:
app = TwoByTwoLayout(top_left=top_left_text, top_right=top_right_text,
bottom_left=bottom_left_slider, bottom_right=bottom_right_slider)
link_left = jslink((app.top_left, 'value'), (app.bottom_left, 'value'))
link_right = jslink((app.top_right, 'value'), (app.bottom_right, 'value'))
app.bottom_right.value = 30
app.top_left.value = 25
app
You can easily create more complex layouts with custom widgets. For example, you can use a bqplot Figure widget to add plots:
In [ ]:
import bqplot as bq
import numpy as np
In [ ]:
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%'))
In [ ]:
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='700px')
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 consist of a header, a footer, two sidebars and a central pane:
In [ ]:
from ipywidgets import AppLayout, Button, Layout
In [ ]:
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')
In [ ]:
AppLayout(header=header_button,
left_sidebar=left_button,
center=center_button,
right_sidebar=right_button,
footer=footer_button,
layout=layout)
However with the automatic merging feature, it's possible to achieve many other layouts:
In [ ]:
AppLayout(header=None,
left_sidebar=None,
center=center_button,
right_sidebar=None,
footer=None,
layout=layout)
In [ ]:
AppLayout(header=header_button,
left_sidebar=left_button,
center=center_button,
right_sidebar=right_button,
footer=None,
layout=layout)
In [ ]:
AppLayout(header=None,
left_sidebar=left_button,
center=center_button,
right_sidebar=right_button,
footer=None,
layout=layout)
In [ ]:
AppLayout(header=header_button,
left_sidebar=left_button,
center=center_button,
right_sidebar=None,
footer=footer_button,
layout=layout)
In [ ]:
AppLayout(header=header_button,
left_sidebar=None,
center=center_button,
right_sidebar=right_button,
footer=footer_button,
layout=layout)
In [ ]:
AppLayout(header=header_button,
left_sidebar=None,
center=center_button,
right_sidebar=None,
footer=footer_button,
layout=layout)
In [ ]:
AppLayout(header=header_button,
left_sidebar=left_button,
center=None,
right_sidebar=right_button,
footer=footer_button,
layout=layout)
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).
In [ ]:
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'],
layout=layout)
GridspecLayout
is a N-by-M 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:
In [ ]:
from ipywidgets import GridspecLayout
grid = GridspecLayout(4, 3, layout=layout)
for i in range(4):
for j in range(3):
grid[i, j] = create_expanded_button('Button {} - {}'.format(i, j), 'warning')
grid
To make a widget span several columns and/or rows, you can use slice notation:
In [ ]:
grid = GridspecLayout(4, 3, layout=layout)
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.
In [ ]:
grid = GridspecLayout(4, 3, layout=layout)
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
In [ ]:
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. Slices are not supported in this context.
If there is already a widget that conflicts with the position of the widget being added, it will be removed from the grid:
In [ ]:
grid = GridspecLayout(4, 3, layout=layout)
grid[:3, 1:] = create_expanded_button('One', 'info')
grid[:, 0] = create_expanded_button('Two', 'info')
grid[3, 1] = create_expanded_button('Three', 'info')
grid[3, 2] = create_expanded_button('Four', 'info')
grid
In [ ]:
grid[3, 1] = create_expanded_button('New button!!', 'danger')
Note: Slices are supported in this context.
In [ ]:
grid[:3, 1:] = create_expanded_button('I am new too!!!!!', 'warning')
In this examples, 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.
For example, you can use the following snippet to obtain a scatter plot across multiple dimensions:
In [ ]:
import bqplot as bq
import numpy as np
from ipywidgets import GridspecLayout, Button, Layout
n_features = 5
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}, default_size=1)
gs[i, j] = bq.Figure(marks=[scatt], layout=Layout(width='auto', height='auto'),
fig_margin=dict(top=0, bottom=0, left=0, right=0))
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=0, bottom=0, left=0, right=0))
gs
You can specify extra style properties to modify the layout. For example, you can change the size of the whole layout using the height
and width
arguments.
In [ ]:
AppLayout(header=None,
left_sidebar=left_button,
center=center_button,
right_sidebar=right_button,
footer=None,
height="200px", width="50%")
The gap between the panes can be increase or decreased with grid_gap
argument:
In [ ]:
AppLayout(header=None,
left_sidebar=left_button,
center=center_button,
right_sidebar=right_button,
footer=None,
height="200px", width="50%",
grid_gap="10px")
Additionally, you can control the alignment of widgets within the layout using justify_content
and align_items
attributes:
In [ ]:
from ipywidgets import Text, HTML
TwoByTwoLayout(top_left=top_left_button, top_right=top_right_button,
bottom_right=bottom_right_button,
justify_items='center',
width="50%",
align_items='center')
For other alignment options it's possible to use common names (top
and bottom
) or their CSS equivalents (flex-start
and flex-end
):
In [ ]:
TwoByTwoLayout(top_left=top_left_button, top_right=top_right_button,
bottom_right=bottom_right_button,
justify_items='center',
width="50%",
align_items='top')