Learning gt: Creating a Retro League Table

R
data analysis
sports
Author

David Schoch

Published

November 18, 2024

It has been way too long since I had time to prepare a blog post that is neither a story about Open Source Development, or an announcement of yet another R package.

I meant to look into gt, a package to create wonderful-looking tables in R, for a very long time but everytime I get some sort of motivation, I shy away because of how complicated it looks to built a table. Hundreds of lines of codes for a 10 line table? Yeah no thank you knitr::kable() it is. But not this time.

Learning by breaking

Whenever I want to learn something new in R, I need a personal project for which this might be relevant. I am very bad at following tutorials. Basically all my data science, web scrapping, and javascript knowledge comes from my football analytics website that I have been maintaining for about 8 years. So I decided, why not football data again and decided to create a simple league table, but with a twist. I try to do something mildly absurd that requires enough extra effort to learn as much about the package as possible. When I wanted to built my first quarto website, I created quartocities, an homage to geocities and the beauty of 90s webpages. I didn’t break quarto, but got to learn so much about Lua, shortcodes, filters just to get those funky gifs everywhere. I am not gonna add that level of absurdity to the league table. For whatever reason, I decided that the table should have a vintage look. With this, I was hopping to get into as many of the styling elements as possible.

Setup

Besides the gt package, I will also use the gtExtras package which offers even more styling possibilities.

library(gt)
library(gtExtras)
library(tidyverse)

# some retro color palettes
cols <- c("#FAEBCC", "#b3e3e0", "#68C7C1", "#FACA78", "#F57F5B", "#DD5341", "#794A3A")
wld <- c("#354065", "#711233", "#666666")

The underlying data (current league table and results) was obtained from weltfussball.de.

Vanilla table

Here is the table without any form of styling.

tbl1 <- tbl |>
  gt()

tbl1
number img mannschaft pk sp wdl tore dif form
1 crests/bayern-munchen-2017-2025-logo.png Bayern München 48 19 15, 3, 1 58:16 42 W, W, W, W, D, D, W, W, W, W, W, D, W, L, W, W, W, W, W
2 crests/bayer-04-leverkusen-2005-logo.png Bayer Leverkusen 42 19 12, 6, 1 46:26 20 W, L, W, W, D, D, W, D, D, D, W, W, W, W, W, W, W, W, D
3 crests/eintracht-frankfurt-1998-logo.png Eintracht Frankfurt 37 19 11, 4, 4 44:26 18 L, W, W, W, W, D, L, D, W, W, W, W, D, L, L, W, W, W, D
4 crests/vfb-stuttgart-2014-logo.png VfB Stuttgart 32 19 9, 5, 5 36:28 8 L, D, W, W, D, D, L, W, D, L, W, D, W, W, L, W, W, W, L
5 crests/rb-leipzig-2020-logo.png RB Leipzig 32 19 9, 5, 5 34:29 5 W, W, D, D, W, W, W, W, L, D, L, L, W, W, L, W, L, D, D
6 crests/mainz-2018-logo.png 1. FSV Mainz 05 31 19 9, 4, 6 33:23 10 D, D, L, W, L, W, L, D, D, W, W, W, L, W, W, W, L, L, W
7 crests/vfl-wolfsburg-2002-logo.png VfL Wolfsburg 28 19 8, 4, 7 42:34 8 L, W, L, L, D, W, L, D, D, W, W, W, W, L, L, W, W, L, D
8 crests/borussia-monchengladbach-1999-logo.png Bor. Mönchengladbach 27 19 8, 3, 8 30:29 1 L, W, L, L, W, L, W, D, W, D, W, L, D, W, W, L, L, L, W
9 crests/werder-bremen-1987-logo.png Werder Bremen 27 19 7, 6, 6 33:36 -3 D, D, W, L, W, L, W, D, L, W, L, D, W, W, W, L, D, L, D
10 crests/sc-freiburg-2008-logo.png SC Freiburg 27 19 8, 3, 8 26:36 -10 W, L, W, W, L, W, W, L, D, D, L, W, D, W, L, W, L, L, L
11 crests/borussia-dortmund-1993-logo.png Borussia Dortmund 26 19 7, 5, 7 34:33 1 W, D, W, L, W, L, W, L, W, L, W, D, D, D, W, L, L, L, D
12 crests/fc-augsburg-2002-logo.png FC Augsburg 25 19 7, 4, 8 23:34 -11 D, L, W, L, L, W, L, W, D, D, L, W, D, L, L, L, W, W, W
13 crests/st-pauli-2005-logo.png FC St. Pauli 20 19 6, 2, 11 17:21 -4 L, L, L, D, W, L, L, D, W, L, L, W, L, L, W, L, L, W, W
14 crests/union-berlin-1966-logo.png 1. FC Union Berlin 20 19 5, 5, 9 16:27 -11 D, W, D, W, L, W, W, D, L, D, L, L, L, D, L, L, L, W, L
15 crests/tsg-1899-hoffenheim-2014-logo.png 1899 Hoffenheim 18 19 4, 6, 9 25:37 -12 W, L, L, L, L, D, W, D, L, D, W, L, D, D, L, L, L, W, D
16 crests/1-fc-heidenheim-2007-logo.png 1. FC Heidenheim 1846 14 19 4, 2, 13 24:40 -16 W, W, L, L, W, L, L, D, L, L, L, L, L, L, L, W, D, L, L
17 crests/holstein-kiel.png Holstein Kiel 12 19 3, 3, 13 28:48 -20 L, L, L, D, L, D, L, L, W, L, L, L, L, L, W, L, W, L, D
18 crests/vfl-bochum-2000-logo.png VfL Bochum 10 19 2, 4, 13 17:43 -26 L, L, L, D, L, L, L, L, L, D, L, L, L, D, W, L, W, D, L

