ipyvuetify

ipyvuetify liefert Jupyter-Widgets, die auf vuetify-UI-Komponenten basieren und das Material Design von Google mit dem Vue.js-Framework implementieren.

Installation

$ pipenv install ipyvuetify
Installing ipyvuetify…
…
$ pipenv run jupyter nbextension enable --py --sys-prefix ipyvuetify
Enabling notebook extension jupyter-vuetify/extension...
      - Validating: OK

Beispiele

Importe

[1]:
import ipywidgets
import ipyvuetify as v
from threading import Timer

from traitlets import (Any, Unicode, List)

Menü

[2]:
def on_menu_click(widget, event, data):
    if len(layout.children) == 1:
        layout.children = layout.children + [info]
    info.children=[f'Item {items.index(widget)+1} clicked']


items = [v.ListItem(children=[
    v.ListItemTitle(children=[
        f'Item {i}'])])
         for i in range(1, 5)]

for item in items:
    item.on_event('click', on_menu_click)

menu = v.Menu(offset_y=True,
    v_slots=[{
        'name': 'activator',
        'variable': 'menuData',
        'children': v.Btn(v_on='menuData.on', class_='ma-2', color='primary', children=[
            'menu',
            v.Icon(right=True, children=[
                'arrow_drop_down'
            ])
        ]),
    }]
    ,
    children=[
        v.List(children=items)
    ]
)

info = v.Chip(class_='ma-2')

layout = v.Layout(children=[
    menu
])
layout

Buttons

[3]:
v.Layout(children=[
    v.Btn(color='primary', children=['primary']),
    v.Btn(color='error', children=['error']),
    v.Btn(disabled=True, children=['disabled']),
    v.Btn(children=['reset']),
])
[4]:
v.Layout(children=[
    v.Btn(color='primary', flat=True, children=['flat']),
    v.Btn(color='primary', round=True, children=['round']),
    v.Btn(color='primary', flat=True, icon=True, children=[v.Icon(children=['thumb_up'])]),
    v.Btn(color='primary', outline=True, children=['outline']),
    v.Btn(color='primary', fab=True, large=True, children=[v.Icon(children=['edit'])]),
])
[5]:
def toggleLoading():
    button2.loading = not button2.loading
    button2.disabled = button2.loading

def on_loader_click(*args):
    toggleLoading()
    Timer(2.0, toggleLoading).start()

button2 = v.Btn(loading=False, children=['loader'])
button2.on_event('click', on_loader_click)

v.Layout(children=[button2])
[6]:
toggle_single = v.BtnToggle(v_model=2, class_='mr-3', children=[
    v.Btn(flat=True, children=[v.Icon(children=['format_align_left'])]),
    v.Btn(flat=True, children=[v.Icon(children=['format_align_center'])]),
    v.Btn(flat=True, children=[v.Icon(children=['format_align_right'])]),
    v.Btn(flat=True, children=[v.Icon(children=['format_align_justify'])]),
])

toggle_multi = v.BtnToggle(v_model=[0,2], multiple=True, children=[
    v.Btn(flat=True, children=[v.Icon(children=['format_bold'])]),
    v.Btn(flat=True, children=[v.Icon(children=['format_italic'])]),
    v.Btn(flat=True, children=[v.Icon(children=['format_underline'])]),
    v.Btn(flat=True, children=[v.Icon(children=['format_color_fill'])]),
])

v.Layout(children=[
    toggle_single,
    toggle_multi,
])
[7]:
v.Layout(children=[
    v.Btn(color='primary', children=[
        v.Icon(left=True, children=['fingerprint']),
        'Icon left'
    ]),
    v.Btn(color='primary', children=[
        'Icon right',
        v.Icon(right=True, children=['fingerprint']),
    ]),
    v.Tooltip(bottom=True, children=[
        v.Btn(slot='activator', color='primary', children=[
           'tooltip'
        ]),
        'Insert tooltip text here'
    ])
])
[8]:
lorum_ipsum = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.'

v.Layout(children=[
    v.Dialog(width='500', v_slots=[{
        'name': 'activator',
        'variable': 'x',
        'children': v.Btn(v_on='x.on', color='success', dark=True, children=[
            'Open dialog'
        ]),
    }],
    children=[
        v.Card(children=[
            v.CardTitle(class_='headline gray lighten-2', primary_title=True, children=[
                "Lorem ipsum"]),
            v.CardText(children=[
                lorum_ipsum])
        ])
    ])
])

Slider

[9]:
slider = v.Slider(v_model=25)
slider2 = v.Slider(thumb_label=True, v_model=25)
slider3 = v.Slider(thumb_label='always', v_model=25)

ipywidgets.jslink((slider, 'v_model'), (slider2, 'v_model'))

v.Container(children=[
    slider,
    slider2,
])

Reiter

[10]:
tab_list = [v.Tab(children=['Tab ' + str(i)]) for i in range(1,4)]
content_list = [v.TabItem(children=[lorum_ipsum]) for i in range(1,4)]
tabs = v.Tabs(
    v_model=1,
    children=tab_list + content_list)
tabs

Akkordeon

[11]:
vepc1 = v.ExpansionPanel(children=[
    v.ExpansionPanelHeader(children=['item1']),
    v.ExpansionPanelContent(children=['First Text'])])

