Skip to content
Open
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
137 changes: 108 additions & 29 deletions great_tables/_utils_render_latex.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,21 @@ def create_table_start_l(data: GTData, use_longtable: bool) -> str:
# Get the column alignments for the visible columns as a list of `col_defs`
col_defs = [align[0] for align in data._boxhead._get_default_alignments()]

# Check if stub is present and determine layout
has_summary_rows = bool(data._summary_rows or data._summary_rows_grand)
stub_layout = data._stub._get_stub_layout(
has_summary_rows=has_summary_rows, options=data._options
)

# Determine if there's a stub column (rowname or group_label)
has_stub = len(stub_layout) > 0

# Build stub column definitions (left-aligned with separator)
stub_col_defs = ""
if has_stub:
# Add 'l' for each stub column, with a '|' separator after the last one
stub_col_defs = "l" * len(stub_layout) + "|"

# If a table width is specified, add an extra column
# space to fill in enough space to match the width
extra_sep = ""
Expand Down Expand Up @@ -181,6 +196,7 @@ def create_table_start_l(data: GTData, use_longtable: bool) -> str:
longtable_post_length if use_longtable else "",
"\\begin{longtable}{" if use_longtable else hdr_tabular,
extra_sep,
stub_col_defs,
"".join(col_defs),
"}",
]
Expand Down Expand Up @@ -264,13 +280,30 @@ def create_columns_component_l(data: GTData) -> str:
# Determine the finalized number of spanner rows
spanner_row_count = _get_spanners_matrix_height(data=data, omit_columns_row=True)

# Check if stub is present and determine layout
has_summary_rows = bool(data._summary_rows or data._summary_rows_grand)
stub_layout = data._stub._get_stub_layout(
has_summary_rows=has_summary_rows, options=data._options
)

# Determine if there's a stub column (rowname or group_label)
has_stub = len(stub_layout) > 0

# Create stub header cells (empty space for each stub column)
stub_headers = []
if has_stub:
stub_headers = [" "] * len(stub_layout)

# Get the column headings
headings_labels = data._boxhead._get_default_column_labels()

# Ensure that the heading labels are processed for LaTeX
headings_labels = [_process_text(x, context="latex") for x in headings_labels]

table_col_headings = "".join(latex_heading_row(content=headings_labels))
# Prepend stub headers to column headings
all_headings = stub_headers + headings_labels

table_col_headings = "".join(latex_heading_row(content=all_headings))

if spanner_row_count > 0:
boxhead = data._boxhead
Expand Down Expand Up @@ -313,6 +346,11 @@ def create_columns_component_l(data: GTData) -> str:
spanner_lines = []
span_accumulator = 0

# Add empty cells for stub columns in spanner row
if has_stub:
spanner_labs.extend([" "] * len(stub_layout))
span_accumulator = len(stub_layout)

for j, level_i_spanner_j in enumerate(level_i_spanners):
if level_i_spanner_j is None:
# Get the number of columns to span nothing
Expand Down Expand Up @@ -385,29 +423,90 @@ def create_body_component_l(data: GTData) -> str:
# Get the default column vars
column_vars = data._boxhead._get_default_columns()

# Check if stub is present and determine layout
has_summary_rows = bool(data._summary_rows or data._summary_rows_grand)
stub_layout = data._stub._get_stub_layout(
has_summary_rows=has_summary_rows, options=data._options
)

# Determine what stub components are present
has_row_stub_column = "rowname" in stub_layout
has_group_stub_column = "group_label" in stub_layout
has_groups = len(data._stub.group_ids) > 0

# Get the stub column info if it exists
row_stub_var = data._boxhead._get_stub_column()

body_rows = []

ordered_index: list[tuple[int, GroupRowInfo | None]] = data._stub.group_indices_map()

for i, _ in ordered_index:
prev_group_info = None
first_group_added = False

# Calculate total number of columns for multicolumn spanning in group headers
n_cols = len(column_vars) + len(stub_layout)

for i, group_info in ordered_index:
# Handle row group labels
if has_groups and group_info is not None:
# Only create group row if this is first row of the group
if group_info is not prev_group_info:
group_label = group_info.defaulted_label()

# Process the group label for LaTeX
group_label = _process_text(group_label, context="latex")

