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

blog: add the v0.13.0 release post #488

Merged
merged 31 commits into from
Oct 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
96f64d8
Add initial version of blog post
rich-iannone Oct 8, 2024
b5f25d8
Update index.qmd
rich-iannone Oct 8, 2024
d8ff727
Update index.qmd
rich-iannone Oct 8, 2024
817631b
Add exclamation mark
rich-iannone Oct 8, 2024
82d592c
Update index.qmd
rich-iannone Oct 8, 2024
2949a19
Update index.qmd
rich-iannone Oct 8, 2024
c5e9a49
Make correction to table code
rich-iannone Oct 8, 2024
857d7da
Update index.qmd
rich-iannone Oct 8, 2024
e0fa179
Update index.qmd
rich-iannone Oct 8, 2024
7c22fef
Update index.qmd
rich-iannone Oct 8, 2024
b65739e
Update index.qmd
rich-iannone Oct 8, 2024
68ba343
Update index.qmd
rich-iannone Oct 8, 2024
92217e1
Update index.qmd
rich-iannone Oct 8, 2024
efc8bc4
Update index.qmd
rich-iannone Oct 8, 2024
7526292
Update index.qmd
rich-iannone Oct 8, 2024
fbef98e
Update index.qmd
rich-iannone Oct 8, 2024
7631a12
Update docs/blog/introduction-0.13.0/index.qmd
rich-iannone Oct 8, 2024
ca04ccc
Update docs/blog/introduction-0.13.0/index.qmd
rich-iannone Oct 8, 2024
54d7d7f
Update docs/blog/introduction-0.13.0/index.qmd
rich-iannone Oct 8, 2024
94238bb
Update docs/blog/introduction-0.13.0/index.qmd
rich-iannone Oct 8, 2024
fdba398
Update index.qmd
rich-iannone Oct 8, 2024
7707bb1
Update docs/blog/introduction-0.13.0/index.qmd
rich-iannone Oct 8, 2024
8bf5dfe
Update index.qmd
rich-iannone Oct 8, 2024
2f0f62b
Update index.qmd
rich-iannone Oct 8, 2024
402a914
Add locations image
rich-iannone Oct 9, 2024
8c27676
Remove list of locations
rich-iannone Oct 9, 2024
23d4ddf
Update index.qmd
rich-iannone Oct 9, 2024
683e91c
Update index.qmd
rich-iannone Oct 9, 2024
d52bbf5
Make some text adjustments
rich-iannone Oct 9, 2024
735eee0
Update index.qmd
rich-iannone Oct 9, 2024
e9a6a98
Update index.qmd
rich-iannone Oct 10, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
270 changes: 270 additions & 0 deletions docs/blog/introduction-0.13.0/index.qmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,270 @@
---
title: "Great Tables `v0.13.0`: Applying styles to all table locations"
html-table-processing: none
author: Rich Iannone and Michael Chow
date: 2024-10-10
freeze: true
jupyter: python3
---

We did something in Great Tables (`0.13.0`) that'll make your tables that much more customizable: super *fine-grained* ways of setting styles throughout the table. Before you were largely constrained to styling through the following strategies:

1. use a limited set of styles (e.g., background color, font weight, etc.) to different table locations like the stub, the column labels, etc., through `tab_options()`
2. use `tab_style()` with a larger set of styling options for the table body cells (specified by `loc.body()`)

In `v0.13.0`, we can target much more than just the table body! Here is the expanded set of `loc.*()` methods along with the locations that they can target.

![](./GT-locations-map.png)

This augmentation of the `loc` module to include all locations in the table means that there won't be a spot in the table to which you can't add styling. This is terrific because it gives you free rein to fully customize the look of your table.

Let's make a table and see how this new feature could be used.

### Starting things off with a big GT table

The table we'll make uses the `nuclides` dataset (available in the `great_tables.data` module). Through use of the `tab_*()` methods, quite a few table components (hence *locations*) will be added. We have hidden the code here because it is quite lengthy but you're encouraged to check it out to glean some interesting GT tricks.