Two things might catch your eye. There seem to be two list columns (wdl and form). We will deal with them later. In general, these types of columns are usually used to create som visualization WITHIN the column.

The column img contains paths to crests of the clubs. These can be displayed with the gt_img_rows() function from gtExtras. We also align the imiges with cols_align().

tbl1 <- tbl1 |>
  gt_img_rows(img, img_source = "local", height = "25px") |>
  cols_align(
    align = "center",
    columns = img
  )
tbl1
number img mannschaft pk sp wdl tore dif form
1 Bayern München 48 19 15, 3, 1 58:16 42 W, W, W, W, D, D, W, W, W, W, W, D, W, L, W, W, W, W, W
2 Bayer Leverkusen 42 19 12, 6, 1 46:26 20 W, L, W, W, D, D, W, D, D, D, W, W, W, W, W, W, W, W, D
3 Eintracht Frankfurt 37 19 11, 4, 4 44:26 18 L, W, W, W, W, D, L, D, W, W, W, W, D, L, L, W, W, W, D
4 VfB Stuttgart 32 19 9, 5, 5 36:28 8 L, D, W, W, D, D, L, W, D, L, W, D, W, W, L, W, W, W, L
5 RB Leipzig 32 19 9, 5, 5 34:29 5 W, W, D, D, W, W, W, W, L, D, L, L, W, W, L, W, L, D, D
6 1. FSV Mainz 05 31 19 9, 4, 6 33:23 10 D, D, L, W, L, W, L, D, D, W, W, W, L, W, W, W, L, L, W
7 VfL Wolfsburg 28 19 8, 4, 7 42:34 8 L, W, L, L, D, W, L, D, D, W, W, W, W, L, L, W, W, L, D
8 Bor. Mönchengladbach 27 19 8, 3, 8 30:29 1 L, W, L, L, W, L, W, D, W, D, W, L, D, W, W, L, L, L, W
9 Werder Bremen 27 19 7, 6, 6 33:36 -3 D, D, W, L, W, L, W, D, L, W, L, D, W, W, W, L, D, L, D
10 SC Freiburg 27 19 8, 3, 8 26:36 -10 W, L, W, W, L, W, W, L, D, D, L, W, D, W, L, W, L, L, L
11 Borussia Dortmund 26 19 7, 5, 7 34:33 1 W, D, W, L, W, L, W, L, W, L, W, D, D, D, W, L, L, L, D
12 FC Augsburg 25 19 7, 4, 8 23:34 -11 D, L, W, L, L, W, L, W, D, D, L, W, D, L, L, L, W, W, W
13 FC St. Pauli 20 19 6, 2, 11 17:21 -4 L, L, L, D, W, L, L, D, W, L, L, W, L, L, W, L, L, W, W
14 1. FC Union Berlin 20 19 5, 5, 9 16:27 -11 D, W, D, W, L, W, W, D, L, D, L, L, L, D, L, L, L, W, L
15 1899 Hoffenheim 18 19 4, 6, 9 25:37 -12 W, L, L, L, L, D, W, D, L, D, W, L, D, D, L, L, L, W, D
16 1. FC Heidenheim 1846 14 19 4, 2, 13 24:40 -16 W, W, L, L, W, L, L, D, L, L, L, L, L, L, L, W, D, L, L
17 Holstein Kiel 12 19 3, 3, 13 28:48 -20 L, L, L, D, L, D, L, L, W, L, L, L, L, L, W, L, W, L, D
18 VfL Bochum 10 19 2, 4, 13 17:43 -26 L, L, L, D, L, L, L, L, L, D, L, L, L, D, W, L, W, D, L

