Great Tables v0.13.0: Applying styles to all table locations
Author
Rich Iannone and Michael Chow
Published
October 10, 2024
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:
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()
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.
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.
Show the code
from great_tables import GT, md, style, loc, google_fontfrom great_tables.data import nuclidesimport polars as plimport polars.selectors as csnuclides_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
Isotopes of Carbon
There are two stable isotopes of carbon and twelve that are unstable.
Isotope
Atomic Mass
Half Life, s
Isospin
Decay Mode
1
2
3
C-8
8.0376
3.51 × 10−21
2
2P
—
—
C-9
9.0310
1.26 × 10−1
3/2
EC+B+
B+P
B+A
C-10
10.0169
1.93 × 101
1
EC+B+
—
—
C-11
11.0114
1.22 × 103
1/2
EC+B+
—
—
C-12
12.0000
STABLE
0
—
—
—
C-13
13.0034
STABLE
1/2
—
—
—
C-14
14.0032
1.80 × 1011
1
B-
—
—
C-15
15.0106
2.45
3/2
B-
—
—
C-16
16.0147
7.47 × 10−1
2
B-
B-N
—
C-17
17.0226
1.93 × 10−1
None
B-
B-N
—
C-18
18.0268
9.20 × 10−2
3
B-
B-N
—
C-19
19.0348
4.63 × 10−2
None
B-
B-N
B-2N
C-20
20.0403
1.63 × 10−2
None
B-
B-N
B-2N
C-22
22.0576
6.10 × 10−3
None
B-
B-N
B-2N
Data obtained from the nuclides dataset.
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.
There are two stable isotopes of carbon and twelve that are unstable.
Isotope
Atomic Mass
Half Life, s
Isospin
Decay Mode
1
2
3
C-8
8.0376
3.51 × 10−21
2
2P
—
—
C-9
9.0310
1.26 × 10−1
3/2
EC+B+
B+P
B+A
C-10
10.0169
1.93 × 101
1
EC+B+
—
—
C-11
11.0114
1.22 × 103
1/2
EC+B+
—
—
C-12
12.0000
STABLE
0
—
—
—
C-13
13.0034
STABLE
1/2
—
—
—
C-14
14.0032
1.80 × 1011
1
B-
—
—
C-15
15.0106
2.45
3/2
B-
—
—
C-16
16.0147
7.47 × 10−1
2
B-
B-N
—
C-17
17.0226
1.93 × 10−1
None
B-
B-N
—
C-18
18.0268
9.20 × 10−2
3
B-
B-N
—
C-19
19.0348
4.63 × 10−2
None
B-
B-N
B-2N
C-20
20.0403
1.63 × 10−2
None
B-
B-N
B-2N
C-22
22.0576
6.10 × 10−3
None
B-
B-N
B-2N
Data obtained from the nuclides dataset.
Note these important pieces in the code:
setting monospace font: we used google_font() (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.
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:
Change the fill color (to ‘Linen’) and make the text bold for the entire stub
Highlight the rows where we have stable isotopes (the extent is both for the stub and the body cells)
There are two stable isotopes of carbon and twelve that are unstable.
Isotope
Atomic Mass
Half Life, s
Isospin
Decay Mode
1
2
3
C-8
8.0376
3.51 × 10−21
2
2P
—
—
C-9
9.0310
1.26 × 10−1
3/2
EC+B+
B+P
B+A
C-10
10.0169
1.93 × 101
1
EC+B+
—
—
C-11
11.0114
1.22 × 103
1/2
EC+B+
—
—
C-12
12.0000
STABLE
0
—
—
—
C-13
13.0034
STABLE
1/2
—
—
—
C-14
14.0032
1.80 × 1011
1
B-
—
—
C-15
15.0106
2.45
3/2
B-
—
—
C-16
16.0147
7.47 × 10−1
2
B-
B-N
—
C-17
17.0226
1.93 × 10−1
None
B-
B-N
—
C-18
18.0268
9.20 × 10−2
3
B-
B-N
—
C-19
19.0348
4.63 × 10−2
None
B-
B-N
B-2N
C-20
20.0403
1.63 × 10−2
None
B-
B-N
B-2N
C-22
22.0576
6.10 × 10−3
None
B-
B-N
B-2N
Data obtained from the nuclides dataset.
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:
There are two stable isotopes of carbon and twelve that are unstable.
Isotope
Atomic Mass
Half Life, s
Isospin
Decay Mode
1
2
3
C-8
8.0376
3.51 × 10−21
2
2P
—
—
C-9
9.0310
1.26 × 10−1
3/2
EC+B+
B+P
B+A
C-10
10.0169
1.93 × 101
1
EC+B+
—
—
C-11
11.0114
1.22 × 103
1/2
EC+B+
—
—
C-12
12.0000
STABLE
0
—
—
—
C-13
13.0034
STABLE
1/2
—
—
—
C-14
14.0032
1.80 × 1011
1
B-
—
—
C-15
15.0106
2.45
3/2
B-
—
—
C-16
16.0147
7.47 × 10−1
2
B-
B-N
—
C-17
17.0226
1.93 × 10−1
None
B-
B-N
—
C-18
18.0268
9.20 × 10−2
3
B-
B-N
—
C-19
19.0348
4.63 × 10−2
None
B-
B-N
B-2N
C-20
20.0403
1.63 × 10−2
None
B-
B-N
B-2N
C-22
22.0576
6.10 × 10−3
None
B-
B-N
B-2N
Data obtained from the nuclides dataset.
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:
There are two stable isotopes of carbon and twelve that are unstable.
Isotope
Atomic Mass
Half Life, s
Isospin
Decay Mode
1
2
3
C-8
8.0376
3.51 × 10−21
2
2P
—
—
C-9
9.0310
1.26 × 10−1
3/2
EC+B+
B+P
B+A
C-10
10.0169
1.93 × 101
1
EC+B+
—
—
C-11
11.0114
1.22 × 103
1/2
EC+B+
—
—
C-12
12.0000
STABLE
0
—
—
—
C-13
13.0034
STABLE
1/2
—
—
—
C-14
14.0032
1.80 × 1011
1
B-
—
—
C-15
15.0106
2.45
3/2
B-
—
—
C-16
16.0147
7.47 × 10−1
2
B-
B-N
—
C-17
17.0226
1.93 × 10−1
None
B-
B-N
—
C-18
18.0268
9.20 × 10−2
3
B-
B-N
—
C-19
19.0348
4.63 × 10−2
None
B-
B-N
B-2N
C-20
20.0403
1.63 × 10−2
None
B-
B-N
B-2N
C-22
22.0576
6.10 × 10−3
None
B-
B-N
B-2N
Data obtained from the nuclides dataset.
The loc.column_header() 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.
There are two stable isotopes of carbon and twelve that are unstable.
Isotope
Atomic Mass
Half Life, s
Isospin
Decay Mode
1
2
3
C-8
8.0376
3.51 × 10−21
2
2P
—
—
C-9
9.0310
1.26 × 10−1
3/2
EC+B+
B+P
B+A
C-10
10.0169
1.93 × 101
1
EC+B+
—
—
C-11
11.0114
1.22 × 103
1/2
EC+B+
—
—
C-12
12.0000
STABLE
0
—
—
—
C-13
13.0034
STABLE
1/2
—
—
—
C-14
14.0032
1.80 × 1011
1
B-
—
—
C-15
15.0106
2.45
3/2
B-
—
—
C-16
16.0147
7.47 × 10−1
2
B-
B-N
—
C-17
17.0226
1.93 × 10−1
None
B-
B-N
—
C-18
18.0268
9.20 × 10−2
3
B-
B-N
—
C-19
19.0348
4.63 × 10−2
None
B-
B-N
B-2N
C-20
20.0403
1.63 × 10−2
None
B-
B-N
B-2N
C-22
22.0576
6.10 × 10−3
None
B-
B-N
B-2N
Data obtained from the nuclides dataset.
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.
There are two stable isotopes of carbon and twelve that are unstable.
Isotope
Atomic Mass
Half Life, s
Isospin
Decay Mode
1
2
3
C-8
8.0376
3.51 × 10−21
2
2P
—
—
C-9
9.0310
1.26 × 10−1
3/2
EC+B+
B+P
B+A
C-10
10.0169
1.93 × 101
1
EC+B+
—
—
C-11
11.0114
1.22 × 103
1/2
EC+B+
—
—
C-12
12.0000
STABLE
0
—
—
—
C-13
13.0034
STABLE
1/2
—
—
—
C-14
14.0032
1.80 × 1011
1
B-
—
—
C-15
15.0106
2.45
3/2
B-
—
—
C-16
16.0147
7.47 × 10−1
2
B-
B-N
—
C-17
17.0226
1.93 × 10−1
None
B-
B-N
—
C-18
18.0268
9.20 × 10−2
3
B-
B-N
—
C-19
19.0348
4.63 × 10−2
None
B-
B-N
B-2N
C-20
20.0403
1.63 × 10−2
None
B-
B-N
B-2N
C-22
22.0576
6.10 × 10−3
None
B-
B-N
B-2N
Data obtained from the nuclides dataset.
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.
There are two stable isotopes of carbon and twelve that are unstable.
Isotope
Atomic Mass
Half Life, s
Isospin
Decay Mode
1
2
3
C-8
8.0376
3.51 × 10−21
2
2P
—
—
C-9
9.0310
1.26 × 10−1
3/2
EC+B+
B+P
B+A
C-10
10.0169
1.93 × 101
1
EC+B+
—
—
C-11
11.0114
1.22 × 103
1/2
EC+B+
—
—
C-12
12.0000
STABLE
0
—
—
—
C-13
13.0034
STABLE
1/2
—
—
—
C-14
14.0032
1.80 × 1011
1
B-
—
—
C-15
15.0106
2.45
3/2
B-
—
—
C-16
16.0147
7.47 × 10−1
2
B-
B-N
—
C-17
17.0226
1.93 × 10−1
None
B-
B-N
—
C-18
18.0268
9.20 × 10−2
3
B-
B-N
—
C-19
19.0348
4.63 × 10−2
None
B-
B-N
B-2N
C-20
20.0403
1.63 × 10−2
None
B-
B-N
B-2N
C-22
22.0576
6.10 × 10−3
None
B-
B-N
B-2N
Data obtained from the nuclides dataset.
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:
There are two stable isotopes of carbon and twelve that are unstable.
Isotope
Atomic Mass
Half Life, s
Isospin
Decay Mode
1
2
3
C-8
8.0376
3.51 × 10−21
2
2P
—
—
C-9
9.0310
1.26 × 10−1
3/2
EC+B+
B+P
B+A
C-10
10.0169
1.93 × 101
1
EC+B+
—
—
C-11
11.0114
1.22 × 103
1/2
EC+B+
—
—
C-12
12.0000
STABLE
0
—
—
—
C-13
13.0034
STABLE
1/2
—
—
—
C-14
14.0032
1.80 × 1011
1
B-
—
—
C-15
15.0106
2.45
3/2
B-
—
—
C-16
16.0147
7.47 × 10−1
2
B-
B-N
—
C-17
17.0226
1.93 × 10−1
None
B-
B-N
—
C-18
18.0268
9.20 × 10−2
3
B-
B-N
—
C-19
19.0348
4.63 × 10−2
None
B-
B-N
B-2N
C-20
20.0403
1.63 × 10−2
None
B-
B-N
B-2N
C-22
22.0576
6.10 × 10−3
None
B-
B-N
B-2N
Data obtained from the nuclides dataset.
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 totab_style()`, regardless of the order of invocation.
Wrapping up
We’d like to thank Tim Paine for getting the expanded loc work off the ground. Additionally, we are grateful to Jerry Wu 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 or GitHub 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.