ipyvuetify: Jupyter widgets based on Vuetify UI components¶
Introduction¶
Ipyvuetify is a widget library for making modern looking GUI’s in Jupyter notebooks (classic and lab) and dashboards (Voilà). It’s based on the Google material design philosophy best known from the Android user interface.
A large set of widgets is provided with many widgets having multiple variants. a few of which are displayed below.
There is support for responsive layouts, which are not that useful in a notebook because of the fixed width, but come in handy when making dashboards with Voilà, making your dashboard usable on tablets and phones.
When comparing ipyvuetify to ipywidgets, the standard widget library of Jupyter, ipyvuetify has a lot more widgets which are also more customizable and composable at the expense of a bit more verbosity in the source code.
Ipyvuetify uses the machinery of ipywidgets as a base, but has different conventions for the API. This is mainly due to the fact the Python API is generated from the JavaScript library it is based on: Vuetify. This exposes the full power of Vuetify and allows us to rely on the extensive documentation and examples of it. Generating code and relying on documentation from the underlying library allowed us to expose a lot of widgets to Jupyter in a relatively short amount of time.
In Usage al concepts and how they differ from ipywidgets will be explained and supported by examples.
To explore which widgets are available and how to use them we defer to the Vuetify documentation. You can browse examples on the left-hand side and see the source code by clicking on ‘< >’ on the top right-hand side of the example. By reading Usage you will be able to translate the examples to ipyvuetify.
Installation¶
Using pip¶
$ pip install ipyvuetify
Using conda¶
(myenv) $ conda install -c conda-forge ipyvuetify
Jupyter Lab¶
If you’re using only the classic notebook, you’re done. If you’re using Jupyter Lab, the extension has to be installed in Jupyter Lab:
$ jupyter labextension install jupyter-vuetify
Note
ipyvuetify depends on ipywidgets being installed in Jupyter Lab, see the ipywidgets documentation on how to do that.
Usage¶
This page shows how to use ipyvuetify and explains how it is different from other widget libraries you may know such as ipywidgets. It also explains how to use the Vuetify documentation. Most examples display real widgets which have animations and behavior.
Create an ipyvuetify widget¶
Below you see how to create an ipyvuetify widget.
import ipyvuetify as v
my_select = v.Select(
label='Fruits',
items=['Apple', 'Pear', 'Cherry'])
my_select
Attributes can be changed at a later time by:
my_select.items = [*my_select.items, 'Banana']
Note
A new List is created to change the items. In-place mutations of List and Dict, e.g. my_select.append(
'Banana')
, are not detected by ipywidgets.
What widgets are available and how they look can be found in the
Vuetify documentation. Browse the side bar on the left hand side and
select a widget, then click <> on the right hand side on an example to see the source code for it. The HTML code may
seem unfamiliar at first, but this documentation will guide you through it. For starters to translate the Vuetify widget
names, which are starting with v-
, to ipyvuetify, remove the v-
prefix and CamelCase the remaining
name. E.g v-select
becomes Select
and v-list-item
becomes ListItem
.
Equivalent Vuetify syntax of the example above:
<v-select label="Fruits" :items="['Apple', 'Pear', 'Cherry']" />
Setting Attributes¶
When translating from Vuetify HTML to Python, some attributes have to be treated different.
Python uses snake_case to separate words in attributes, while Vuetify uses kebab-case. For example the attribute
append-icon
becomes append_icon
:
Vuetify:
<v-select append-icon="mdi-gamepad-down" label="Fruits" />
ipyvuetify:
v.Select(append_icon='mdi-gamepad-down', label='Fruits')
In HTML attributes don’t have to have values, just defining the attribute is enough to use it as a boolean. In Python we
have to set the value to True. For example clearable
becomes clearable=True
:
Vuetify:
<v-select clearable label="Fruits" :items="['Apple', 'Pear', 'Cherry']" value="Apple" />
ipyvuetify:
v.Select(clearable=True, label='Fruits', items=['Apple', 'Pear', 'Cherry'], value='Apple')
Some attribute have naming conflicts with Python or ipywidgets. These are for
, open
, class
and
style
and must be suffixed with an underscore. For example style
becomes style_
Vuetify:
<v-select style="width: 75px" label="Fruits" />
ipyvuetify:
v.Select(style_='width: 75px', label='Fruits')
In the Vuetify HTML examples you’ll see attributes prefixed with a colon :
. This means the attribute is bound to
a variable or it is evaluated as an expression. If it is bound to a variable you’ll see that variable being used in
other parts of the example. In ipyvuetify we use jslink()
to link these attributes. In the next section you’ll
see an example of this. To look at how that variable is initialized you select the ‘script’ tab on a Vuetify example.
If it’s an expression it’s mostly used to set a List or a Dict, as is done with items
in the examples above.
This can be the same in ipyvuetify.
Reading the value¶
Now we want to be able to read out the selected value. In ipywidgets this would be done by reading the value
attribute. In Vue this is done with the v-model
directive, which is translated to Python as v_model
(
note the ‘_’ instead of ‘-‘). The v_model
attribute has to be explicitly set when creating the widget.
Vuetify:
<v-container>
<v-select
v-model="colorVariable"
label="Colors"
items="['red', 'green', 'blue']" />
<v-chip :color="colorVariable"><v-chip>
</v-container>
ipyvuetify:
from ipywidgets import jslink
color_select = v.Select(
v_model='green',
label='Colors',
items=['red', 'green', 'blue'])
color_display = v.Chip()
jslink((color_select, 'v_model'), (color_display, 'color'))
v.Container(children=[
color_select,
color_display
])
Note
ipyvuetify widgets have a value
attribute, but that’s only used for setting the value, it will not change on
interactions with the widget.
The children attribute¶
Because ipyvuetify is based on HTML, which represents a GUI as a tree of elements, all widgets have an attribute
children
which is a list of widgets or strings. This way the same tree can be represented in Python. Sometimes
something you would expect to be specified as an attribute, must be specified as an item in children
, e.g. in
ipywidgets the text of a button is set with the attribute description
while in ipyvuetify the text is set with
setting an item in the children
list:
Vuetify:
<v-container>
<v-btn color="primary">Click me</v-btn>
</v-container>
ipyvuetify
v.Container(children=[
v.Btn(color='primary', children=['Click me'])
])
This has the benefit of composability, e.g. the button can, in addition to text, also contain an icon:
Vuetify:
<v-container>
<v-btn color="primary">
<v-icon left>
mdi-email-edit-outline
</v-icon>
Click me
</v-btn>
</v-container>
ipyvuetify:
v.Container(children=[
v.Btn(color='primary', children=[
v.Icon(left=True, children=[
'mdi-email-edit-outline'
]),
'Click me'
])
])
Events¶
Events are specified with .on_event(event_name, callback_fn)
instead of setting an attribute like in ipywidgets.
btn = v.Btn(color='primary', children=['Click me'])
count = 0
def on_click(widget, event, data):
global count
btn.children=[f'Click me {count}']
count += 1
btn.on_event('click', on_click)
v.Container(children=[
btn
])
# The output of this example is intentionally left out, because
# it will not work without an active kernel.
The three arguments in the callback function are:
- widget: the widget the event originates from. This is useful when using the same callback for multiple widgets.
- event: the event name. This is useful when using the same callback for multiple events.
- data: data for the event. For e.g.
click
ofBtn
this contains which modifier keys are pressed and some information on the position of the mouse.
All HTML events can be used. The on
prefix must be
omitted.
Widgets can have custom events, to find out which, the Vuetify API explorer can be used. Search for a component and on the left-hand side of list of attributes you will find a tab for the events.
In Vuetify events are defined as attributes with an @
prefix. The equivalent Vuetify syntax of the example above
is:
<v-container>
<v-btn color="primary" @click="on_click">
Click me {{ count }}
</v-btn>
</v-container>
The on_click method would be in the ‘script’ tab of an example and is not shown here.
Regular HTML tags¶
Sometimes some regular HTML tags are needed. For this the Html widget can be used.
Vuetify:
<v-container>
<h1>My heading</h1>
</v-container>
ipyvuetify
v.Container(children=[
v.Html(tag='h1', children=['My heading'])
])
Styling¶
To visually customize widgets, the underlying CSS facilities of Vuetify are exposed. With the style_
attribute
CSS properties can be set. Multiple CSS
properties can be set by separating them with a semicolon ;
.
v.Select(label='Fruit', style_='width: 75px; opacity: 0.7')
With the class_
attribute predefined Vuetify styles can be set. Predefined styles of note are
spacing and colors <https://vuetifyjs.com/styles/colors/>. More can be
found in the section ‘Styles and animations’ of the Vuetify documentation. Multiple classes can be applied by separating
them with a space.
Buttons without spacing:
v.Container(children=[
v.Btn(children=[f'Button {i}']) for i in range(3)
])
With 2 units of margin in the x direction:
v.Container(children=[
v.Btn(class_='mx-2', children=[f'Button {i}']) for i in range(3)
])
And colors:
v.Container(children=[
v.Btn(class_=f'mx-2 indigo lighten-{i+1}', children=[f'Button {i}']) for i in range(3)
])
Layout (HBox/VBox alternative)¶
In ipywidgets you would layout a grid of widgets with HBox and VBox.
import ipywidgets as widgets
widgets.HBox([
widgets.VBox([
widgets.Button(description="top left"),
widgets.Button(description="bottom left"),
]),
widgets.VBox([
widgets.Button(description="top right"),
widgets.Button(description="bottom right"),
]),
])
This can be done in ipyvuetify with the help of some classes described in flex helpers.
v.Html(tag='div', class_='d-flex flex-row', children=[
v.Html(tag='div', class_='d-flex flex-column', children=[
v.Btn(class_='ma-2', children=['top left']),
v.Btn(class_='ma-2', children=['bottom left'])
]),
v.Html(tag='div', class_='d-flex flex-column', children=[
v.Btn(class_='ma-2', children=['top right']),
v.Btn(class_='ma-2', children=['bottom right'])
]),
])
Icons¶
Icons can be displayed with the Icon widget:
v.Icon(children=['mdi-thumb-up'])
In some widgets icons are specified by setting an attribute:
v.Select(prepend_icon='mdi-thumb-up')
See materialdesignicons.com/4.5.95 for a list of available icons.
Themes¶
To enable the dark theme:
v.theme.dark = True
To customize the themes:
v.theme.themes.light.primary = '#b71c1c'
v.theme.themes.dark.primary = '#a71c1c'
Also, the pre-defined material colors are supported:
v.theme.themes.light.primary = 'colors.teal'
v.theme.themes.light.secondary = 'colors.teal.lighten4'
Available theme properties:
- primary
- secondary
- accent
- error
- info
- success
- warning
- anchor
Summary¶
Below you will find a summary of all concepts of Vuetify and how they translate to ipyvuetify to help with the translation from Vuetify examples to ipyvuetify.
Component names convert to CamelCase and the v- prefix is stripped
Vuetify <v-list-tile .../>
ipyvuetify ListTitle(...)
Attributes
convert to snake_case
Vuetify <v-menu offset-y ...
ipyvuetify Menu(offset_y=True ...
must have a value
Vuetify <v-btn round ...
ipyvuetify Btn(round=True ...
with naming conflicts,
style
,class
,open
andfor
, are suffixed with an_
Vuetify <v-btn class="mr-3" style="..." >
ipyvuetify Btn(class_='mr-3', style_='...')
v-model (value in ipywidgets) contains the value directly
Vuetify <v-slider v-model="some_property" ...
ipyvuetify myslider = Slider(v_model=25...
jslink((myslider, 'v_model'), (..., ...))
Child components and text are defined in the children attribute
Vuetify <v-btn>text <v-icon>...</icon></v-btn>
ipyvuetify Btn(children=['text', Icon(...)])
Event listeners are defined with on_event
Vuetify <v-btn @click='someMethod()' ...
ipyvuetify def some_method(widget, event, data):
button.on_event('click', some_method)
Regular HTML tags can made with the Html widget
Vuetify <div>...</div>
ipyvuetify Html(tag='div', children=[...])
Advanced Usage¶
(Scoped) Slots¶
Slots are used to add content at a certain location in a widget. You can find out what slots a widget supports by using
the Vuetify documentation. If you want to know what slots Select
has, search for v-select
on the
Vuetify API explorer or for this example use the direct link. On the left-hand side of list of attributes you will find a tab
‘slots’.
An example for using the slot ‘no-data’, which changes what the Select widget shows when it has no items:
Vuetify:
<v-select>
<template v-slot:no-data>
<v-list-item>
<v-list-item-title>
My custom no data message
</v-list-item-title>
</v-list-item>
</template>
</v-select>
ipyvuetify:
v.Select(v_slots=[{
'name': 'no-data',
'children': [
v.ListItem(children=[
v.ListItemTitle(children=['My custom no data message'])])]
}])
Scoped slots are used if the parent widget needs to share its scope with the content. In the example below the events of the parent widget are used in the slot content.
Vuetify:
<v-tooltip>
<template v-slot:activator="tooltip">
<v-btn v-on="tooltip.on" color="primary">
button with tooltip
</v-btn>
</template>
Insert tooltip text here
</v-tooltip>
ipyvuetify:
v.Container(children=[
v.Tooltip(bottom=True, v_slots=[{
'name': 'activator',
'variable': 'tooltip',
'children': v.Btn(v_on='tooltip.on', color='primary', children=[
'button with tooltip'
]),
}], children=['Insert tooltip text here'])
])
In the Vuetify examples you will actually see:
...
<template v-slot:activator="{ on }">
<v-btn v-on="on">
...
Instead of the functionally equivalent (like used in the example above):
...
<template v-slot:activator="tooltip">
<v-btn v-on="tooltip.on">
...
The { on }
is JavaScript syntax for destructuring an object. It takes the ‘on’ attribute from an object and
exposes it as the ‘on’ variable.
Note
The ‘default’ slot can be ignored, this is where the content defined in the children
attribute goes.
Responsive Layout¶
When making dashbords with Voilà you can change the layout depending on the users screen size. This is done with a grid system. For example on a laptop (breakpoint md) you could fit two elements next to each other while on a smartphone (defined with ‘cols’ as default) you would want one element to take up the full width:
v.Row(children=[
v.Col(cols=12, md=6, children=[
v.Card(outlined=True, style_='height: 400px', children=[f'Element {i}'])
]) for i in range (1,3)
])
Which displays on a laptop as:
On a phone as:
In the display section you will find CSS helper classes to do more customizations based on screen size.
Event modifiers¶
In Vue event modifiers can be used to change event behavior.
For example when you have two nested elements and want a different click handler for the inner and outer element, the
stop
event modifier can be used by appending .stop
to the event name:
icon = v.Icon(right=True, children=['mdi-account-lock'])
btn = v.Btn(color='primary', children=[
'button',
icon
])
icon.on_event('click.stop', lambda *args: print('icon clicked'))
btn.on_event('click', lambda *args: print('btn clicked'))
v.Container(children=[
btn
])
# Note: the event handlers won't work in this page because there is no active kernel.
.sync¶
When you see .sync
appended to an attribute in Vuetify syntax, it means the attribute has a two-way binding (like v-model
). This is shorthand in Vue
that automatically listens to an event named update:<attributeNameInCamelCase>
.
We can achieve the same manually in ipyvuetify by setting an event handler
<widget>.on_event('update:<attributeNameInCamelCase>', <function>)
Vuetify:
<v-navigation-drawer :mini-variant.sync="someProperty" ...
ipyvuetify:
drawer = v.NavigationDrawer(mini_variant=True, ...)
def update_mini(widget, event, data):
drawer.mini_variant = data
drawer.on_event('update:miniVariant', update_mini)