PablocFonseca / streamlit-aggrid

Implementation of Ag-Grid component for Streamlit
https://pypi.org/project/streamlit-aggrid/
MIT License
1.06k stars 202 forks source link

Add custom CSS classes to AgGrid constructor #35

Closed ljnsn closed 2 years ago

ljnsn commented 3 years ago

Hi @PablocFonseca ,

first all, thanks for this component!

I would like to apply conditional formatting to rows in the grid using the rowClassRules grid option. From the documentation, I think this is the correct way to apply conditional formatting to rows, please let me know if there is another way that I'm not aware of.

When I add rowClassRules via the gridOptionsBuilder, the classes are applied correctly to the respective rows, but the styling is not. This is because the html added with st.markdown is added to the main page and the component is rendered in an iframe that does not have access to the parent page's CSS.

I think a solution would be to provide the custom classes to the AgGrid constructor that then adds them to the iframe page's styles. Is this something you would be open to adding?

Here's an example app with my simplified use case to illustrate:

import datetime

import numpy as np
import pandas as pd
import streamlit as st
from st_aggrid import AgGrid, GridOptionsBuilder

styles = """
<style>
.trade-buy-green {
    color: green
}
.trade-sell-red {
    color: red
}
</style>
"""
st.markdown(styles, unsafe_allow_html=True)

now = int(datetime.datetime.now().timestamp())
start_ts = now - 3 * 30 * 24 * 60 * 60

@st.cache
def make_data():
    df = pd.DataFrame(
        {
            "timestamp": np.random.randint(start_ts, now, 20),
            "side": [np.random.choice(["buy", "sell"]) for i in range(20)],
            "base": [np.random.choice(["JPY", "GBP", "CAD"]) for i in range(20)],
            "quote": [np.random.choice(["EUR", "USD"]) for i in range(20)],
            "amount": list(
                map(
                    lambda a: round(a, 2),
                    np.random.rand(20) * np.random.randint(1, 1000, 20),
                )
            ),
            "price": list(
                map(
                    lambda p: round(p, 5),
                    np.random.rand(20) * np.random.randint(1, 10, 20),
                )
            ),
        }
    )
    df["cost"] = round(df.amount * df.price, 2)
    df.insert(
        0,
        "datetime",
        df.timestamp.apply(lambda ts: datetime.datetime.fromtimestamp(ts)),
    )
    return df.sort_values("timestamp").drop("timestamp", axis=1)

df = make_data()
gb = GridOptionsBuilder.from_dataframe(df)

row_class_rules = {
    "trade-buy-green": "data.side == 'buy'",
    "trade-sell-red": "data.side == 'sell'",
}
gb.configure_grid_options(rowClassRules=row_class_rules)
grid_options = gb.build()

st.title("rowClassRules Test")
AgGrid(df, theme="streamlit", gridOptions=grid_options)
ljnsn commented 3 years ago

I made a quick implementation of this in my fork, if you're interested I'll open a PR.

PablocFonseca commented 2 years ago

Please send the PR!

usbader commented 2 years ago

This feature is nice to have.

vtslab commented 2 years ago

Hi @ljnsn, are you sure your example can work? It adds row classes which are not easily reachable for styles defined outside the iframe in which aggrid resides (that is not with a st.markdown, like you propose). I ended up styling rows in the following way (using the JsCode utility of st-aggrid):

rowstyle_js = JsCode("""
    function(params) {
        if (params.data.Status == 9999999999) {
            return {
                'color': 'white',
                'backgroundColor': 'darkred'
            }
        } else {
            return {
                'color': 'white',
                'backgroundColor': 'darkblue'
            }
        }
    };
""")
gb.configure_grid_options(**dict(getRowStyle=rowstyle_js))

Here, 'Status' is one of the column names of the grid.

ljnsn commented 2 years ago

The example, as is, does not work. You're right in that the styles are not accessible in the IFrame which is why I add them to the IFrame's stylesheet in my PR. This is done via a custom_css argument that is passed to the AgGrid constructor, rather than using st.markdown, and then the conditional styling options just work. What you're proposing is possible, but I prefer to avoid writing JS code in string whenever I can. Sometimes it's necessary, but since AgGrid has the rowClassRule option I think we should use that (there is also cellClassRule or similar).

Check out my PR and let me know if it works for you, I'm happy to make adjustments to it based on yours or Pablo's feedback.