posit-dev / py-shiny

Shiny for Python
https://shiny.posit.co/py/
MIT License
1.1k stars 62 forks source link

feat(data frame): Support basic cell styling #1475

Closed schloerke closed 1 day ago

schloerke commented 1 week ago

~~POC for styling cells via great_tables package. ~~ POC proven. great_tables support pushed to later release.

Using this approach as great_tables has generic styling support, whereas the styler component of pandas is only for pandas and great_tables is already integrated into polars. Hopefully for the next release, both py-shiny and great-tables will be leveraging narwhals ("Lightweight and extensible compatibility layer between Polars, pandas, cuDF, Modin, and more"). This way, both py-shiny gets styling support for any data frame package! (Related: #1439)


Example excerpt:

@render.data_frame
def summary_data():
    df_gt = gt.GT(df).tab_style(
        [
            gt.style.fill(color="purple"),
            gt.style.borders(color="green", style="dashed"),
        ],
        gt.loc.body("Species", [1, 2]),
    )

    # Current working code
    return render.DataGrid(
        df,
        styles=df_gt._styles,
    )

    # Ideal code
    return render.DataGrid(df_gt)

Screenshot 2024-06-21 at 11 11 35 AM


Update: June 27th:

While the great_tables integration was a success, it brought up problems for how render.data_frame was to interact with the GT object. We will approach this subject in a 1.1 release.

For now, the styles= parameter has been added to render.DataTable and render.DataGrid. This value can either be a list of style info objects or function (given data as a param) that returns a list of style objects. This style function will be called after any cells have been updated as styling for the whole table may change due to a single cell update.

Ex:

from palmerpenguins import load_penguins_raw

from shiny import App, Inputs, render, ui

df = load_penguins_raw()

df_styles: list[render.StyleInfo] = [
    {
        "location": "body",
        "rows": None,
        "cols": None,
        "style": {"background-color": "lightblue"},
    },
    {
        "location": "body",
        "rows": [1, 2],
        "cols": "Species",
        "style": {
            "background-color": "purple",
            "border-color": "green",
            "border-style": "dashed",
        },
    },
    {
        "location": "body",
        "rows": 2,
        "cols": ["Region"],
        "style": {"background-color": "yellow"},
    },
    {
        "location": "body",
        "rows": None,
        "cols": [4],  # "Island",
        "style": {"background-color": "red"},
    },
    {
        "location": "body",
        "rows": [False, True, False, False, False],
        "cols": [False, False, False, False, True, True],  # "Stage",
        "style": {"background-color": "green"},
    },
]

def server(input: Inputs):
    @render.data_frame
    def list_styles():
        return render.DataTable(
            df,
            selection_mode=("rows"),
            editable=True,
            # filters=True,
            styles=df_styles,
        )