Source code for datatable

"""Display of a Pandas DataFrame in a data-table widget."""
# Author(s): Davide.De-Marchi@ec.europa.eu
# Copyright © European Union 2022-2023
# 
# Licensed under the EUPL, Version 1.2 or as soon they will be approved by 
# the European Commission subsequent versions of the EUPL (the "Licence");
# 
# You may not use this work except in compliance with the Licence.
# 
# You may obtain a copy of the Licence at:
# https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12

# Unless required by applicable law or agreed to in writing, software
# distributed under the Licence is distributed on an "AS IS"
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
# express or implied.
# 
# See the Licence for the specific language governing permissions and
# limitations under the Licence.
import ipyvuetify as v
import traitlets
import pandas as pd
import json

# DataTable managing click on a row. See https://github.com/mariobuikhuizen/ipyvuetify/issues/163
[docs]class datatable(v.VuetifyTemplate): """ Display of a Pandas DataFrame in a data-table widget. Parameters ---------- data : Pandas DataFrame, optional Pandas DataFrame to be displayed (default is pd.DataFrame() empty DataFrame) height : str, optional Height of the data-table widget (default is '400px') on_click : function, optional Python function to call when the user clicks on one of the rows of the data-table. The function will receive a parameter of type dict containing all the column names as keys and the clicked row data as values color : str, optional Color to use for the display of alert and warning messages (for instance if no records are present in input DataFrame) (default is 'error') dark : bool, optional If True, the error and warning messages are displayed in white color, if False they are displayed in black (default is False) searchshow : bool, optional If True, on top of the table a search field will be displayed, allowing for search (default is False) search : str, optional Initial search string to be displayed in the search field (default is ''). If searchshow is False, this argument will not be used title : str, optional In case searchshow is True, a Title can be shown on top of the datatable (default is '') icon : str, optional In case searchshow is True, an icon can be shown on the right of the datatable title (default is '') icon_tooltip : str, optional Tooltip string to be used for the icon (default is '') icon_color : str, optional Color of the icon (default is 'grey') icon_disabled : bool, optional If True, the icon will be disabled (default is False) font_size : str, optional Font size to use for the rows and headers of the datatable (default is '14px') font_size_title : str, optional Font size to use for the title of the datatable (default is '16px') on_icon : function, optional Python function to call when the user clicks on the icon on the top of the data-table. The function will receive no parameters unsortable_columns : list of str, optional List of names of columns that must not be sortable in the datatable (please note that the DataFrame column names will be displayed in capital letters in the datatable: use in this list the original column names of the DataFrame and not the capitalized names) Example ------- Creation of a Pandas DataFrame from the 'Our World In Data' dataset on Covid-19 daily data and display of last 100 days for Italy:: from vois.vuetify import datatable import pandas as pd from ipywidgets import widgets from IPython.display import display output = widgets.Output() df = pd.read_csv('https://raw.githubusercontent.com/owid/covid-19-data/master/public/data/owid-covid-data.csv') def on_click(data): output.clear_output() with output: print(data) df = df[df['location']=='Italy'] d = datatable.datatable(data=df.tail(100), height='500px', on_click=on_click) display(d) display(output) .. figure:: figures/datatable.png :scale: 100 % :alt: datatable widget Last 100 days of Covid-19 data on Italy displayed in a datatable widget """ headers = traitlets.List([]).tag(sync=True, allow_null=True) items = traitlets.List([]).tag(sync=True, allow_null=True) index_col = traitlets.Unicode('').tag(sync=True) height = traitlets.Unicode('400px').tag(sync=True) color = traitlets.Unicode('error').tag(sync=True) # Color for error message in case of empty input DF dark = traitlets.Bool(False).tag(sync=True) searchshow = traitlets.Bool(False).tag(sync=True) title = traitlets.Unicode('').tag(sync=True) search = traitlets.Unicode('').tag(sync=True) icon = traitlets.Unicode('').tag(sync=True) icon_tooltip = traitlets.Unicode('').tag(sync=True) icon_color = traitlets.Unicode('grey').tag(sync=True) icon_disabled = traitlets.Bool(False).tag(sync=True) font_size = traitlets.Unicode('14px').tag(sync=True) font_size_title = traitlets.Unicode('16px').tag(sync=True) @traitlets.default('template') def _template(self): if self.searchshow: stricon = '' if len(self.icon) > 0: stricon = ''' <v-tooltip bottom> <template v-slot:activator="{ on, attrs }"> <v-btn icon @click="onicon()" :disabled="icon_disabled" color="%s" v-on="on"><v-icon small>%s</v-icon></v-btn> </template> <span>%s</span> </v-tooltip> '''% (self.icon_color, self.icon, self.icon_tooltip) cardtitle = ''' <v-card-title class="pa-0 ma-0 ml-2 mb-1" style="font-size: %s;"> %s %s <v-spacer></v-spacer> <v-text-field class="pa-0 ma-0 mb-3" v-model="search" :color="color" append-icon="mdi-magnify" label="Search" single-line hide-details></v-text-field> </v-card-title> ''' % (self.font_size_title, self.title, stricon) else: cardtitle = '' return ''' <template> <v-card> %s <v-data-table dense hide-default-footer fixed-header :search="search" :height="height" :headers="headers" :items="items" :item-key="index_col" :footer-props="{'items-per-page-options': [10000000]}"> <template v-slot:no-data> <v-alert :value="true" :color="color" :dark="dark" icon="mdi-alert"> No records to display </v-alert> </template> <template v-slot:no-results> <v-alert :value="true" :color="color" :dark="dark" icon="mdi-alert" width="80vw"> Your search for "{{ search }}" found no results </v-alert> </template> <template v-slot:item="row"> <tr> <td v-for="value in Object.values(row.item)" @click="cell_click(row.item)" > {{ value }} </td> </tr> </template> </v-data-table> </v-card> </template> <style> .v-data-table > .v-data-table__wrapper > table > tbody > tr > th, .v-data-table > .v-data-table__wrapper > table > thead > tr > th, .v-data-table > .v-data-table__wrapper > table > tfoot > tr > th, .v-data-table > .v-data-table__wrapper > table > tbody > tr > td { font-size: %s !important; } </style> ''' % (cardtitle, self.font_size) on_click = None # This must receive a function which will be called inside `self.vue_cell_click()` # Click on the "icon" button def vue_onicon(self, data): if not self.on_icon is None: self.on_icon() # Click on one row of the datatable def vue_cell_click(self, data): if not self.on_click is None: self.on_click(data) # Initializing def __init__(self, *args, data=pd.DataFrame(), height='400px', title='', dictwidth={}, searchshow=False, search='', on_click=None, on_icon=None, icon='', icon_tooltip='', icon_color='grey', icon_disabled=False, font_size='14px', font_size_title='16px', unsortable_columns=[], **kwargs): self.title = title self.searchshow = searchshow self.search = search data = data.reset_index() self.index_col = data.columns[0] #headers = [{"text": col, "value": col } for col in data.columns] headers = [] for col in data.columns: sortable = True if col in unsortable_columns: sortable = False h = {"text": str(col).upper(), "value": col, "sortable": sortable} if col in dictwidth: h["width"] = dictwidth[col] else: h["width"] = "20px" headers.append(h) #print(headers) self.headers = headers self.height = height self.on_click = on_click self.on_icon = on_icon self.icon = icon self.icon_tooltip = icon_tooltip self.icon_color = icon_color self.icon_disabled = icon_disabled self.font_size = font_size self.font_size_title = font_size_title self.items = json.loads(data.to_json(orient='records')) super().__init__(*args, **kwargs)