#!/usr/bin/env python # coding: utf-8 # # Using Layout Templates # # As we showed in containers exercices, multiple widgets can be aranged together using `HBox` and `VBox`. It is also possible to use the flexible [GridBox](Widget%20Styling.ipynb#The-Grid-Layout) 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')) # ## 2x2 Grid # You can easily create a layout with 4 widgets aranged on 2x2 matrix using the `TwoByTwoLayout` widget: # In[ ]: 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 neighbouring cells # In[ ]: 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 # In[ ]: TwoByTwoLayout(top_left=top_left_button, bottom_left=bottom_left_button, bottom_right=bottom_right_button, merge=False) # 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_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 [bqplot](http://github.com/bloomberg/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 # `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) # 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) # In[ ]: AppLayout(header=header_button, left_sidebar=left_button, center=center_button, right_sidebar=right_button, footer=None) # In[ ]: AppLayout(header=None, left_sidebar=left_button, center=center_button, right_sidebar=right_button, footer=None) # In[ ]: AppLayout(header=header_button, left_sidebar=left_button, center=center_button, right_sidebar=None, footer=footer_button) # In[ ]: AppLayout(header=header_button, left_sidebar=None, center=center_button, right_sidebar=right_button, footer=footer_button) # In[ ]: AppLayout(header=header_button, left_sidebar=None, center=center_button, right_sidebar=None, footer=footer_button) # In[ ]: AppLayout(header=header_button, left_sidebar=left_button, center=None, right_sidebar=right_button, footer=footer_button) # 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'` (same as integer) 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']) # ## Grid layout # `GridspecLayout` is a N-by-M grid layout allowing for flexible layout definitions using an API similar to matplotlib's [GridSpec](https://matplotlib.org/tutorials/intermediate/gridspec.html#sphx-glr-tutorials-intermediate-gridspec-py). # # 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) 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, height='300px') 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, height='300px') 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, height='300px') 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') # ## Creating scatter plots using GridspecLayout # 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](https://bqplot.readthedocs.io/en/latest/) 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 # ## Style attributes # 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')