Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions great-docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ reference:
- loc.body
- loc.footer
- loc.source_notes
- loc.footnotes
- style.fill
- style.text
- style.borders
Expand Down Expand Up @@ -246,6 +247,7 @@ reference:
contents:
- GT.opt_stylize
- GT.opt_footnote_marks
- GT.opt_footnote_spec
- GT.opt_row_striping
- GT.opt_align_table_header
- GT.opt_vertical_padding
Expand Down
3 changes: 3 additions & 0 deletions great_tables/_gt_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -1443,6 +1443,9 @@ class Options:
# footnotes_border_lr_width: OptionsInfo = OptionsInfo(True, "footnotes", "px", "2px")
# footnotes_border_lr_color: OptionsInfo = OptionsInfo(True, "footnotes", "value", "#D3D3D3")
footnotes_marks: OptionsInfo = OptionsInfo(False, "footnotes", "values", "numbers")
footnotes_spec_ref: OptionsInfo = OptionsInfo(False, "footnotes", "value", "^i")
footnotes_spec_ftr: OptionsInfo = OptionsInfo(False, "footnotes", "value", "^i")
footnotes_order: OptionsInfo = OptionsInfo(False, "footnotes", "value", "marks_last")
# footnotes_multiline: OptionsInfo = OptionsInfo(False, "footnotes", "boolean", True)
# footnotes_sep: OptionsInfo = OptionsInfo(False, "footnotes", "value", " ")
source_notes_padding: OptionsInfo = OptionsInfo(True, "source_notes", "px", "4px")
Expand Down
2 changes: 2 additions & 0 deletions great_tables/_locations.py
Original file line number Diff line number Diff line change
Expand Up @@ -1342,6 +1342,7 @@ def set_style(loc: Loc, data: GTData, style: list[CellStyle | FootnoteEntry]) ->
@set_style.register(LocStubheadLabel)
@set_style.register(LocColumnHeader)
@set_style.register(LocFooter)
@set_style.register(LocFootnotes)
@set_style.register(LocSourceNotes)
def _(
loc: (
Expand All @@ -1352,6 +1353,7 @@ def _(
| LocStubheadLabel
| LocColumnHeader
| LocFooter
| LocFootnotes
| LocSourceNotes
),
data: GTData,
Expand Down
182 changes: 182 additions & 0 deletions great_tables/_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,9 @@ def tab_options(
# footnotes_border_lr_width: str | None = None,
# footnotes_border_lr_color: str | None = None,
footnotes_marks: str | list[str] | None = None,
footnotes_spec_ref: str | None = None,
footnotes_spec_ftr: str | None = None,
footnotes_order: str | None = None,
# footnotes_multiline: bool | None = None,
# footnotes_sep: str | None = None,
source_notes_background_color: str | None = None,
Expand Down Expand Up @@ -653,6 +656,185 @@ def opt_footnote_marks(self: GTSelf, marks: str | list[str] = "numbers") -> GTSe
return tab_options(self, footnotes_marks=marks)


def opt_footnote_spec(
self: GTSelf,
spec_ref: str | None = None,
spec_ftr: str | None = None,
) -> GTSelf:
"""
Option to modify the formatting of footnote marks.

Control how footnote marks are styled in two independent contexts: inline references next to
cell content (`spec_ref`) and marks in the footer listing (`spec_ftr`). Each takes a compact
DSL string composed of formatting codes.

The spec DSL codes are:

- `"^"`: superscript
- `"b"`: bold
- `"i"`: italic
- `"()"` or `"(x)"`: parentheses around the mark
- `"[]"` or `"[x]"`: square brackets around the mark
- `"."`: trailing period
- `"x"`: optional placeholder for readability (ignored in parsing)

Parameters
----------
spec_ref
A spec string controlling the formatting of inline reference marks (marks that appear
next to cell content). If `None`, the current setting is unchanged. The default value in
the options system is `"^i"` (superscript + italic).
spec_ftr
A spec string controlling the formatting of footer marks (marks that appear in the
footnote listing at the bottom of the table). If `None`, the current setting is unchanged.
The default value in the options system is `"^i"` (superscript + italic).

Returns
-------
GT
The GT object is returned. This is the same object that the method is called on so that we
can facilitate method chaining.

Examples
--------
Let's create a table with footnotes to demonstrate the spec DSL. We'll use superscript bold
marks for inline references and superscript italic marks in the footer (the default).

```{python}
from great_tables import GT, loc
import pandas as pd

df = pd.DataFrame({"city": ["Paris", "London", "Tokyo"], "pop_m": [2.1, 8.8, 13.9]})

(
GT(df)
.tab_header(title="Major Cities")
.tab_footnote("2023 estimate", locations=loc.body(columns="pop_m", rows=[0, 1, 2]))
.tab_footnote("Metropolitan area", locations=loc.column_labels(columns="pop_m"))
.opt_footnote_spec(spec_ref="^b", spec_ftr="^i")
)
```

Use parenthesized marks at baseline (no superscript) in both the inline references and the
footer listing.

```{python}
(
GT(df)
.tab_header(title="Major Cities")
.tab_footnote("2023 estimate", locations=loc.body(columns="pop_m", rows=[0, 1, 2]))
.tab_footnote("Metropolitan area", locations=loc.column_labels(columns="pop_m"))
.opt_footnote_spec(spec_ref="(x)", spec_ftr="(x)")
)
```

Use superscript bold marks inline with bracketed marks and a trailing period in the footer.

```{python}
(
GT(df)
.tab_header(title="Major Cities")
.tab_footnote("2023 estimate", locations=loc.body(columns="pop_m", rows=[0, 1, 2]))
.tab_footnote("Metropolitan area", locations=loc.column_labels(columns="pop_m"))
.opt_footnote_spec(spec_ref="^b", spec_ftr="[x].")
)
```
"""
valid_chars = set("^bi()[].x")

if spec_ref is not None:
invalid = set(spec_ref) - valid_chars
if invalid:
raise ValueError(
f"Invalid characters in `spec_ref`: {', '.join(repr(c) for c in sorted(invalid))}. "
f"Valid characters are: ^, b, i, (, ), [, ], ., x"
)

if spec_ftr is not None:
invalid = set(spec_ftr) - valid_chars
if invalid:
raise ValueError(
f"Invalid characters in `spec_ftr`: {', '.join(repr(c) for c in sorted(invalid))}. "
f"Valid characters are: ^, b, i, (, ), [, ], ., x"
)

kwargs: dict[str, str] = {}
if spec_ref is not None:
kwargs["footnotes_spec_ref"] = spec_ref
if spec_ftr is not None:
kwargs["footnotes_spec_ftr"] = spec_ftr

return tab_options(self, **kwargs)


def opt_footnote_order(
self: GTSelf,
order: str = "marks_last",
) -> GTSelf:
"""
Option to modify the ordering of footnotes.

Control the arrangement of marked footnotes (those attached to cells) relative to unmarked
footnotes (general notes added with `tab_footnote()` without `locations=`) in the footer.

Parameters
----------
order
The ordering mode. One of:

- `"marks_last"`: unmarked footnotes first, then marked (the default)
- `"marks_first"`: marked footnotes first, then unmarked
- `"preserve_order"`: all footnotes in the order they were added

Returns
-------
GT
The GT object is returned. This is the same object that the method is called on so that we
can facilitate method chaining.

Examples
--------
Let's create a table with both marked and unmarked footnotes to demonstrate the ordering modes.
With `"marks_first"`, the marked footnotes appear before the unmarked ones.

```{python}
from great_tables import GT, loc
import pandas as pd

df = pd.DataFrame({"city": ["Paris", "London"], "pop_m": [2.1, 8.8]})

(
GT(df)
.tab_header(title="Major Cities")
.tab_footnote("2023 estimate", locations=loc.body(columns="pop_m", rows=[0, 1]))
.tab_footnote("Rounded to nearest 100k", locations=None)
.tab_footnote("Source: UN World Urbanization Prospects", locations=None)
.opt_footnote_order(order="marks_first")
)
```

With `"preserve_order"`, all footnotes appear in the order they were added, and marks are
assigned by insertion order rather than visual reading order.

```{python}
(
GT(df)
.tab_header(title="Major Cities")
.tab_footnote("2023 estimate", locations=loc.body(columns="pop_m", rows=[0, 1]))
.tab_footnote("Source: UN World Urbanization Prospects", locations=None)
.tab_footnote("City proper only", locations=loc.body(columns="city", rows=[0]))
.opt_footnote_order(order="preserve_order")
)
```
"""
order = _utils._match_arg(
x=order,
lst=["marks_last", "marks_first", "preserve_order"],
)

return tab_options(self, footnotes_order=order)


def opt_row_striping(self: GTSelf, row_striping: bool = True) -> GTSelf:
"""
Option to add or remove row striping.
Expand Down
Loading
Loading