vepc2 = v.ExpansionPanel(children=[
    v.ExpansionPanelHeader(children=['item2']),
    v.ExpansionPanelContent(children=['Second Text'])])

vep = v.ExpansionPanels(children=[vepc1, vepc2])
vl = v.Layout(class_='pa-4', children=[vep])
vl

In der Vuetify-Dokumentation könnt ihr nach allen verfügbaren Komponenten und Attributen suchen. Dabei orientiert sich Ipyvuetify an der Syntax von Vue.js- und Vuetify, aber es gibt auch einige Unterschiede:

Beschreibung

Vuetify

ipyvuetify

Komponentennamen werden in CamelCase geschrieben und der v-Präfix entfernt

<v-list-tile …/>

ListTile(…)

Untergeordnete Komponenten und Text werden im Traitlet für untergeordnete Elemente definiert

<v-btn>text <v-icon …/></v-btn>

Btn(children=['text', Icon(…)])

Flag-Attribute erfordern einen booleschen Wert

<v-btn round

Btn(round=True

Attribute sind snake_case

<v-menu offset-y

Menu(offset_y=True

Das Attribut v_model (Wert in ipywidgets) erhält den Wert direkt

<v-slider v-model="some_property"

Slider(v_model=25…

Der Geltungsbereich von slot kann aktuell noch nicht angegeben werden

<v-menu><template slot:activator="{ on }"><v-btn v-on=on>

Menu(children=[Btn(slot='activator',…), …]

Event-Listener werden mit on_event definiert

<v-btn @click='someMethod()'

def some_method (widget, event, data): mit button.on_event('click', some_method)

Reguläre HTML-Tags können mit der Klasse Html erstellt werden

<div>…</div>

Html(tag='div', children=[…])

Den Attributen class und style muss ein Unterstrich angefügt werden

<v-btn class="mr-3" style="…" >

Btn(class_='mr-3', style_='…')

VuetifyTemplate

Eine engere Übereinstimmung mit der Vue/Vuetify-API erhaltet ihr mit VuetifyTemplate. Hierfür erstellt ihr eine Unterklasse von VuetifyTemplate und definiert eigene Traitlets. Auf die Traitlets kann über das Template zugegriffen werden, so als befänden sie sich in einem Vue-Modell. Methoden können mit dem Präfix vue_ definiert werden, z.B. def vue_button_click(self, data), das dann mit @click="button_click(e)" aufgerufen werden kann. Im Folgenden zeige ich Euch eine Tabelle mit Suche, Sortierung und Zeilenanzahl:

[12]:
import pandas as pd
import traitlets
import ipyvuetify as v
import json

class PandasDataFrame(v.VuetifyTemplate):
    """
    Vuetify DataTable rendering of a pandas DataFrame

    Args:
        data (DataFrame) - the data to render
        title (str) - optional title
    """

    headers = traitlets.List([]).tag(sync=True, allow_null=True)
    items = traitlets.List([]).tag(sync=True, allow_null=True)
    search = traitlets.Unicode('').tag(sync=True)
    title = traitlets.Unicode('DataFrame').tag(sync=True)
    index_col = traitlets.Unicode('').tag(sync=True)
    template = traitlets.Unicode('''
        <template>
          <v-card>
            <v-card-title>
              <span class="title font-weight-bold">{{ title }}</span>
              <v-spacer></v-spacer>
                <v-text-field
                    v-model="search"
                    append-icon="search"
                    label="Search ..."
                    single-line
                    hide-details
                ></v-text-field>
            </v-card-title>
            <v-data-table
                :headers="headers"
                :items="items"
                :search="search"
                :item-key="index_col"
                :rows-per-page-items="[25, 50, 250, 500]"
            >
                <template v-slot:no-data>
                  <v-alert :value="true" color="error" icon="warning">
                    Sorry, nothing to display here :(
                  </v-alert>
                </template>
                <template v-slot:no-results>
                    <v-alert :value="true" color="error" icon="warning">
                      Your search for "{{ search }}" found no results.
                    </v-alert>
                </template>
                <template v-slot:items="rows">
                  <td v-for="(element, label, index) in rows.item"
                      @click=cell_click(element)
                      >
                    {{ element }}
                  </td>
                </template>
            </v-data-table>
          </v-card>
        </template>
        ''').tag(sync=True)

    def __init__(self, *args,
                 data=pd.DataFrame(),
                 title=None,
                 **kwargs):
        super().__init__(*args, **kwargs)
        data = data.reset_index()
        self.index_col = data.columns[0]
        headers = [{
              "text": col,
              "value": col
            } for col in data.columns]
        headers[0].update({'align': 'left', 'sortable': True})
        self.headers = headers
        self.items = json.loads(
            data.to_json(orient='records'))
        if title is not None:
            self.title = title

iris = pd.read_csv('https://raw.githubusercontent.com/mwaskom/seaborn-data/master/iris.csv')
test = PandasDataFrame(data = iris, title='Iris')
test
[13]:
v.Banner(single_line=True,
    v_slots=[{
        'name': 'icon',
        'children': v.Icon(children=['thumb_up'])
    }, {
        'name': 'actions',
        'children': v.Btn(text=True, color='deep-purple accent-4', children=['Action'])
    }],
    children=['One line message text string with two actions on tablet / Desktop'])