Global styling

Next I do some very basic global styling. tab_options() is used to set styles for the whole table and tab_style() for specific cells. Here we just add a background color and some cell borders at various locations.

tbl2 <- tbl1 |>
  tab_style(
    style = cell_borders(color = "black", sides = c("top", "bottom"), weight = "3px"),
    locations = cells_body()
  ) |>
  tab_style(
    style = cell_borders(color = "black", sides = c("right"), weight = "3px"),
    locations = cells_body(columns = c(1, 4, 6, 8, 9))
  ) |>
  tab_options(
    table.width = pct(100),
    table.background.color = cols[1],
    table.border.top.color = "black",
    table.border.top.width = "3px",
    table.border.bottom.color = "black",
    table.border.bottom.width = "3px"
  )
tbl2
number img mannschaft pk sp wdl tore dif form
1 Bayern München 48 19 15, 3, 1 58:16 42 W, W, W, W, D, D, W, W, W, W, W, D, W, L, W, W, W, W, W
2 Bayer Leverkusen 42 19 12, 6, 1 46:26 20 W, L, W, W, D, D, W, D, D, D, W, W, W, W, W, W, W, W, D
3 Eintracht Frankfurt 37 19 11, 4, 4 44:26 18 L, W, W, W, W, D, L, D, W, W, W, W, D, L, L, W, W, W, D
4 VfB Stuttgart 32 19 9, 5, 5 36:28 8 L, D, W, W, D, D, L, W, D, L, W, D, W, W, L, W, W, W, L
5 RB Leipzig 32 19 9, 5, 5 34:29 5 W, W, D, D, W, W, W, W, L, D, L, L, W, W, L, W, L, D, D
6 1. FSV Mainz 05 31 19 9, 4, 6 33:23 10 D, D, L, W, L, W, L, D, D, W, W, W, L, W, W, W, L, L, W
7 VfL Wolfsburg 28 19 8, 4, 7 42:34 8 L, W, L, L, D, W, L, D, D, W, W, W, W, L, L, W, W, L, D
8 Bor. Mönchengladbach 27 19 8, 3, 8 30:29 1 L, W, L, L, W, L, W, D, W, D, W, L, D, W, W, L, L, L, W
9 Werder Bremen 27 19 7, 6, 6 33:36 -3 D, D, W, L, W, L, W, D, L, W, L, D, W, W, W, L, D, L, D
10 SC Freiburg 27 19 8, 3, 8 26:36 -10 W, L, W, W, L, W, W, L, D, D, L, W, D, W, L, W, L, L, L
11 Borussia Dortmund 26 19 7, 5, 7 34:33 1 W, D, W, L, W, L, W, L, W, L, W, D, D, D, W, L, L, L, D
12 FC Augsburg 25 19 7, 4, 8 23:34 -11 D, L, W, L, L, W, L, W, D, D, L, W, D, L, L, L, W, W, W
13 FC St. Pauli 20 19 6, 2, 11 17:21 -4 L, L, L, D, W, L, L, D, W, L, L, W, L, L, W, L, L, W, W
14 1. FC Union Berlin 20 19 5, 5, 9 16:27 -11 D, W, D, W, L, W, W, D, L, D, L, L, L, D, L, L, L, W, L
15 1899 Hoffenheim 18 19 4, 6, 9 25:37 -12 W, L, L, L, L, D, W, D, L, D, W, L, D, D, L, L, L, W, D
16 1. FC Heidenheim 1846 14 19 4, 2, 13 24:40 -16 W, W, L, L, W, L, L, D, L, L, L, L, L, L, L, W, D, L, L
17 Holstein Kiel 12 19 3, 3, 13 28:48 -20 L, L, L, D, L, D, L, L, W, L, L, L, L, L, W, L, W, L, D
18 VfL Bochum 10 19 2, 4, 13 17:43 -26 L, L, L, D, L, L, L, L, L, D, L, L, L, D, W, L, W, D, L

Individual styling

Next we go into styling specific columns rows. This is all done with the tab_style() function, setting fill and text options with cell_fill() and cell_text() and controlling where to apply the style with cells_body().

tbl3 <- tbl2 |>
  tab_style(
    style = cell_fill("black"),
    locations = cells_body(columns = 1)
  ) |>
  tab_style(
    style = cell_text(color = "white", weight = 700),
    locations = cells_body(columns = 1)
  ) |>
  cols_align(
    align = "center",
    columns = number
  ) |>
  tab_style(
    style = cell_fill(color = cols[6]),
    locations = cells_body(rows = 1:4, columns = 2:9)
  ) |>
  tab_style(
    style = cell_fill(color = cols[5]),
    locations = cells_body(rows = 5, columns = 2:9)
  ) |>
  tab_style(
    style = cell_fill(color = cols[4]),
    locations = cells_body(rows = 6, columns = 2:9)
  ) |>
  tab_style(
    style = cell_fill(color = cols[3]),
    locations = cells_body(rows = 17:18, columns = 2:9)
  ) |>
  tab_style(
    style = cell_fill(color = cols[2]),
    locations = cells_body(rows = 16, columns = 2:9)
  ) |>
  tab_style(
    style = cell_text(weight = 700),
    locations = cells_body(columns = 4)
  )
tbl3
number img mannschaft pk sp wdl tore dif form
1 Bayern München 48 19 15, 3, 1 58:16 42 W, W, W, W, D, D, W, W, W, W, W, D, W, L, W, W, W, W, W
2 Bayer Leverkusen 42 19 12, 6, 1 46:26 20 W, L, W, W, D, D, W, D, D, D, W, W, W, W, W, W, W, W, D
3 Eintracht Frankfurt 37 19 11, 4, 4 44:26 18 L, W, W, W, W, D, L, D, W, W, W, W, D, L, L, W, W, W, D
4 VfB Stuttgart 32 19 9, 5, 5 36:28 8 L, D, W, W, D, D, L, W, D, L, W, D, W, W, L, W, W, W, L
5 RB Leipzig 32 19 9, 5, 5 34:29 5 W, W, D, D, W, W, W, W, L, D, L, L, W, W, L, W, L, D, D
6 1. FSV Mainz 05 31 19 9, 4, 6 33:23 10 D, D, L, W, L, W, L, D, D, W, W, W, L, W, W, W, L, L, W
7 VfL Wolfsburg 28 19 8, 4, 7 42:34 8 L, W, L, L, D, W, L, D, D, W, W, W, W, L, L, W, W, L, D
8 Bor. Mönchengladbach 27 19 8, 3, 8 30:29 1 L, W, L, L, W, L, W, D, W, D, W, L, D, W, W, L, L, L, W
9 Werder Bremen 27 19 7, 6, 6 33:36 -3 D, D, W, L, W, L, W, D, L, W, L, D, W, W, W, L, D, L, D
10 SC Freiburg 27 19 8, 3, 8 26:36 -10 W, L, W, W, L, W, W, L, D, D, L, W, D, W, L, W, L, L, L
11 Borussia Dortmund 26 19 7, 5, 7 34:33 1 W, D, W, L, W, L, W, L, W, L, W, D, D, D, W, L, L, L, D
12 FC Augsburg 25 19 7, 4, 8 23:34 -11 D, L, W, L, L, W, L, W, D, D, L, W, D, L, L, L, W, W, W
13 FC St. Pauli 20 19 6, 2, 11 17:21 -4 L, L, L, D, W, L, L, D, W, L, L, W, L, L, W, L, L, W, W
14 1. FC Union Berlin 20 19 5, 5, 9 16:27 -11 D, W, D, W, L, W, W, D, L, D, L, L, L, D, L, L, L, W, L
15 1899 Hoffenheim 18 19 4, 6, 9 25:37 -12 W, L, L, L, L, D, W, D, L, D, W, L, D, D, L, L, L, W, D
16 1. FC Heidenheim 1846 14 19 4, 2, 13 24:40 -16 W, W, L, L, W, L, L, D, L, L, L, L, L, L, L, W, D, L, L
17 Holstein Kiel 12 19 3, 3, 13 28:48 -20 L, L, L, D, L, D, L, L, W, L, L, L, L, L, W, L, W, L, D
18 VfL Bochum 10 19 2, 4, 13 17:43 -26 L, L, L, D, L, L, L, L, L, D, L, L, L, D, W, L, W, D, L

Adding a sparkline

Ok now it is time to draw the attention to the list columns. The first one contains the record of wins, draws, and losses over the whole season. Here I had to rewrite the function gt_plt_bar_stack() slightly because it did not expose all parameters I wanted to set. Additionally, there is a known bug that prevents to render the column header correctly. That is why I needed to add it manually.

tbl4 <- tbl3 |>
  gt_plt_bar_stack1(wdl,
    width = 45,
    labels = c("Wins", "Draws", "Losses"),
    palette = wld[c(1, 3, 2)],
    color = NA # cols[3]
  ) |>
  cols_label(wdl = html(glue::glue("<div><span style='color:{wld[1]}'><b>W</b></span>||<span style='color:{wld[3]}'><b>D</b></span>||<span style='color:{wld[2]}'><b>L</b></span></div>")))
tbl4
number img mannschaft pk sp
W||D||L
tore dif form
1 Bayern München 48 19 1531 58:16 42 W, W, W, W, D, D, W, W, W, W, W, D, W, L, W, W, W, W, W
2 Bayer Leverkusen 42 19 1261 46:26 20 W, L, W, W, D, D, W, D, D, D, W, W, W, W, W, W, W, W, D
3 Eintracht Frankfurt 37 19 1144 44:26 18 L, W, W, W, W, D, L, D, W, W, W, W, D, L, L, W, W, W, D
4 VfB Stuttgart 32 19 955 36:28 8 L, D, W, W, D, D, L, W, D, L, W, D, W, W, L, W, W, W, L
5 RB Leipzig 32 19 955 34:29 5 W, W, D, D, W, W, W, W, L, D, L, L, W, W, L, W, L, D, D
6 1. FSV Mainz 05 31 19 946 33:23 10 D, D, L, W, L, W, L, D, D, W, W, W, L, W, W, W, L, L, W
7 VfL Wolfsburg 28 19 847 42:34 8 L, W, L, L, D, W, L, D, D, W, W, W, W, L, L, W, W, L, D
8 Bor. Mönchengladbach 27 19 838 30:29 1 L, W, L, L, W, L, W, D, W, D, W, L, D, W, W, L, L, L, W
9 Werder Bremen 27 19 766 33:36 -3 D, D, W, L, W, L, W, D, L, W, L, D, W, W, W, L, D, L, D
10 SC Freiburg 27 19 838 26:36 -10 W, L, W, W, L, W, W, L, D, D, L, W, D, W, L, W, L, L, L
11 Borussia Dortmund 26 19 757 34:33 1 W, D, W, L, W, L, W, L, W, L, W, D, D, D, W, L, L, L, D
12 FC Augsburg 25 19 748 23:34 -11 D, L, W, L, L, W, L, W, D, D, L, W, D, L, L, L, W, W, W
13 FC St. Pauli 20 19 6211 17:21 -4 L, L, L, D, W, L, L, D, W, L, L, W, L, L, W, L, L, W, W
14 1. FC Union Berlin 20 19 559 16:27 -11 D, W, D, W, L, W, W, D, L, D, L, L, L, D, L, L, L, W, L
15 1899 Hoffenheim 18 19 469 25:37 -12 W, L, L, L, L, D, W, D, L, D, W, L, D, D, L, L, L, W, D
16 1. FC Heidenheim 1846 14 19 4213 24:40 -16 W, W, L, L, W, L, L, D, L, L, L, L, L, L, L, W, D, L, L
17 Holstein Kiel 12 19 3313 28:48 -20 L, L, L, D, L, D, L, L, W, L, L, L, L, L, W, L, W, L, D
18 VfL Bochum 10 19 2413 17:43 -26 L, L, L, D, L, L, L, L, L, D, L, L, L, D, W, L, W, D, L

Having done that, I realized how html and css heavy gt’s actually are. That’s kind of nice, because I know that (thank you quartocities).

Adding a custom column viz

Having understood how to inject html into a gt object, I decided to create a design for the form column myself. There is a lot of code down there, but the essential part is the text_transform() part. This function allows to apply a function to the content of a cell. And thats how we inject some html and css into the target column.

html_squares <- function(x, palette, max_val = 5) {
  row <- '<div style="display: flex;font-family: Bungee, sans-serif; font-size: 12px;gap:3px;">'
  for (i in (length(x) - max_val + 1):length(x)) {
    col <- ifelse(x[i] == "W", palette[1], ifelse(x[i] == "L", palette[2], palette[3]))
    row <- glue::glue('{row} <div style="display: flex; justify-content: center;align-items: center;background-color: {col}; color: #ffffff; border-radius: 8px; width: 1.5rem; height:2rem">{x[i]}</div>')
  }
  row <- glue::glue("{row} </div>")
  return(gt::html(row))
}

form_squares <- function(gt_object, column, palette, max_val = 5) {
  list_vals <- gt_index(gt_object = gt_object, {{ column }}, as_vector = TRUE)
  text_transform(gt_object, locations = cells_body(columns = {{ column }}), fn = function(x) {
    lapply(list_vals, html_squares, palette = palette, max_val = max_val)
  })
}

tbl5 <- tbl4 |>
  form_squares(form, wld, max_val = 5)

tbl5
number img mannschaft pk sp
W||D||L
tore dif form
1 Bayern München 48 19 1531 58:16 42
W
W
W
W
W
2 Bayer Leverkusen 42 19 1261 46:26 20
W
W
W
W
D
3 Eintracht Frankfurt 37 19 1144 44:26 18
L
W
W
W
D
4 VfB Stuttgart 32 19 955 36:28 8
L
W
W
W
L
5 RB Leipzig 32 19 955 34:29 5
L
W
L
D
D
6 1. FSV Mainz 05 31 19 946 33:23 10
W
W
L
L
W
7 VfL Wolfsburg 28 19 847 42:34 8
L
W
W
L
D
8 Bor. Mönchengladbach 27 19 838 30:29 1
W
L
L
L
W
9 Werder Bremen 27 19 766 33:36 -3
W
L
D
L
D
10 SC Freiburg 27 19 838 26:36 -10
L
W
L
L
L
11 Borussia Dortmund 26 19 757 34:33 1
W
L
L
L
D
12 FC Augsburg 25 19 748 23:34 -11
L
L
W
W
W
13 FC St. Pauli 20 19 6211 17:21 -4
W
L
L
W
W
14 1. FC Union Berlin 20 19 559 16:27 -11
L
L
L
W
L
15 1899 Hoffenheim 18 19 469 25:37 -12
L
L
L
W
D
16 1. FC Heidenheim 1846 14 19 4213 24:40 -16
L
W
D
L
L
17 Holstein Kiel 12 19 3313 28:48 -20
W
L
W
L
D
18 VfL Bochum 10 19 2413 17:43 -26
W
L
W
D
L

The rest of the f*** table

That was almost it. What was left was enhance the retro look and add a legend. After the previous step, I was very comfortable adding css to the table so I went nuts. The one new thing here is the function opt_css(). This allows for example to add css classes to the table. I used to get the header of the table in retro shape.

tbl5 |>
  opt_table_font(
    font = list(
      google_font(name = "Bungee"), "serif"
    ),
    size = "14px"
  ) |>
  tab_header(
    title = html('<span class="vintage-text smaller">Bundesliga 2024/25</span>')
  ) |>
  cols_label(number = "", img = "", mannschaft = "", sp = "G", pk = "Pt", tore = "Goals", dif = "Diff", form = "Form") |>
  tab_source_note(
    html(glue::glue(
      '<span style="display: inline;font-family: Bungee, sans-serif; font-size: 12px"><span style="background-color: {cols[6]};"> Champions League</span> </span>
         <span style="display: inline;font-family: Bungee, sans-serif; font-size: 12px"><span style="background-color: {cols[5]};"> Europa League</span> </span>
         <span style="display: inline;font-family: Bungee, sans-serif; font-size: 12px"><span style="background-color: {cols[4]};"> Europa Conf. League</span> </span>
         <span style="display: inline;font-family: Bungee, sans-serif; font-size: 12px"><span style="background-color: {cols[2]};"> Playoffs</span> </span>
         <span style="display: inline;font-family: Bungee, sans-serif; font-size: 12px"><span style="background-color: {cols[3]};"> Relegation</span> </span>'
    ))
  ) |>
  opt_css(
    css = "
    .vintage-text {
        margin: 0;
        font-family: Bungee;
        line-height: 85px;
    }
    .smaller {
        font-size: 60px;
        color: #2e6364;
        letter-spacing: 4px;
        text-shadow: 0.5px 0.5px #f1f4d8, 1px 1px #f1f4d8, 2px 2px #f1f4d8, 2.5px 2.5px #822d2f, 3px 3px #822d2f, 3.5px 3.5px #822d2f, 4px 4px #822d2f, 4.5px 4.5px #822d2f, 5px 5px #822d2f, 5.5px 5.5px #822d2f, 6px 6px #822d2f, 6.5px 6.5px #822d2f, 7px 7px #822d2f, 7.5px 7.5px #822d2f;
    }"
  )
Bundesliga 2024/25
Pt G
W||D||L
Goals Diff Form
1 Bayern München 48 19 1531 58:16 42
W
W
W
W
W
2 Bayer Leverkusen 42 19 1261 46:26 20
W
W
W
W
D
3 Eintracht Frankfurt 37 19 1144 44:26 18
L
W
W
W
D
4 VfB Stuttgart 32 19 955 36:28 8
L
W
W
W
L
5 RB Leipzig 32 19 955 34:29 5
L
W
L
D
D
6 1. FSV Mainz 05 31 19 946 33:23 10
W
W
L
L
W
7 VfL Wolfsburg 28 19 847 42:34 8
L
W
W
L
D
8 Bor. Mönchengladbach 27 19 838 30:29 1
W
L
L
L
W
9 Werder Bremen 27 19 766 33:36 -3
W
L
D
L
D
10 SC Freiburg 27 19 838 26:36 -10
L
W
L
L
L
11 Borussia Dortmund 26 19 757 34:33 1
W
L
L
L
D
12 FC Augsburg 25 19 748 23:34 -11
L
L
W
W
W
13 FC St. Pauli 20 19 6211 17:21 -4
W
L
L
W
W
14 1. FC Union Berlin 20 19 559 16:27 -11
L
L
L
W
L
15 1899 Hoffenheim 18 19 469 25:37 -12
L
L
L
W
D
16 1. FC Heidenheim 1846 14 19 4213 24:40 -16
L
W
D
L
L
17 Holstein Kiel 12 19 3313 28:48 -20
W
L
W
L
D
18 VfL Bochum 10 19 2413 17:43 -26
W
L
W
D
L
Champions League Europa League Europa Conf. League Playoffs Relegation

The end result is not perfect but gave me enough of an understanding how gt works. Now I just have to design tables for a year or so and I am sure I will remember all the things.

Reuse

Citation

BibTeX citation:
@online{schoch2024,
  author = {Schoch, David},
  title = {Learning Gt: {Creating} a {Retro} {League} {Table}},
  date = {2024-11-18},
  url = {http://blog.schochastics.net/posts/2024-11-18_retro-league-table-with-gt/},
  langid = {en}
}
For attribution, please cite this work as:
Schoch, David. 2024. “Learning Gt: Creating a Retro League Table.” November 18, 2024. http://blog.schochastics.net/posts/2024-11-18_retro-league-table-with-gt/.