Using tables

Tables are a visual element in Taipy GUI that not only act as a means for presenting data but also function as a control. Building any data application (a Taipy specialty!) is a perfect opportunity to utilize Taipy’s tables and their nifty features.

This article showcases settings that users would most commonly look for when creating their tables. If you’re looking to do something with tables not in this article, check the table control documentation for a comprehensive list of properties!

You can find the full code incorporating all the discussed table features at the end of this article.

daily calorie tracker with Taipy

Creating a Table

First, let’s take a look at how tables are created in Taipy GUI:

### app.py

from taipy.gui import Gui, Markdown
import pandas as pd

 

food_df = pd.DataFrame({

“Meal”: [“Lunch”, “Dinner”, “Lunch”, “Lunch”, “Breakfast”, “Breakfast”, “Lunch”, “Dinner”],
“Category”: [“Food”, “Food”, “Drink”, “Food”, “Food”, “Drink”, “Dessert”, “Dessert”],
“Name”: [“Burger”, “Pizza”, “Soda”, “Salad”, “Pasta”, “Water”, “Ice Cream”, “Cake”],
“Calories”: [300, 400, 150, 200, 500, 0, 400, 500],
})

main_md = Markdown(“<|{food_df}|table|>”)

Gui(page=main_md).run(dark_mode=False)

The table definition of “<|{food_df}|table|>” (a Taipy GUI syntax you’re probably already familiar with) consists of the following components:

    1. {food_df} : The variable containing the data to be tabulated — someone’s food log for a given day;
    2. table : The name of the control.

In this case, the data (food_df) is a pandas DataFrame. If applicable for your use case, you may also use built-in Python list objects or NumPy arrays.

The above-mentioned code produces the following graphics:

a basic table with Taipy

To add aggregation to our table, we need to specify the column to group, and the aggregation function to be performed. In our food tracker example, an application could be to:

    1. Group by “Category”; and
    2. Sum the “Calories”.