# When group is shown as a column, we don't add a separate row
# Instead, it will be added as a cell in each data row
if not has_group_stub_column:
# Add midrule before group heading (except for first group, which already has
# one from column headers) then the group heading, then midrule after
if first_group_added:
group_row = f"\\midrule\\addlinespace[2.5pt]\n\\multicolumn{{{n_cols}}}{{l}}{{{group_label}}} \\\\[2.5pt] \n\\midrule\\addlinespace[2.5pt]"
else:
group_row = f"\\multicolumn{{{n_cols}}}{{l}}{{{group_label}}} \\\\[2.5pt] \n\\midrule\\addlinespace[2.5pt]"
first_group_added = True
body_rows.append(group_row)

# Create data row cells
body_cells: list[str] = []

# Create a body row
# Add stub cells first (group_label column, then rowname column)
if has_group_stub_column and group_info is not None:
# Only show group label in first row of group
if group_info is prev_group_info:
# Use an empty cell for continuation rows in same group
body_cells.append("")
else:
# Get the group label from the group info
group_label = group_info.defaulted_label()
group_label = _process_text(group_label, context="latex")

body_cells.append(group_label)

if has_row_stub_column:
# Get the row name from the stub
rowname = _get_cell(tbl_data, i, row_stub_var.var)
rowname_str = str(rowname)

body_cells.append(rowname_str)

# Add data cells
for colinfo in column_vars:
cell_content = _get_cell(tbl_data, i, colinfo.var)
cell_str: str = str(cell_content)

body_cells.append(cell_str)

# When joining the body cells together, we need to ensure that each item is separated by
# an ampersand and that the row is terminated with a double backslash
body_cells = " & ".join(body_cells) + " \\\\"
# Join cells with ampersand and terminate with a double backslash
body_row_str = " & ".join(body_cells) + " \\\\"

body_rows.append("".join(body_cells))
body_rows.append(body_row_str)

# When joining all the body rows together, we need to ensure that each row is separated by
# newline except for the last
prev_group_info = group_info

# Join all body rows with newlines
all_body_rows = "\n".join(body_rows)

