Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Conditional coloring for dataframe outputs #1472

Open
corey-dawson opened this issue Jun 20, 2024 · 7 comments
Open

Conditional coloring for dataframe outputs #1472

corey-dawson opened this issue Jun 20, 2024 · 7 comments
Labels
data frame Related to @render.data_frame enhancement New feature or request
Milestone

Comments

@corey-dawson
Copy link

Right now, there doesn't seem to be any options for conditionally coloring rows and cells in dataframe outputs (datagrid and datatable). This is especially important for analytical dashboards and data. Requesting a new feature or any recommendations on how to achieve conditional coloring in Python Shiny's current state

@schloerke schloerke added enhancement New feature or request data frame Related to @render.data_frame and removed needs-triage labels Jul 10, 2024
@schloerke schloerke added this to the v1.2.0 milestone Jul 10, 2024
@schloerke
Copy link
Collaborator

schloerke commented Jul 10, 2024

Good news! You can do this manually as of #1475 . That PR will be released in the v1.0.0 release shortly.

Type information for each StyleInfo item in styles= for render.DataGrid and render.DataTable objects being returned in a @render.data_frame output function:

StyleInfoBody = TypedDict(
    "StyleInfoBody",
    {
        "location": Required[Literal["body"]],
        "rows": NotRequired[Union[int, ListOrTuple[int], ListOrTuple[bool], None]],
        "cols": NotRequired[
            Union[str, int, ListOrTuple[str], ListOrTuple[int], ListOrTuple[bool], None]
        ],
        "style": NotRequired[Union[Dict[str, Jsonifiable], None]],
        "class": NotRequired[Union[str, None]],
    },
)
StyleInfo = StyleInfoBody

Example

import pandas as pd

from shiny.express import render

df_styles = [
    {
        "location": "body",
        "rows": [2, 4],
        "cols": [2, 4],
        "style": {
            "background-color": "coral",
        },
    }
]

n = 6
df = pd.DataFrame(
    {
        "a": range(n),
        "b": range(n, n * 2),
        "c": range(n * 2, n * 3),
        "d": range(n * 3, n * 4),
        "e": range(n * 4, n * 5),
    }
)


@render.data_frame
def my_df():
    return render.DataGrid(
        df,
        styles=df_styles,
    )

Screenshot 2024-07-10 at 9 37 44 AM


We are continuing to explore using great_tables as a way to set the styles and formatting of the cells. But for the v1 release, we ran into more problems than we were willing to let slide. However, integrating great_tables is still my end goal as its API to define lazy expressions that can create conditional formatting is very convenient!

For now, you'll need to define the style information yourself. 🫤 But at least it can be defined! 🥳


(Leaving issue open as conditional styling has not be implemented. Only cell styling.)

@maxmoro
Copy link

maxmoro commented Jul 10, 2024

This is great and a very good solution for the 1.0. Thank you. Can styles manage also columns width? Or will it be addressed in a different way?

@schloerke
Copy link
Collaborator

Yes! ... a pleasant surprise!

import pandas as pd

from shiny.express import render

df_styles = [
    {
        "location": "body",
        "rows": [2, 4],
        "cols": [2, 4],
        "style": {
            "background-color": "coral",
            "width": "300px",
            "height": "100px",
        },
    }
]

n = 6
df = pd.DataFrame(
    {
        "a": range(n),
        "b": range(n, n * 2),
        "c": range(n * 2, n * 3),
        "d": range(n * 3, n * 4),
        "e": range(n * 4, n * 5),
    }
)


@render.data_frame
def my_df():
    return render.DataGrid(
        df,
        styles=df_styles,
    )

Screenshot 2024-07-10 at 11 48 13 AM

@roivant-matts
Copy link

Maybe I miss something, but if we wanted to style 14 and 28 how is it done (ie both rows and both columns but not combinatorial)? Two dictionaries in the styles array?

@schloerke
Copy link
Collaborator

if we wanted to style 14 and 28 how is it done (ie both rows and both columns but not combinatorial)? Two dictionaries in the styles array?

If rows is missing, or None, then the corresponding style or class values will be applied to all rows. Same goes for cols. If cols is missing or None, then the style or class values will be applied to all columns.

If both rows and cols are supplied, then the combination of all rows and cols values will be made and their corresponding style or class values will be applied. If no rows or cols are supplied, then it will be applied to every cell.

Obviously it is nice to have multiple style definitions for different locations, so multiple StyleInfo objects can be supplied to the styles= parameter.

Updating the example above...

import pandas as pd

from shiny.express import render

df_styles = [
    {
        "location": "body",
        "style": {
            "background-color": "lightblue",
            "border": "transparent",
            "color": "transparent",
        },
    },
    {
        "location": "body",
        "rows": [2],
        "cols": [2],
        "style": {
            "background-color": "yellow",
            "width": "100px",
            "height": "75px",
        },
    },
    {
        "location": "body",
        "cols": [1, 3, 5, 7],
        "style": {
            "background-color": "yellow",
        },
    },
    {
        "location": "body",
        "rows": [3],
        "cols": [7],
        "style": {
            "background-color": "lightblue",
        },
    },
]

n = 5
df = pd.DataFrame(
    {
        "a": range(n),
        "b": range(n, n * 2),
        "c": range(n * 2, n * 3),
        "d": range(n * 3, n * 4),
        "e": range(n * 4, n * 5),
        "f": range(n * 5, n * 6),
        "g": range(n * 6, n * 7),
        "h": range(n * 7, n * 8),
        "i": range(n * 8, n * 9),
    }
)


@render.data_frame
def my_df():
    return render.DataGrid(
        df,
        styles=df_styles,
    )

Screenshot 2024-07-10 at 4 47 17 PM

@roivant-matts
Copy link

Thanks. Really looking forward to this!

@maxmoro
Copy link

maxmoro commented Jul 11, 2024

Amazing! Thank you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
data frame Related to @render.data_frame enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

4 participants