main_md = Markdown(“<|{food_df}|table|group_by&#91;Category&#93;=True|apply&#91;Calories&#93;=sum|>”)

Table aggregation was configured by adding two properties to the table:

    1. group_by[Category]=True : Indicates that the data should be grouped by the “Category” column. This property may be defined more than once with different column names.
    2. apply[Calories]=sum : Indicates that the “Calories” column should be aggregated with the “sum” function. The valid values for the aggregation function are “first” (default), “last“, “count“, “sum“, “mean“, “median“, “min“, “max” and “std“. A custom user-defined function may also be used.

Since the “Meal” and “Name” columns were not explicitly assigned an apply function, the default, “first”, is used.

As a side note, if more suitable for our use case, we could also create a separate table to show the aggregated data. This could be done by creating a new DataFrame (e.g. df_agg) from df with the desired transformations applied, binding it to a new table, then updating the new DataFrame when df is modified. However, this topic is illustrating toggleable aggregation by dynamically updating the same table.

 

Filtering Data

We can add filters to our table.

filtering tables with Taipy

Filters allow us to filter the data by one or more columns. For example, we can filter the “Calories” column for values above 300 and/or the “Meal” column to view only “Breakfast”.

Adding filters to our table is as simple as setting the filter property to True, like so:

main_md = Markdown(“<|{food_df}|table|filter=True|>”)

As with all Taipy GUI construct properties, we can omit the True, making it: "<|{food_df}|table|filter|>".

 

Styling (Stylekit)

There are 2 properties we can use to customize the style of a table in Taipy GUI:

  1. class_name : To apply a CSS class to the entire table; and
  2. style : To apply a CSS class to specific rows (specified in Python).

 

Property 1: class_name

The Stylekit is a set of CSS classes predefined by Taipy which provide an easy way to customize the style of your Taipy web application — and is easy to use even for users who don’t know any CSS at all!

For example, we can add row borders to our table:

table styling with Taipy

And we accomplished this by simply adding the “rows-bordered” Stylekit CSS class to the class_name property of the table control:

main_md = Markdown(“<|{food_df}|table|class_name=rows-bordered|>”)

To know more about Stylekit support for Taipy GUI tables, read the documentation here. To know more about the Stylekit’s many features beyond tables in Taipy, you can read this tip article!

Besides the convenient predefined Stylekit classes, we may also define and use our own CSS classes. We can do this by creating a CSS file with our style sheet code, and passing the path string to the css_name parameter of the Gui object.

Otherwise, an easier way is to simply give the CSS file the same name as the Python script (but change the extension to .css), and place the file in the same directory as the Python script — e.g., have our Taipy application code in “app.py” and the CSS code in “app.css”, in the same directory. You can read more about this here.

 

Property 2: style

For further control over our styling, we can use the style property of tables to set the CSS classes of each individual row in the table. For example, we can assign rows whose “Category” column is “Dessert” a user-defined “highlight-row” CSS class, to give them a yellow background:

tables-styling-style with Taipy

The style property receives a function. This function is subsequently applied to each row of the table, and returns a string with the name of the CSS class to be used for that row. We can produce the table above with the following code:

### app.py

def table_style(state, index, row):

print(type(row)) # pd.Series
return “highlight-row” if row.Category == “Dessert” else “”

table_properties = {

“class_name”: “rows-bordered rows-similar”, # optional
“style”: table_style,
}

main_md = Markdown(“<|{food_df}|table|properties=table_properties|>”)
# or Markdown(“<|{food_df}|table|class_name=rows-bordered rows-similar|style=table_style|>”)
/* app.css */
.highlight-row td {
    background-color: yellow;
}

Notice that we also used the class_name property we discussed in the previous section, and used 2 Stylekit table classes: “rows-bordered” and “rows-similar”. The “rows-similar” class removes the 0.5 opacity applied to odd rows by default. We didn’t need to, but it does help make the table look nicer when applying our “highlight-row” style.

Modifying Data

Tables provide several data modification properties that can be used for modifying data in the table. Notably, the on_edit, on_add and on_delete properties accept a user-defined “callback” function, which is executed when interacting with a new control, and only appears when the relevant data modification property is specified.

Taipy does not provide default functions for each property, so we will define each function to achieve our use case ourselves. For some added flair, we’re also adding the Taipy notify function to our data modification callback functions to send a notification to the user of their change.

Editing (on_edit)

When the on_edit property is used, new buttons with a pencil icon are added to each cell. Clicking it allows the user to modify the value of that cell, then clicking the tick triggers the callback function:

tables-on_edit with Taipy

The following code can be used to implement basic editing functionality:

def food_df_on_edit(state, var_name, action, payload):

index = payload[“index”] # row index
col = payload[“col”] # column name
value = payload[“value”] # new value cast to the column type
user_value = payload[“user_value”] # new value as entered by the user

# state.food_df.loc[index, col] = value # Don’t do this!
old_value = state.food_df.loc[index, col]
new_food_df = state.food_df.copy()
new_food_df.loc[index, col] = value
state.food_df = new_food_df
notify(state, “I”, f”Edited value from ‘{old_value}’ to ‘{value}’. (index ‘{index}’, column ‘{col}’)”)

main_md = Markdown(“<|{food_df}|table|on_edit=food_df_on_edit|>”)

The table documentation provides more information on the function signature — which is slightly different for each data modification property. The code example above is self-explanatory though.

Notice that we didn’t modify the food_df DataFrame with state.food_df.loc[index, col] = value. This is because Taipy GUI state variables should be updated by assignment to the variable itself — hence why we instead create a copy of the DataFrame, modify the relevant cell, then assign it back to state.food_df.

 

Adding (on_add)

Adding (and deleting) are very similar to editing. When on_add is specified, a button with a plus icon is added, which calls the defined on_add callback function when clicked:

tables-on_add with Taipy

We can implement the functionality above as follows:

def food_df_on_delete(state, var_name, action, payload):

index = payload[“index”] # row index

state.food_df = state.food_df.drop(index=index)
notify(state, “E”, f”Deleted row at index ‘{index}'”)

main_md = Markdown(“<|{food_df}|table|on_delete=food_df_on_delete|>”)

This code simply adds a new empty row to the top of the table (DataFrame). You can customize the callback function accordingly if your use case requires some columns to have a default or set value.

 

Deleting (on_delete)

Finally, deleting works like so:

tables-on_delete

And we can implement basic functionality with:

def food_df_on_add(state, var_name, action, payload):

empty_row = pd.DataFrame([[None for _ in state.food_df.columns]], columns=state.food_df.columns)
state.food_df = pd.concat([empty_row, state.food_df], axis=0, ignore_index=True)

notify(state, “S”, f”Added a new row.”)

main_md = Markdown(“<|{food_df}|table|on_add=food_df_on_add|>”)

The Complete App

Again, this tip article doesn’t exhaustively cover all the features of tables in Taipy GUI, so be sure to check the documentation out if you’re looking for a feature that wasn’t addressed here!

Finally, here is the code combining all the features we discussed in this article, used to produce the app shown at the beginning of the article:

from taipy.gui import Gui, Markdown, notify
import pandas as pd

food_df = pd.DataFrame({

"Meal": ["Lunch", "Dinner", "Lunch", "Lunch", "Breakfast", "Breakfast", "Lunch", "Dinner"],
"Category": ["Food", "Food", "Drink", "Food", "Food", "Drink", "Dessert", "Dessert"],
"Name": ["Burger", "Pizza", "Soda", "Salad", "Pasta", "Water", "Ice Cream", "Cake"],
"Calories": [300, 400, 150, 200, 500, 0, 400, 500],
})

def food_df_on_edit(state, var_name, action, payload):

index = payload["index"] # row index
col = payload["col"] # column name
value = payload["value"] # new value cast to the column type
user_value = payload["user_value"] # new value as entered by the user

old_value = state.food_df.loc[index, col]
new_food_df = state.food_df.copy()
new_food_df.loc[index, col] = value
state.food_df = new_food_df
notify(state, "I", f"Edited value from '{old_value}' to '{value}'. (index '{index}', column '{col}')")

def food_df_on_delete(state, var_name, action, payload):

index = payload["index"] # row index

state.food_df = state.food_df.drop(index=index)
notify(state, "E", f"Deleted row at index '{index}'")

def food_df_on_add(state, var_name, action, payload):

empty_row = pd.DataFrame([[None for _ in state.food_df.columns]], columns=state.food_df.columns)
state.food_df = pd.concat([empty_row, state.food_df], axis=0, ignore_index=True)

notify(state, "S", f"Added a new row.")

table_properties = {

"class_name": "rows-bordered",
"filter": True,
"on_edit": food_df_on_edit,
"on_delete": food_df_on_delete,
"on_add": food_df_on_add,
"group_by[Category]": True,
"apply[Calories]": "sum",
}

main_md = Markdown("""
# Daily Calorie Tracker

<|{food_df}|table|properties=table_properties|>
""")

Gui(page=main_md).run(dark_mode=False)