posit-dev / great-tables

Make awesome display tables using Python.
https://posit-dev.github.io/great-tables/
MIT License
1.77k stars 56 forks source link

How about adding an app demo section to the documentation? #417

Open jrycw opened 3 weeks ago

jrycw commented 3 weeks ago

Hello @rich-iannone and @machow ,

I've noticed that some users have questions about how to integrate great-tables into their applications. The requests come from various frameworks, including Streamlit, Panel, FastAPI, Flask, and Django, among others. While it's not feasible for us to cover every possible use case, I think it would be beneficial to create a section in the documentation (or a separate demo repository) that demonstrates how to render tables in raw HTML within these applications.

I've drafted four examples using Streamlit, FastHTML, FastAPI and Django. Regarding the Panel app, Marc Skov Madsen has already created a great demonstration here. What do you think of this approach?

jrycw commented 3 weeks ago

I submitted a color picker example to FastHTML-Gallery to demonstrate how to integrate Great Tables with FastHTML here.

The maintainer suggested that we could create a plugin to help users more easily adopt Great Tables in a FastHTML app.

I've drafted a rough implementation at the end of this message. I created a decorator called gt2fasthtml that allows users to return a GT instance, with the output of GT.as_raw_html() wrapped in FastHTML components.

The implementation seems straightforward, and we could consider adding a decorators.py file to include various decorators for different apps. For more complex apps like Django, users might need a separate plugin or template tag to achieve this. However, for apps primarily designed for data scientists, the decorators.py file could meet their needs without requiring an additional plugin. Which approach do you prefer?

from functools import cache, partial, wraps

import polars as pl
from fasthtml.common import (
    H2,
    Card,
    Div,
    Form,
    Grid,
    Input,
    Main,
    NotStr,
    Title,
    Titled,
    fast_app,
)
from great_tables import GT, html
from great_tables.data import sza

app, rt = fast_app()

def gt2fasthtml(func=None, **div_kwargs):
    """
    https://pybit.es/articles/decorator-optional-argument/
    """
    if func is None:
        return partial(gt2fasthtml, **div_kwargs)

    @wraps(func)
    def wrapper(*args, **kwargs):
        gtbl = func(*args, **kwargs)
        gtbl_html = gtbl.as_raw_html()
        return Div(NotStr(gtbl_html), **div_kwargs)

    return wrapper

@cache
def get_sza_pivot():
    return (
        pl.from_pandas(sza)
        .filter((pl.col("latitude") == "20") & (pl.col("tst") <= "1200"))
        .select(pl.col("*").exclude("latitude"))
        .drop_nulls()
        .pivot(values="sza", index="month", on="tst", sort_columns=True)
    )

@gt2fasthtml(id="gt")
def get_gtbl(color1: str = "#663399", color2: str = "#FFA500"):
    return (
        GT(get_sza_pivot(), rowname_col="month")
        .data_color(
            domain=[90, 0],
            palette=[color1, "white", color2],
            na_color="white",
        )
        .tab_header(
            title="Solar Zenith Angles from 05:30 to 12:00",
            subtitle=html("Average monthly values at latitude of 20&deg;N."),
        )
        .sub_missing(missing_text="")
    )

@app.post("/submit")
def post(d: dict):
    return get_gtbl(**d)

@app.get("/")
def homepage():
    return (
        Title("FastHTML-GT Website"),
        Titled("Great Tables shown in FastHTML", style="text-align:center"),
        Main(
            Form(
                hx_post="/submit",
                hx_target="#gt",
                hx_trigger="input",
                hx_swap="outerHTML",
            )(
                Grid(
                    Div(),
                    Card(
                        H2("Color1"), Input(type="color",
                                            id="color1", value="#663399")
                    ),
                    Card(
                        H2("Color2"), Input(type="color",
                                            id="color2", value="#FFA500")
                    ),
                    Div(),
                )
            ),
            get_gtbl(),
            cls="container",
        ),
    )
jrycw commented 3 weeks ago

For the Streamlit app, we could introduce a gt2streamlit decorator that automatically calls GT.as_raw_html() and sets unsafe_allow_html=False, similar to the code at the end. I'm not sure if this approach will be well-received, but since users seem to be confused about how to render our tables, I reckon this decorator could reduce some of the friction for them.

from functools import wraps

import polars as pl
import streamlit as st
from great_tables import GT, html
from great_tables.data import sza

def gt2streamlit(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        gtbl = func(*args, **kwargs)
        gtbl_html = gtbl.as_raw_html()
        return st.write(gtbl_html, unsafe_allow_html=True)

    return wrapper

@st.cache_data
def get_sza():
    return pl.from_pandas(sza)

@gt2streamlit
def st_write_gt(df):
    sza_pivot = (
        df
        .filter((pl.col("latitude") == "20") & (pl.col("tst") <= "1200"))
        .select(pl.col("*").exclude("latitude"))
        .drop_nulls()
        .pivot(values="sza", index="month", on="tst", sort_columns=True)
    )

    return (
        GT(sza_pivot, rowname_col="month")
        .data_color(
            domain=[90, 0],
            palette=["rebeccapurple", "white", "orange"],
            na_color="white",
        )
        .tab_header(
            title="Solar Zenith Angles from 05:30 to 12:00",
            subtitle=html("Average monthly values at latitude of 20&deg;N."),
        )
        .sub_missing(missing_text="")
    )

st.title("Great Tables shown in Streamlit")
st_write_gt(get_sza())
jrycw commented 3 weeks ago

The FastHTML example has been merged, and you can try interacting with it here.