```{python}

# | code-fold: true
# | code-summary: "Show the code"

from great_tables import GT, md, style, loc, google_font
from great_tables.data import nuclides
import polars as pl
import polars.selectors as cs

nuclides_mini = (
pl.from_pandas(nuclides)
.filter(pl.col("element") == "C")
.with_columns(pl.col("nuclide").str.replace(r"[0-9]+$", ""))
.with_columns(mass_number=pl.col("z") + pl.col("n"))
.with_columns(
isotope=pl.concat_str(pl.col("element") + "-" + pl.col("mass_number").cast(pl.String))
)
.select(["isotope", "atomic_mass", "half_life", "isospin", "decay_1", "decay_2", "decay_3"])
)

gt_tbl = (
GT(nuclides_mini, rowname_col="isotope")
.tab_header(
title="Isotopes of Carbon",
subtitle="There are two stable isotopes of carbon and twelve that are unstable.",
)
.tab_spanner(label="Decay Mode", columns=cs.starts_with("decay"))
.tab_source_note(md("Data obtained from the *nuclides* dataset."))
.tab_stubhead(label="Isotope")
.fmt_scientific(columns="half_life")
.fmt_number(
columns="atomic_mass",
decimals=4,
scale_by=1 / 1e6,
)
.sub_missing(columns="half_life", missing_text=md("**STABLE**"))
.sub_missing(columns=cs.starts_with("decay"))
.cols_label(
atomic_mass="Atomic Mass",
half_life="Half Life, s",
isospin="Isospin",
decay_1="1",
decay_2="2",
decay_3="3",
)
.cols_align(align="center", columns=[cs.starts_with("decay"), "isospin"])
.opt_align_table_header(align="left")
.opt_table_font(font=google_font(name="IBM Plex Sans"))
.opt_vertical_padding(scale=0.5)
.opt_horizontal_padding(scale=2)
)

gt_tbl
```

This table will serve as a great starting point for demonstrating all the things you can now do with `tab_style()`. And the following checklist will serve as a rough plan for how we will style the table:

- use `loc.body()` to emphasize isotope half-life values
- employ `loc.stub()` to draw attention to isotope names (and also point out the 'STABLE' rows)
- use `style.css()` for creating custom CSS styles (e.g., to indent row labels for stable isotopes)
- work with composite locations and style the whole header and footer quite simply
- set the default table body fill with `tab_options()`

Really this'll be `tab_style()` like you've never seen it before, so let's get on with it.

### Styling the body

First, we'll use `loc.body()` to emphasize half life values in two ways:

* Make the values in the `atomic_mass` and `half_life` use a monospace font.
* fill the background of isotopes with STABLE half lives to be PaleTurquoise.

```{python}
gt_tbl = (
gt_tbl
.tab_style(
style=style.text(font=google_font(name="IBM Plex Mono")),
locations=loc.body(columns=["atomic_mass", "half_life"])
)
.tab_style(
style=[style.text(color="Navy"), style.fill(color="PaleTurquoise")],
locations=loc.body(columns="half_life", rows=pl.col("half_life").is_not_null())
)
)

gt_tbl
```

rich-iannone marked this conversation as resolved.
Show resolved Hide resolved
Note these important pieces in the code:

* setting monospace font: we used [`google_font()`](/reference/google_font.qmd) (added in the previous release) to apply the monospaced font IBM Plex Mono.
* filling unstable half lives to turquoise: because the half life cells with the value STABLE are actually missing in the underlying data, and filled in using `GT.sub_missing()`, we used the polars expression `pl.col("half_life").is_not_null()` to target everything that isn't STABLE.

This is mainly a reminder that Polars expressions are quite something. And targeting cells in the body with `loc.body(rows=...)` can be powerful by extension.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
This is mainly a reminder that Polars expressions are quite something. And targeting cells in the body with `loc.body(rows=...)` can be powerful by extension.
Polars expressions are quite something. And targeting cells in the body with `loc.body(rows=...)` can be powerful by extension.


### Don't forget the stub!

We mustn't forget the stub. It's a totally separate location, being off to the side and having the important responsibility of holding the row labels. Here, we are going to do two things:

1. Change the fill color (to 'Linen') and make the text bold for the *entire stub*
2. Highlight the rows where we have stable isotopes (the extent is both for the stub and the body cells)

```{python}
gt_tbl = (
gt_tbl
.tab_style(
style=[style.fill(color="Linen"), style.text(weight="bold")],
locations=loc.stub()
)
.tab_style(
style=style.fill(color="LightCyan"),
locations=[
loc.body(rows=pl.col("half_life").is_null()),
loc.stub(rows=pl.col("half_life").is_null())
]
)
)

gt_tbl
```

rich-iannone marked this conversation as resolved.
Show resolved Hide resolved
For task #1, a simple `.tab_style(..., locations=loc.stub())` targeted the entire stub.

Task #2 is more interesting. Like `loc.body()`, `loc.stub()` has a `rows=` argument that can target specific rows with Polars expressions. We used the same Polars expression as in the previous section to target those rows that belong to the stable isotopes.

We've dressed up the stub so that it is that much more prominent. And that linen-colored stub goes so well with the light-cyan rows, representative of carbon-12 and carbon-13!

### Using custom style rules with the new `style.css()`

Aside from decking out the `loc` module with all manner of location methods, we've added a little something to the `style` module: `style.css()`! What's it for? It lets you supply style declarations to its single `rule=` argument.

As an example, I might want to indent some text in one or more table cells. You can't really do that with the `style.text()` method since it doesn't have an `indent=` argument. So, in Great Tables `0.13.0` you can manually indent the row label text for the 'STABLE' rows using a CSS style rule:

