Skip to content

feat: implement the fmt_bar() function #677

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

Open
jrycw opened this issue Apr 27, 2025 · 0 comments
Open

feat: implement the fmt_bar() function #677

jrycw opened this issue Apr 27, 2025 · 0 comments

Comments

@jrycw
Copy link
Collaborator

jrycw commented Apr 27, 2025

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!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant