library(gt)
library(gtscales)
library(scales)
big_number_labels <- label_number(scale_cut = cut_short_scale())
date_labels <- label_date('%b %d')gtscales adds matched legends to color-encoded
gt tables. The package is built around two common
needs:
The package supports continuous, diverging, binned, quantile, and
discrete scales. It is designed to work naturally with
scales helpers such as label functions, break functions,
transform specifications, and palette functions.
For most work, the gtscale_data_color_*() helpers are
the fastest path. They call gt::data_color() and then
attach a matching legend.
exibble |>
gt() |>
gtscale_data_color_continuous(
column = num,
palette = c('#A0442C', 'white', '#0063B1'),
labels = big_number_labels,
width = '220px',
title = 'Numeric scale'
)| num | char | fctr | date | time | datetime | currency | row | group |
|---|---|---|---|---|---|---|---|---|
| 1.111e-01 | apricot | one | 2015-01-15 | 13:35 | 2018-01-01 02:22 | 49.950 | row_1 | grp_a |
| 2.222e+00 | banana | two | 2015-02-15 | 14:40 | 2018-02-02 14:33 | 17.950 | row_2 | grp_a |
| 3.333e+01 | coconut | three | 2015-03-15 | 15:45 | 2018-03-03 03:44 | 1.390 | row_3 | grp_a |
| 4.444e+02 | durian | four | 2015-04-15 | 16:50 | 2018-04-04 15:55 | 65100.000 | row_4 | grp_a |
| 5.550e+03 | NA | five | 2015-05-15 | 17:55 | 2018-05-05 04:00 | 1325.810 | row_5 | grp_b |
| NA | fig | six | 2015-06-15 | NA | 2018-06-06 16:11 | 13.255 | row_6 | grp_b |
| 7.770e+05 | grapefruit | seven | NA | 19:10 | 2018-07-07 05:22 | NA | row_7 | grp_b |
| 8.880e+06 | honeydew | eight | 2015-08-15 | 20:20 | NA | 0.440 | row_8 | grp_b |
Numeric scale 2M4M6M8M |
||||||||
Binned scales are useful when you want fixed intervals rather than a smooth gradient.
exibble |>
gt() |>
gtscale_data_color_bins(
column = currency,
palette = c('#f7fbff', '#08306b'),
bins = c(0, 10, 100, 1000, 10000, 70000),
title = 'Currency bins'
)| num | char | fctr | date | time | datetime | currency | row | group |
|---|---|---|---|---|---|---|---|---|
| 1.111e-01 | apricot | one | 2015-01-15 | 13:35 | 2018-01-01 02:22 | 49.950 | row_1 | grp_a |
| 2.222e+00 | banana | two | 2015-02-15 | 14:40 | 2018-02-02 14:33 | 17.950 | row_2 | grp_a |
| 3.333e+01 | coconut | three | 2015-03-15 | 15:45 | 2018-03-03 03:44 | 1.390 | row_3 | grp_a |
| 4.444e+02 | durian | four | 2015-04-15 | 16:50 | 2018-04-04 15:55 | 65100.000 | row_4 | grp_a |
| 5.550e+03 | NA | five | 2015-05-15 | 17:55 | 2018-05-05 04:00 | 1325.810 | row_5 | grp_b |
| NA | fig | six | 2015-06-15 | NA | 2018-06-06 16:11 | 13.255 | row_6 | grp_b |
| 7.770e+05 | grapefruit | seven | NA | 19:10 | 2018-07-07 05:22 | NA | row_7 | grp_b |
| 8.880e+06 | honeydew | eight | 2015-08-15 | 20:20 | NA | 0.440 | row_8 | grp_b |
Currency bins 0.4 - 1010.0 - 100100.0 - 1,0001,000.0 - 10,00010,000.0 - 65,100 |
||||||||
Quantile scales instead split the data into equally sized groups.
exibble |>
gt() |>
gtscale_data_color_quantiles(
column = num,
palette = c('#fdd49e', '#fdbb84', '#ef6548', '#990000'),
quantiles = 4,
labels = big_number_labels,
width = '220px',
title = 'Quartiles'
)| num | char | fctr | date | time | datetime | currency | row | group |
|---|---|---|---|---|---|---|---|---|
| 1.111e-01 | apricot | one | 2015-01-15 | 13:35 | 2018-01-01 02:22 | 49.950 | row_1 | grp_a |
| 2.222e+00 | banana | two | 2015-02-15 | 14:40 | 2018-02-02 14:33 | 17.950 | row_2 | grp_a |
| 3.333e+01 | coconut | three | 2015-03-15 | 15:45 | 2018-03-03 03:44 | 1.390 | row_3 | grp_a |
| 4.444e+02 | durian | four | 2015-04-15 | 16:50 | 2018-04-04 15:55 | 65100.000 | row_4 | grp_a |
| 5.550e+03 | NA | five | 2015-05-15 | 17:55 | 2018-05-05 04:00 | 1325.810 | row_5 | grp_b |
| NA | fig | six | 2015-06-15 | NA | 2018-06-06 16:11 | 13.255 | row_6 | grp_b |
| 7.770e+05 | grapefruit | seven | NA | 19:10 | 2018-07-07 05:22 | NA | row_7 | grp_b |
| 8.880e+06 | honeydew | eight | 2015-08-15 | 20:20 | NA | 0.440 | row_8 | grp_b |
Quartiles 0 - 1818 - 444444 - 391K391K - 9M |
||||||||
Discrete legends are useful when colors encode categories rather than ordered values.
data.frame(
district = c('A', 'B', 'C', 'D'),
status = c('Safe D', 'Toss-up', 'Lean R', 'Safe R'),
margin = c(18, 2, -6, -21)
) |>
gt() |>
gtscale_data_color_discrete(
column = status,
values = c('#2166ac', '#f7f7f7', '#f4a3b4', '#b2182b'),
labels = c('Safe D', 'Toss-up', 'Lean R', 'Safe R'),
title = 'Race rating'
)| district | status | margin |
|---|---|---|
| A | Safe D | 18 |
| B | Toss-up | 2 |
| C | Lean R | -6 |
| D | Safe R | -21 |
Race rating Safe DToss-upLean RSafe R |
||
Sometimes the table is already colored, or the color mapping is
handled elsewhere. In that case, the gtscale_color_*()
helpers attach only the legend.
exibble |>
gt() |>
gt::data_color(
columns = num,
method = 'numeric',
palette = c('#A0442C', 'white', '#0063B1')
) |>
gtscale_color_continuous(
column = num,
palette = c('#A0442C', 'white', '#0063B1'),
title = 'Numeric scale'
)| num | char | fctr | date | time | datetime | currency | row | group |
|---|---|---|---|---|---|---|---|---|
| 1.111e-01 | apricot | one | 2015-01-15 | 13:35 | 2018-01-01 02:22 | 49.950 | row_1 | grp_a |
| 2.222e+00 | banana | two | 2015-02-15 | 14:40 | 2018-02-02 14:33 | 17.950 | row_2 | grp_a |
| 3.333e+01 | coconut | three | 2015-03-15 | 15:45 | 2018-03-03 03:44 | 1.390 | row_3 | grp_a |
| 4.444e+02 | durian | four | 2015-04-15 | 16:50 | 2018-04-04 15:55 | 65100.000 | row_4 | grp_a |
| 5.550e+03 | NA | five | 2015-05-15 | 17:55 | 2018-05-05 04:00 | 1325.810 | row_5 | grp_b |
| NA | fig | six | 2015-06-15 | NA | 2018-06-06 16:11 | 13.255 | row_6 | grp_b |
| 7.770e+05 | grapefruit | seven | NA | 19:10 | 2018-07-07 05:22 | NA | row_7 | grp_b |
| 8.880e+06 | honeydew | eight | 2015-08-15 | 20:20 | NA | 0.440 | row_8 | grp_b |
Numeric scale 2,000,0004,000,0006,000,0008,000,000 |
||||||||
For more control, use a gtscale_spec. Specs separate
scale definition from application and legend placement.
spec <- gtscale_spec_continuous(
num,
palette = c('#A0442C', 'white', '#0063B1'),
labels = big_number_labels,
width = '220px',
title = 'Numeric scale'
) |>
gtscale_spec_set_application(apply_to = 'fill') |>
gtscale_spec_set_legend(placement = 'subtitle')
exibble |>
gt() |>
gtscale_apply_legend(spec)Numeric scale 2M4M6M8M |
||||||||
| num | char | fctr | date | time | datetime | currency | row | group |
|---|---|---|---|---|---|---|---|---|
| 1.111e-01 | apricot | one | 2015-01-15 | 13:35 | 2018-01-01 02:22 | 49.950 | row_1 | grp_a |
| 2.222e+00 | banana | two | 2015-02-15 | 14:40 | 2018-02-02 14:33 | 17.950 | row_2 | grp_a |
| 3.333e+01 | coconut | three | 2015-03-15 | 15:45 | 2018-03-03 03:44 | 1.390 | row_3 | grp_a |
| 4.444e+02 | durian | four | 2015-04-15 | 16:50 | 2018-04-04 15:55 | 65100.000 | row_4 | grp_a |
| 5.550e+03 | NA | five | 2015-05-15 | 17:55 | 2018-05-05 04:00 | 1325.810 | row_5 | grp_b |
| NA | fig | six | 2015-06-15 | NA | 2018-06-06 16:11 | 13.255 | row_6 | grp_b |
| 7.770e+05 | grapefruit | seven | NA | 19:10 | 2018-07-07 05:22 | NA | row_7 | grp_b |
| 8.880e+06 | honeydew | eight | 2015-08-15 | 20:20 | NA | 0.440 | row_8 | grp_b |
This becomes more useful when the same scale needs to be reused or when you want to separate coloring from legend placement.
gtscales is designed to accept the same kinds of helpers
you would already use in plots.
You can pass label functions and break functions directly.
data.frame(share = c(0.12, 0.33, 0.57, 0.91)) |>
gt() |>
gtscale_data_color_bins(
column = share,
palette = c('#f7fbff', '#08306b'),
bins = c(0, 0.25, 0.5, 0.75, 1),
labels = label_percent(),
title = 'Share bins'
)| share |
|---|
| 0.12 |
| 0.33 |
| 0.57 |
| 0.91 |
Share bins 12% - 25%25% - 50%50% - 75%75% - 91% |
Palette functions from scales can be supplied
directly.
data.frame(value = c(1, 10, 100, 1000)) |>
gt() |>
gtscale_data_color_continuous(
column = value,
palette = pal_viridis(),
transform = 'log10',
breaks = breaks_log(),
labels = label_number(),
title = 'Log scale'
)| value |
|---|
| 1 |
| 10 |
| 100 |
| 1000 |
Log scale 1101001 000 |
Date-like columns work through the continuous and binned workflows.
In many cases, gtscales can infer the appropriate transform
from the column class.
data.frame(
when = as.Date(c('2024-01-01', '2024-01-20', '2024-02-10', '2024-03-05')),
value = c(10, 18, 35, 52)
) |>
gt() |>
gtscale_data_color_bins(
column = when,
palette = pal_viridis(),
bins = breaks_width('1 month'),
labels = date_labels,
width = '220px',
title = 'Monthly bins'
)| when | value |
|---|---|
| 2024-01-01 | 10 |
| 2024-01-20 | 18 |
| 2024-02-10 | 35 |
| 2024-03-05 | 52 |
Monthly bins Jan 01 - Feb 01Feb 01 - Mar 01Mar 01 - Mar 05 |
|
Legends can be attached as source notes, subtitles, or titles.
Source notes are the default and are the most portable across outputs.
exibble |>
gt() |>
gtscale_legend(
gtscale_spec_continuous(
num,
palette = c('#A0442C', 'white', '#0063B1'),
labels = big_number_labels,
width = '220px',
title = 'Numeric scale'
) |>
gtscale_spec_set_legend(placement = 'source_note')
)| num | char | fctr | date | time | datetime | currency | row | group |
|---|---|---|---|---|---|---|---|---|
| 1.111e-01 | apricot | one | 2015-01-15 | 13:35 | 2018-01-01 02:22 | 49.950 | row_1 | grp_a |
| 2.222e+00 | banana | two | 2015-02-15 | 14:40 | 2018-02-02 14:33 | 17.950 | row_2 | grp_a |
| 3.333e+01 | coconut | three | 2015-03-15 | 15:45 | 2018-03-03 03:44 | 1.390 | row_3 | grp_a |
| 4.444e+02 | durian | four | 2015-04-15 | 16:50 | 2018-04-04 15:55 | 65100.000 | row_4 | grp_a |
| 5.550e+03 | NA | five | 2015-05-15 | 17:55 | 2018-05-05 04:00 | 1325.810 | row_5 | grp_b |
| NA | fig | six | 2015-06-15 | NA | 2018-06-06 16:11 | 13.255 | row_6 | grp_b |
| 7.770e+05 | grapefruit | seven | NA | 19:10 | 2018-07-07 05:22 | NA | row_7 | grp_b |
| 8.880e+06 | honeydew | eight | 2015-08-15 | 20:20 | NA | 0.440 | row_8 | grp_b |
Numeric scale 2M4M6M8M |
||||||||
When you want the legend closer to the heading, use
subtitle or title.
exibble |>
gt() |>
gtscale_legend(
gtscale_spec_quantiles(
num,
palette = c('#fdd49e', '#fdbb84', '#ef6548', '#990000'),
quantiles = 4,
labels = big_number_labels,
width = '220px',
title = 'Quartiles'
) |>
gtscale_spec_set_legend(placement = 'subtitle')
)Quartiles 0 - 1818 - 444444 - 391K391K - 9M |
||||||||
| num | char | fctr | date | time | datetime | currency | row | group |
|---|---|---|---|---|---|---|---|---|
| 1.111e-01 | apricot | one | 2015-01-15 | 13:35 | 2018-01-01 02:22 | 49.950 | row_1 | grp_a |
| 2.222e+00 | banana | two | 2015-02-15 | 14:40 | 2018-02-02 14:33 | 17.950 | row_2 | grp_a |
| 3.333e+01 | coconut | three | 2015-03-15 | 15:45 | 2018-03-03 03:44 | 1.390 | row_3 | grp_a |
| 4.444e+02 | durian | four | 2015-04-15 | 16:50 | 2018-04-04 15:55 | 65100.000 | row_4 | grp_a |
| 5.550e+03 | NA | five | 2015-05-15 | 17:55 | 2018-05-05 04:00 | 1325.810 | row_5 | grp_b |
| NA | fig | six | 2015-06-15 | NA | 2018-06-06 16:11 | 13.255 | row_6 | grp_b |
| 7.770e+05 | grapefruit | seven | NA | 19:10 | 2018-07-07 05:22 | NA | row_7 | grp_b |
| 8.880e+06 | honeydew | eight | 2015-08-15 | 20:20 | NA | 0.440 | row_8 | grp_b |
exibble |>
gt() |>
gtscale_legend(
gtscale_spec_continuous(
num,
palette = c('#A0442C', 'white', '#0063B1'),
labels = big_number_labels,
width = '220px',
title = 'Numeric scale'
) |>
gtscale_spec_set_legend(placement = 'title')
)Numeric scale 2M4M6M8M |
||||||||
| num | char | fctr | date | time | datetime | currency | row | group |
|---|---|---|---|---|---|---|---|---|
| 1.111e-01 | apricot | one | 2015-01-15 | 13:35 | 2018-01-01 02:22 | 49.950 | row_1 | grp_a |
| 2.222e+00 | banana | two | 2015-02-15 | 14:40 | 2018-02-02 14:33 | 17.950 | row_2 | grp_a |
| 3.333e+01 | coconut | three | 2015-03-15 | 15:45 | 2018-03-03 03:44 | 1.390 | row_3 | grp_a |
| 4.444e+02 | durian | four | 2015-04-15 | 16:50 | 2018-04-04 15:55 | 65100.000 | row_4 | grp_a |
| 5.550e+03 | NA | five | 2015-05-15 | 17:55 | 2018-05-05 04:00 | 1325.810 | row_5 | grp_b |
| NA | fig | six | 2015-06-15 | NA | 2018-06-06 16:11 | 13.255 | row_6 | grp_b |
| 7.770e+05 | grapefruit | seven | NA | 19:10 | 2018-07-07 05:22 | NA | row_7 | grp_b |
| 8.880e+06 | honeydew | eight | 2015-08-15 | 20:20 | NA | 0.440 | row_8 | grp_b |
If you attach multiple legends to the same heading area, use
layout = "inline" to place them side by side.
data.frame(a = 1:3, b = 4:6) |>
gt() |>
gtscale_legend(
gtscale_spec_continuous(
a,
palette = c('#f7fbff', '#08306b'),
title = 'A'
) |>
gtscale_spec_set_legend(placement = 'subtitle', layout = 'inline')
) |>
gtscale_legend(
gtscale_spec_continuous(
b,
palette = c('#fff5eb', '#7f2704'),
title = 'B'
) |>
gtscale_spec_set_legend(placement = 'subtitle', layout = 'inline')
)A 1.01.52.02.53.0 B 4.04.55.05.56.0 |
|
| a | b |
|---|---|
| 1 | 4 |
| 2 | 5 |
| 3 | 6 |
Validated example workflows live in inst/examples
for:
The gt integration is strongest when legends are
attached as source notes, since that path now works across HTML, LaTeX,
DOCX, and RTF. Heading placement is still useful, but it is best
reserved for workflows where you control the output path more
tightly.