great-tables icon indicating copy to clipboard operation
great-tables copied to clipboard

feat: implement the `fmt_bar()` function

Open jrycw opened this issue 7 months ago • 0 comments

Hello team,

I found that the bar example is quite inspiring for what we could achieve with Great Tables.

However, it would be great to have a native function like fmt_bar() to handle this directly, rather than manipulating it manually on the dataframe side.

I did a quick experiment, and the implementation seems quite straightforward:

# _formats.py

def create_bar(
    prop_fill: float,
    max_width: int,
    height: int,
    background_color1: str,
    background_color2: str,
) -> str:
    """Create divs to represent prop_fill as a bar."""
    width = round(max_width * prop_fill, 2)
    px_width = f"{width}px"
    return f"""\
    <div style="width: {max_width}px; background-color: {background_color1};">\
        <div style="height:{height}px;width:{px_width};background-color:{background_color2};"></div>\
    </div>\
    """


def fmt_bar(
    self: GTSelf,
    columns: SelectExpr = None,
    rows: int | list[int] | None = None,
    max_width: int = 75,
    height: int = 25,
    background_color1: str = "lightgray",
    background_color2: str = "green",
) -> GTSelf:
    pf_format = partial(
        fmt_bar_context,
        data=self,
        max_width=max_width,
        height=height,
        background_color1=background_color1,
        background_color2=background_color2,
    )
    return fmt_by_context(self, pf_format=pf_format, columns=columns, rows=rows)


def fmt_bar_context(
    x: Any,
    data: GTData,
    max_width: int,
    height: int,
    background_color1: str,
    background_color2: str,
    context: str,
) -> str:
    x_formatted = create_bar(
        x,
        max_width=max_width,
        height=height,
        background_color1=background_color1,
        background_color2=background_color2,
    )
    return x_formatted

That said, I’m also aware that introducing such a function might lead to more incoming requests for custom CSS styling around this.

So I have two thoughts on how we could approach it:

  • We implement a small set of fundamental functions, such as fmt_bar() and potentially a few other core utilities. More advanced or highly customized functions would be left to third-party packages (we might also need to expose fmt_by_context()). In the future, we could allow third-party functions to be mounted to GT via a register mechanism or applied through GT.pipe().

  • We don't include this feature directly in our project. Instead, we demonstrate the capability through documentation — for example, in the GT.fmt() section. We could rewrite the progress bar example using GT.fmt() like this:

import polars as pl
from great_tables import GT

df = (
    pl.DataFrame({"n": [0.21, 0.82, 0.72, 0.56, 0.42, 0.77]})
    .with_columns(pl.col("n").alias("perc"))
    .with_row_index()
)


def create_bar(
    prop_fill: float,
    max_width: int,
    height: int,
    background_color1: str,
    background_color2: str,
) -> str:
    """Create divs to represent prop_fill as a bar."""
    width = round(max_width * prop_fill, 2)
    px_width = f"{width}px"
    return f"""\
    <div style="width: {max_width}px; background-color: {background_color1};">\
        <div style="height:{height}px;width:{px_width};background-color:{background_color2};"></div>\
    </div>\
    """


(
    GT(df)
    .fmt(
        lambda x: create_bar(x, 75, 20, "lightgray", "green"),
        columns="perc",
        rows=pl.col("index").mod(2).eq(0),
    )
    .fmt(
        lambda x: create_bar(x, 75, 20, "lightgray", "lightblue"),
        columns="perc",
        rows=pl.col("index").mod(2).eq(0).not_(),
    )
)

Image

If the team aligns with either of these options, I would be happy to contribute a PR for it!

jrycw avatar Apr 27 '25 08:04 jrycw