```{python}
gt_tbl = (
gt_tbl
.tab_style(
style=style.css(rule="text-indent: 4px;"),
locations=loc.stub(rows=pl.col("half_life").is_null())
)
)

gt_tbl
```

We targeted the cells in the stub that corresponded to the stable isotopes (carbon-12 and -13) with a Polars expression (same one as in the previous code cell) and now we have a 4px indentation of the 'C-12' and 'C-13' text! This new bonus functionality really allows almost any type of styling possible, provided you have those CSS skillz.

### The *combined* location helpers: `loc.column_header()` and `loc.footer()`

Look, I know we brought up the expression *fine-grained* before---right in the first paragraph---but sometimes you need just the opposite. There are lots of little locations in a GT table and some make for logical groupings. To that end, we have the concept of *combined* location helpers.

Let's set a grey background fill on the stubhead, column header, and footer:

```{python}
gt_tbl = (
gt_tbl
.tab_style(
style=[style.text(v_align="middle"), style.fill(color="#EEEEEE")],
locations=[loc.stubhead(), loc.column_header(), loc.footer()]
)
)

gt_tbl
```

rich-iannone marked this conversation as resolved.
Show resolved Hide resolved
The [`loc.column_header()`](/reference/loc.column_header.qmd) location always targets both `loc.column_labels()` and `loc.spanner_labels()`.

A good strategy for your tables would be to style with combined location helpers first and then drill into the specific cells of those super locations with more fine-grained styles in a later `tab_style()` call.

### Styling the title and the subtitle

Although it really doesn't appear to have separate locations, the table header (produced by way of `tab_header()`) can have two of them: the title and the subtitle (the latter is optional). These can be targeted via `loc.title()` and `loc.subtitle()`. Let's focus in on the title location and set an aliceblue background fill on the title, along with some font and border adjustments.

```{python}
gt_tbl = (
gt_tbl
.tab_style(
style=[
style.text(size="24px"),
style.fill(color="aliceblue"),
style.borders(sides="bottom", color="#BFDFF6", weight="2px")
],
locations=loc.title()
)
)

gt_tbl
```

Looks good. Notice that the title location is separate from the subtitle one, the background fill reveals the extent of its area.

A subtitle is an optional part of the header. We do have one in our table example, so let's style that as well. The `style.css()` method will be used to give the subtitle text some additional top and bottom padding, and, we'll put in a fancy background involving a linear gradient.

```{python}
gt_tbl = (
gt_tbl
.tab_style(
style=style.css(rule="padding-top: 5px;"
"padding-bottom: 5px;"
"background-image: linear-gradient(120deg, #d4fc79 0%, #96f6a1 100%);"
),
locations=loc.subtitle()
)
)

gt_tbl
```

None of what was done above could be done prior to `v0.13.0`. The `style.css()` method makes this all possible.

The combined location helper for the title and the subtitle locations is `loc.header()`. As mentioned before, it can be used as a shorthand for `locations=[loc.title(), loc_subtitle()]` and it's useful here where we want to change the font for the title and subtitle text.

```{python}
gt_tbl = (
gt_tbl
.tab_style(
style=style.text(font=google_font("IBM Plex Serif")),
locations=loc.header()
)
)

gt_tbl
```

Though the order of things matters when setting styles via `tab_style()`, it's not a problem here to set a style for the combined 'header' location after doing so for the 'title' and 'subtitle' locations because the 'font' attribute *wasn't* set by `tab_style()` for those smaller locations.

### How `tab_style()` fits in with `tab_options()`

When it comes to styling, you can use `tab_options()` for some of the basics and use `tab_style()` for the more demanding styling tasks. And you could combine the usage of both in your table. Let's set a default honeydew background fill on the body values:

```{python}
gt_tbl = gt_tbl.tab_options(table_background_color="HoneyDew")

gt_tbl
```

In the example, we asked for the HoneyDew background fill on the entire table with `tab_options()`. However, even though `tab_options()` was used after those `tab_style()` invocations, the 'HoneyDew' background color was only applied to the locations that didn't have a background color set through `tab_style(). The important takeaway here is that the precedence (or priority) is *always* given to `tab_style()`, regardless of the order of invocation.

### Wrapping up

We'd like to thank [Tim Paine](https://github.com/timkpaine) for getting the expanded `loc` work off the ground. Additionally, we are grateful to [Jerry Wu](https://github.com/jrycw) for his contributions to the `v0.13.0` release of the package.

We'd be very pleased to receive comments or suggestions on the new functionality. [GitHub Issues](https://github.com/posit-dev/great-tables/issues) or [GitHub Discussions](https://github.com/posit-dev/great-tables/discussions) are both fine venues for getting in touch with us. Finally, if ever you want to talk about tables with us, you're always welcome to jump into our [Discord Server](https://discord.com/invite/Ux7nrcXHVV).
Loading