return all_body_rows
Expand Down Expand Up @@ -553,26 +652,6 @@ def _render_as_latex(data: GTData, use_longtable: bool = False, tbl_pos: str | N
if data._styles:
_not_implemented("Styles are not yet supported in LaTeX output.")

# Get list representation of stub layout
has_summary_rows = bool(data._summary_rows or data._summary_rows_grand)
stub_layout = data._stub._get_stub_layout(
has_summary_rows=has_summary_rows, options=data._options
)

# Throw exception if a stub is present in the table
if "rowname" in stub_layout or "group_label" in stub_layout:
raise NotImplementedError(
"The table stub (row names and/or row groups) are not yet supported in LaTeX output."
)

# Determine if row groups are used
has_groups = len(data._stub.group_ids) > 0

# Throw exception if row groups are used in LaTeX output (extra case where row
# groups are used but not in the stub)
if has_groups:
raise NotImplementedError("Row groups are not yet supported in LaTeX output.")

# Create a LaTeX fragment for the start of the table
table_start = create_table_start_l(data=data, use_longtable=use_longtable)

Expand Down
166 changes: 166 additions & 0 deletions tests/__snapshots__/test_utils_render_latex.ambr
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,63 @@

'''
# ---
# name: test_snap_render_as_latex_groups_as_column
'''
\begin{table}[!t]


\fontsize{12.0pt}{14.4pt}\selectfont

\begin{tabular*}{\linewidth}{@{\extracolsep{\fill}}l|rllrrrrl}
\toprule
& num & char & fctr & date & time & datetime & currency & row \\
\midrule\addlinespace[2.5pt]
grp\_a & 0.1111 & apricot & one & 2015-01-15 & 13:35 & 2018-01-01 02:22 & 49.95 & row\_1 \\
& 2.222 & banana & two & 2015-02-15 & 14:40 & 2018-02-02 14:33 & 17.95 & row\_2 \\
& 33.33 & coconut & three & 2015-03-15 & 15:45 & 2018-03-03 03:44 & 1.39 & row\_3 \\
& 444.4 & durian & four & 2015-04-15 & 16:50 & 2018-04-04 15:55 & 65100.0 & row\_4 \\
grp\_b & 5550.0 & nan & five & 2015-05-15 & 17:55 & 2018-05-05 04:00 & 1325.81 & row\_5 \\
& nan & fig & six & 2015-06-15 & nan & 2018-06-06 16:11 & 13.255 & row\_6 \\
& 777000.0 & grapefruit & seven & nan & 19:10 & 2018-07-07 05:22 & nan & row\_7 \\
& 8880000.0 & honeydew & eight & 2015-08-15 & 20:20 & nan & 0.44 & row\_8 \\
\bottomrule
\end{tabular*}

\end{table}

'''
# ---
# name: test_snap_render_as_latex_groups_only
'''
\begin{table}[!t]


\fontsize{12.0pt}{14.4pt}\selectfont

\begin{tabular*}{\linewidth}{@{\extracolsep{\fill}}rllrrrrl}
\toprule
num & char & fctr & date & time & datetime & currency & row \\
\midrule\addlinespace[2.5pt]
\multicolumn{8}{l}{grp\_a} \\[2.5pt]
\midrule\addlinespace[2.5pt]
0.1111 & apricot & one & 2015-01-15 & 13:35 & 2018-01-01 02:22 & 49.95 & row\_1 \\
2.222 & banana & two & 2015-02-15 & 14:40 & 2018-02-02 14:33 & 17.95 & row\_2 \\
33.33 & coconut & three & 2015-03-15 & 15:45 & 2018-03-03 03:44 & 1.39 & row\_3 \\
444.4 & durian & four & 2015-04-15 & 16:50 & 2018-04-04 15:55 & 65100.0 & row\_4 \\
\midrule\addlinespace[2.5pt]
\multicolumn{8}{l}{grp\_b} \\[2.5pt]
\midrule\addlinespace[2.5pt]
5550.0 & nan & five & 2015-05-15 & 17:55 & 2018-05-05 04:00 & 1325.81 & row\_5 \\
nan & fig & six & 2015-06-15 & nan & 2018-06-06 16:11 & 13.255 & row\_6 \\
777000.0 & grapefruit & seven & nan & 19:10 & 2018-07-07 05:22 & nan & row\_7 \\
8880000.0 & honeydew & eight & 2015-08-15 & 20:20 & nan & 0.44 & row\_8 \\
\bottomrule
\end{tabular*}

\end{table}

'''
# ---
# name: test_snap_render_as_latex_longtable
'''
\begingroup
Expand Down Expand Up @@ -96,3 +153,112 @@

'''
# ---
# name: test_snap_render_as_latex_no_stub_no_groups
'''
\begin{table}[!t]


\fontsize{12.0pt}{14.4pt}\selectfont

\begin{tabular*}{\linewidth}{@{\extracolsep{\fill}}rllrrrrll}
\toprule
num & char & fctr & date & time & datetime & currency & row & group \\
\midrule\addlinespace[2.5pt]
0.1111 & apricot & one & 2015-01-15 & 13:35 & 2018-01-01 02:22 & 49.95 & row\_1 & grp\_a \\
2.222 & banana & two & 2015-02-15 & 14:40 & 2018-02-02 14:33 & 17.95 & row\_2 & grp\_a \\
33.33 & coconut & three & 2015-03-15 & 15:45 & 2018-03-03 03:44 & 1.39 & row\_3 & grp\_a \\
444.4 & durian & four & 2015-04-15 & 16:50 & 2018-04-04 15:55 & 65100.0 & row\_4 & grp\_a \\
5550.0 & nan & five & 2015-05-15 & 17:55 & 2018-05-05 04:00 & 1325.81 & row\_5 & grp\_b \\
nan & fig & six & 2015-06-15 & nan & 2018-06-06 16:11 & 13.255 & row\_6 & grp\_b \\
777000.0 & grapefruit & seven & nan & 19:10 & 2018-07-07 05:22 & nan & row\_7 & grp\_b \\
8880000.0 & honeydew & eight & 2015-08-15 & 20:20 & nan & 0.44 & row\_8 & grp\_b \\
\bottomrule
\end{tabular*}

\end{table}

'''
# ---
# name: test_snap_render_as_latex_stub_and_groups
'''
\begin{table}[!t]


\fontsize{12.0pt}{14.4pt}\selectfont

\begin{tabular*}{\linewidth}{@{\extracolsep{\fill}}l|rllrrrr}
\toprule
& num & char & fctr & date & time & datetime & currency \\
\midrule\addlinespace[2.5pt]
\multicolumn{8}{l}{grp\_a} \\[2.5pt]
\midrule\addlinespace[2.5pt]
row\_1 & 0.1111 & apricot & one & 2015-01-15 & 13:35 & 2018-01-01 02:22 & 49.95 \\
row\_2 & 2.222 & banana & two & 2015-02-15 & 14:40 & 2018-02-02 14:33 & 17.95 \\
row\_3 & 33.33 & coconut & three & 2015-03-15 & 15:45 & 2018-03-03 03:44 & 1.39 \\
row\_4 & 444.4 & durian & four & 2015-04-15 & 16:50 & 2018-04-04 15:55 & 65100.0 \\
\midrule\addlinespace[2.5pt]
\multicolumn{8}{l}{grp\_b} \\[2.5pt]
\midrule\addlinespace[2.5pt]
row\_5 & 5550.0 & nan & five & 2015-05-15 & 17:55 & 2018-05-05 04:00 & 1325.81 \\
row\_6 & nan & fig & six & 2015-06-15 & nan & 2018-06-06 16:11 & 13.255 \\
row\_7 & 777000.0 & grapefruit & seven & nan & 19:10 & 2018-07-07 05:22 & nan \\
row\_8 & 8880000.0 & honeydew & eight & 2015-08-15 & 20:20 & nan & 0.44 \\
\bottomrule
\end{tabular*}

\end{table}

'''
# ---
# name: test_snap_render_as_latex_stub_and_groups_as_column
'''
\begin{table}[!t]


\fontsize{12.0pt}{14.4pt}\selectfont

\begin{tabular*}{\linewidth}{@{\extracolsep{\fill}}ll|rllrrrr}
\toprule
& & num & char & fctr & date & time & datetime & currency \\
\midrule\addlinespace[2.5pt]
grp\_a & row\_1 & 0.1111 & apricot & one & 2015-01-15 & 13:35 & 2018-01-01 02:22 & 49.95 \\
& row\_2 & 2.222 & banana & two & 2015-02-15 & 14:40 & 2018-02-02 14:33 & 17.95 \\
& row\_3 & 33.33 & coconut & three & 2015-03-15 & 15:45 & 2018-03-03 03:44 & 1.39 \\
& row\_4 & 444.4 & durian & four & 2015-04-15 & 16:50 & 2018-04-04 15:55 & 65100.0 \\
grp\_b & row\_5 & 5550.0 & nan & five & 2015-05-15 & 17:55 & 2018-05-05 04:00 & 1325.81 \\
& row\_6 & nan & fig & six & 2015-06-15 & nan & 2018-06-06 16:11 & 13.255 \\
& row\_7 & 777000.0 & grapefruit & seven & nan & 19:10 & 2018-07-07 05:22 & nan \\
& row\_8 & 8880000.0 & honeydew & eight & 2015-08-15 & 20:20 & nan & 0.44 \\
\bottomrule
\end{tabular*}

\end{table}

'''
# ---
# name: test_snap_render_as_latex_stub_only
'''
\begin{table}[!t]


\fontsize{12.0pt}{14.4pt}\selectfont

\begin{tabular*}{\linewidth}{@{\extracolsep{\fill}}l|rllrrrrl}
\toprule
& num & char & fctr & date & time & datetime & currency & group \\
\midrule\addlinespace[2.5pt]
row\_1 & 0.1111 & apricot & one & 2015-01-15 & 13:35 & 2018-01-01 02:22 & 49.95 & grp\_a \\
row\_2 & 2.222 & banana & two & 2015-02-15 & 14:40 & 2018-02-02 14:33 & 17.95 & grp\_a \\
row\_3 & 33.33 & coconut & three & 2015-03-15 & 15:45 & 2018-03-03 03:44 & 1.39 & grp\_a \\
row\_4 & 444.4 & durian & four & 2015-04-15 & 16:50 & 2018-04-04 15:55 & 65100.0 & grp\_a \\
row\_5 & 5550.0 & nan & five & 2015-05-15 & 17:55 & 2018-05-05 04:00 & 1325.81 & grp\_b \\
row\_6 & nan & fig & six & 2015-06-15 & nan & 2018-06-06 16:11 & 13.255 & grp\_b \\
row\_7 & 777000.0 & grapefruit & seven & nan & 19:10 & 2018-07-07 05:22 & nan & grp\_b \\
row\_8 & 8880000.0 & honeydew & eight & 2015-08-15 & 20:20 & nan & 0.44 & grp\_b \\
\bottomrule
\end{tabular*}

\end{table}

'''
# ---
Loading
Loading