##// END OF EJS Templates
Merge pull request #6051 from chronitis/interact-widget-on-demand...
Merge pull request #6051 from chronitis/interact-widget-on-demand Interact on_demand option

File last commit:

r17515:bd0a408c
r17945:a10f9b08 merge
Show More
Widget Styling.ipynb
1386 lines | 34.9 KiB | text/plain | TextLexer

Index - [Back](Widget Events.ipynb) - [Next](Custom Widget - Hello World.ipynb)

In [ ]:
%%html
<style>
.example-container { background: #999999; padding: 2px; min-height: 100px; }
.example-container.sm { min-height: 50px; }
.example-box { background: #9999FF; width: 50px; height: 50px; text-align: center; vertical-align: middle; color: white; font-weight: bold; margin: 2px;}
.example-box.med { width: 65px; height: 65px; }   
.example-box.lrg { width: 80px; height: 80px; }   
</style>

Widget Styling

CSS

Since the representation of the widget you see is a browser element, Cascading Style Sheets (CSS) are used for styling. Widgets have a set_css method that allows you to add and remove CSS properties from your elements. The following example shows had set_css can be used to set the background color of a TextWidget.

In [ ]:
from IPython.html import widgets
text = widgets.TextWidget(value="Hello World!")
text.set_css('background', 'lime')
text 

Color codes

In the example above, the color lime is specified by name. CSS also supports specifying colors by a 3 byte hexadecimal string. The first byte is red, second green, and third blue (RGB). The following example sets the TextWidget's background to blue.

In [ ]:
text.set_css('background', '#0000FF')

Forecolor

In CSS the font color is color.

In [ ]:
text.set_css('color', '#FFFFFF')

Size

CSS is also used to set the height and width of controls. The set_css method also can accept a single dictionary with multiple CSS properties (as seen below).

In [ ]:
btn = widgets.ButtonWidget()
btn.set_css({
    'width': '100px',
    'height': '100px',
    'background': 'red',
})
btn

Removing

To remove the styling, you can call set_css again, but use an empty string instead of a color value.

In [ ]:
btn.set_css('background', '')

For more information about what can be done with CSS, please refer to the Mozilla Developer Network's series on it.

Parent/child relationships

To display widget A inside widget B, widget A must be a child of widget B. Only one instance of any particular widget can be child of another (this limitation will be removed in IPython 3.0). In other words, widget A cannot have widget B listed twice in it's list of children.

Widgets that can contain other widgets have a children attribute. This attribute can be set via a keyword argument in the widget's constructor or after construction. Calling display on an object with children automatically displays those children, too.

In [ ]:
from IPython.display import display

float_range = widgets.FloatSliderWidget()
string = widgets.TextWidget(value='hi')
container = widgets.ContainerWidget(children=[float_range, string])

container.set_css('border', '3px dotted red')
display(container) # Displays the `container` and all of it's children.

After the parent is displayed

Children can be added to parents after the parent has been displayed. The parent is responsible for rendering its children.

In [ ]:
container = widgets.ContainerWidget()
container.set_css('border', '3px dotted red')
display(container)

int_range = widgets.IntSliderWidget()
container.children=[int_range]

Fancy containers

If you need to display a more complicated set of widgets, there are specialized containers that you can use. To display multiple sets of widgets, you can use an AccordionWidget or a TabWidget in combination with one ContainerWidget per set of widgets (as seen below). The "pages" of these widgets are their children. To set the titles of the pages, one must call set_title after the widget has been displayed.

AccordionWidget

In [ ]:
name1 = widgets.TextWidget(description='Location:')
zip1 = widgets.BoundedIntTextWidget(description='Zip:', min=0, max=99999)
page1 = widgets.ContainerWidget(children=[name1, zip1])

name2 = widgets.TextWidget(description='Location:')
zip2 = widgets.BoundedIntTextWidget(description='Zip:', min=0, max=99999)
page2 = widgets.ContainerWidget(children=[name2, zip2])

accord = widgets.AccordionWidget(children=[page1, page2])
display(accord)

accord.set_title(0, 'From')
accord.set_title(1, 'To')

TabWidget

In [ ]:
name = widgets.TextWidget(description='Name:')
color = widgets.DropdownWidget(description='Color:', values=['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet'])
page1 = widgets.ContainerWidget(children=[name, color])

age = widgets.IntSliderWidget(description='Age:', min=0, max=120, value=50)
gender = widgets.RadioButtonsWidget(description='Gender:', values=['male', 'female'])
page2 = widgets.ContainerWidget(children=[age, gender])

tabs = widgets.TabWidget(children=[page1, page2])
display(tabs)

tabs.set_title(0, 'Name')
tabs.set_title(1, 'Details')

PopupWidget

Unlike the other two special containers, the PopupWidget is only designed to display one set of widgets. The PopupWidget can be used to display widgets outside of the widget area.

In [ ]:
counter = widgets.IntTextWidget(description='Counter:')
popup = widgets.PopupWidget(children=[counter], description='Popup Demo', button_text='Popup Button')
display(popup)
In [ ]:
counter.value += 1
In [ ]:
 
In [ ]:
 
In [ ]:
 
In [ ]:
 
In [ ]:
 
In [ ]:
 
In [ ]:
 
In [ ]:
 
In [ ]:
 
In [ ]:
 
In [ ]:
 
In [ ]:
 
In [ ]:
 
In [ ]:
 
In [ ]:
counter.value += 1
In [ ]:
popup.close()

Alignment

Most widgets have a description attribute, which allows a label for the widget to be defined. The label of the widget has a fixed minimum width. The text of the label is always right aligned and the widget is left aligned:

In [ ]:
display(widgets.TextWidget(description="a:"))
display(widgets.TextWidget(description="aa:"))
display(widgets.TextWidget(description="aaa:"))

If a label is longer than the minimum width, the widget is shifted to the right:

In [ ]:
display(widgets.TextWidget(description="a:"))
display(widgets.TextWidget(description="aa:"))
display(widgets.TextWidget(description="aaa:"))
display(widgets.TextWidget(description="aaaaaaaaaaaaaaaaaa:"))

If a description is not set for the widget, the label is not displayed:

In [ ]:
display(widgets.TextWidget(description="a:"))
display(widgets.TextWidget(description="aa:"))
display(widgets.TextWidget(description="aaa:"))
display(widgets.TextWidget())

DOM Classes

IPython defines a large number of DOM (document object model) classes that you can apply to your widgets. Applying a DOM class causes all of the CSS associated with that class to be applied to the element. Classes can be applied and removed using the add_class and remove_class methods after a widget has been displayed. The majority of DOM classes defined by IPython are actually Bootstrap classes. For more information on Bootstrap classes and CSS, please refer to Bootstrap's website.

Path dependent

Both add_class and remove_class allow you to use CSS selectors to pick which sub elements of your widget get styled. Because of this, the add_class and remove_class methods are path dependent (order specific). The following example shows the same three calls made in three different orders and the resulting output. All three differ.

In [ ]:
%%html
<style>
    div.cube { display: inline; padding: 5px; }
    div.red { background: red; }
    div.blue { background: blue; }
</style>
In [ ]:
from IPython.html import widgets
from IPython.display import display
html = '<br />'.join([''.join(['<div class="cube">x</div>' for i in range(8)]) for j in range(8)])
widget = [widgets.HTMLWidget(value=html) for i in range(3)]

display(widget[0])
widget[0].add_class('red', 'div.cube:nth-child(even)')
widget[0].remove_class('red', 'div.red:nth-child(7n+1)')
widget[0].add_class('blue', 'div.red:nth-child(3n+1)')
In [ ]:
display(widget[1])
widget[1].remove_class('red', 'div.red:nth-child(7n+1)')
widget[1].add_class('blue', 'div.red:nth-child(3n+1)')
widget[1].add_class('red', 'div.cube:nth-child(even)')
In [ ]:
display(widget[2])
widget[2].add_class('red', 'div.cube:nth-child(even)')
widget[2].add_class('blue', 'div.red:nth-child(3n+1)')
widget[2].remove_class('red', 'div.red:nth-child(7n+1)')

Alignment classes

Widgets can be aligned using IPython alignment classes. These classes should work with most widgets, but were designed to be applied to ContainerWidgets. Examples of these classes follow:

Orientation classes

"vbox"

Widget containers default to this orientation.

A
B
C

"hbox"

A
B
C

Packing classes

These examples use the hbox layout to show packing. Packing is the alignment of the widgets along the the axis that they are displayed on.

"start"

A
B
C

"center"

A
B
C

"end"

A
B
C

Aligning classes

These examples use the hbox layout to show alignment. Packing is the alignment of the widgets along the the axis perpendicular to the one that they are displayed on.

"align-start"

A
B
C

"align-center"

A
B
C

"align-end"

A
B
C

Flex classes

To specify how "greedy" a container is when filling in the remaining space of its parent, the box-flexN classes are used (where N is 0, 1, or 2). The higher the value of N, the more greedy the child is. box-flex0 is the default behavior, which is to not fill the parent.

Example 1

box-flex0
box-flex0
box-flex0

Example 2

box-flex0
box-flex1
box-flex0

Example 3

box-flex0
box-flex1
box-flex1

Example 4

box-flex1
box-flex1
box-flex1

Example 5

box-flex2
box-flex1
box-flex1

Example 6

box-flex0
box-flex1
box-flex2

Application to widgets

Widget containers default to vbox alignment.

In [ ]:
buttons = [widgets.ButtonWidget(description=str(i)) for i in range(3)]

container = widgets.ContainerWidget(children=buttons)
display(container)

Using hbox

To make a widget container display its widgets horizontally, you need to remove the vbox class from the container and add the hbox class in its place.

In [ ]:
container = widgets.ContainerWidget(children=buttons)
display(container)
container.remove_class('vbox')
container.add_class('hbox')

By setting the width of the container to 100% and adding the center class to it, you can center the buttons.

In [ ]:
container.set_css('width', '100%')
container.add_class('center')

Style classes

In addition to alignment classes, the classes defined by Bootstrap can also be used. This tutorial will only cover a few of the most common classes. For a full list of Bootstrap classes, please refer to Bootstrap's website.

ButtonWidgets

In [ ]:
# List of the bootstrap button styles
classes = [
    'btn', 
    'btn-primary', 
    'btn-info', 
    'btn-success', 
    'btn-warning', 
    'btn-danger', 
    'btn-inverse', 
    'btn-link'
]

# Display the buttons in a hbox
container = widgets.ContainerWidget(children=[widgets.ButtonWidget(description=c) for c in classes])
display(container)

# Apply classes after display
container.remove_class('vbox')
container.add_class('hbox')
ret = [container.children[i].add_class(c) for i, c in enumerate(classes)]

ContainerWidgets

In [ ]:
def create_label(cls):
    class_name = widgets.HTMLWidget(value=cls)
    container = widgets.ContainerWidget(children=[class_name])
    display(container)
    container.add_class(cls)

ret = [create_label(c) for c in [
    'alert', 
    'alert alert-error', 
    'alert alert-success', 
    'alert alert-info'
]]

ProgressWidgets

In [ ]:
classes = [
    'progress-info', 
    'progress-success', 
    'progress-warning', 
    'progress-danger',
    'progress-info progress-striped', 
    'progress-success progress-striped', 
    'progress-warning progress-striped', 
    'progress-danger progress-striped',
    'active progress-info progress-striped', 
    'active progress-success progress-striped', 
    'active progress-warning progress-striped', 
    'active progress-danger progress-striped',
]
ws = [widgets.IntProgressWidget(value=50, description=c) for c in classes]
ret = [display(w) for w in ws]
ret = [ws[i].add_class(c) for i, cs in enumerate(classes) for c in cs.split(' ')]

Visibility

Sometimes it is necessary to hide or show widgets in place, without having to re-display the widget. The visibility property of widgets can be used to hide or show widgets that have already been displayed (as seen below).

In [ ]:
string = widgets.LatexWidget(value="Hello World!")
display(string) 
In [ ]:
string.visible=False
In [ ]:
string.visible=True

Another example

In the example below, a form is rendered, which conditionally displays widgets depending on the state of other widgets. Try toggling the student checkbox.

In [ ]:
form = widgets.ContainerWidget()
first = widgets.TextWidget(description="First Name:")
last = widgets.TextWidget(description="Last Name:")

student = widgets.CheckboxWidget(description="Student:", value=False)
school_info = widgets.ContainerWidget(visible=False, children=[
    widgets.TextWidget(description="School:"),
    widgets.IntTextWidget(description="Grade:", min=0, max=12)
    ])

pet = widgets.TextWidget(description="Pet's Name:")
form.children = [first, last, student, school_info, pet]
display(form)

def on_student_toggle(name, value):
    if value:
        school_info.visible = True
    else:
        school_info.visible = False
student.on_trait_change(on_student_toggle, 'value')

Index - [Back](Widget Events.ipynb) - [Next](Custom Widget - Hello World.ipynb)