Skip to content

Commit 4217ad9

Browse files
authored
Merge pull request #2079 from shannonhaughton/spanner_widths
Update LaTeX spanner width logic to use the correct columns
2 parents 92e47bc + 21aceff commit 4217ad9

File tree

3 files changed

+187
-76
lines changed

3 files changed

+187
-76
lines changed

NEWS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
* Update latex Unicode conversions to be optional (default to TRUE) as well as make them work in-line (@thebioengineer, #2037, #2041)
88

9+
* Fix bug where latex spanner widths were based on incorrect column widths (@shannonhaughton, #2016, #1922)
10+
911
# gt 1.1.0
1012

1113
## New features

R/utils_render_latex.R

Lines changed: 53 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,7 @@ create_table_start_l <- function(data, colwidth_df) {
248248
} else {
249249
n_stub_cols <- length(stub_layout)
250250
}
251-
251+
252252
col_defs[seq_len(n_stub_cols)] <-
253253
paste0(col_defs[seq_len(n_stub_cols)], "|")
254254
}
@@ -455,8 +455,32 @@ create_heading_component_l <- function(data) {
455455
#' @noRd
456456
create_columns_component_l <- function(data, colwidth_df) {
457457

458+
458459
# Get vector representation of stub layout
459460
stub_layout <- get_stub_layout(data = data)
461+
n_stub_cols <- 0
462+
463+
# if exists, get the length of the stub
464+
if(length(stub_layout)>0){
465+
stub_vars <- dt_boxhead_get_var_stub(data = data)
466+
# Get the actual number of stub columns for the header
467+
# This determines how many columns the stub header should span
468+
if ("group_label" %in% stub_layout && "rowname" %in% stub_layout) {
469+
n_stub_cols <- length(stub_vars) + 1 # group_label + all rowname columns
470+
# Get stub_df for width calculations
471+
stub_df <- dplyr::filter(colwidth_df, type %in% c("row_group","stub")) %>%
472+
dplyr::arrange(type)
473+
} else if ("rowname" %in% stub_layout) {
474+
stub_df <- dplyr::filter(colwidth_df, type == "stub")
475+
n_stub_cols <- nrow(stub_df)
476+
} else if ("group_label" %in% stub_layout) { # stub columns can exist in the dataset with only groups in the stub-layout.
477+
n_stub_cols <- 1
478+
stub_df <- dplyr::filter(colwidth_df, type == "row_group")
479+
} else {
480+
n_stub_cols <- length(stub_layout)
481+
stub_df <- dplyr::filter(colwidth_df, type %in% c("stub", "row_group"))
482+
}
483+
}
460484

461485
styles_tbl <- dt_styles_get(data = data)
462486

@@ -495,28 +519,11 @@ create_columns_component_l <- function(data, colwidth_df) {
495519
vctrs::vec_slice(styles_tbl, styles_tbl$locname == "stubhead")
496520
)
497521

498-
# Get the actual number of stub columns for the header
499-
# This determines how many columns the stub header should span
500-
if ("group_label" %in% stub_layout && "rowname" %in% stub_layout) {
501-
n_stub_cols <- 2 # group_label + rowname
502-
# Get stub_df for width calculations
503-
stub_df <- dplyr::filter(colwidth_df, type %in% c("stub", "row_group"))
504-
} else if ("rowname" %in% stub_layout) {
505-
stub_df <- dplyr::filter(colwidth_df, type == "stub")
506-
n_stub_cols <- nrow(stub_df)
507-
} else if ("group_label" %in% stub_layout) {
508-
n_stub_cols <- 1
509-
stub_df <- dplyr::filter(colwidth_df, type == "row_group")
510-
} else {
511-
n_stub_cols <- length(stub_layout)
512-
stub_df <- dplyr::filter(colwidth_df, type %in% c("stub", "row_group"))
513-
}
514-
515522
# Check if we have multiple stubhead labels for multi-column stub
516523
stub_vars <- dt_boxhead_get_var_stub(data = data)
517524
has_multi_column_stub <- length(stub_vars) > 1 && !any(is.na(stub_vars))
518525
has_multiple_labels <- has_multi_column_stub && length(stubh$label) > 1
519-
526+
520527
if (has_multiple_labels) {
521528
# Create individual headers for each stub column
522529
# Process in reverse order since prepend_vec adds to the front
@@ -532,13 +539,13 @@ create_columns_component_l <- function(data, colwidth_df) {
532539
} else {
533540
# Single label spanning all stub columns (current behavior)
534541
headings_vars <- prepend_vec(headings_vars, "::stub")
535-
542+
536543
stub_label <- ifelse(
537544
length(stubh$label) > 0,
538545
apply_cell_styles_l(stubh$label[1], styles_stubhead),
539546
""
540547
)
541-
548+
542549
if (n_stub_cols > 1L) {
543550
# Use multicolumn to span all stub columns
544551
if (any(stub_df$unspec == 1L)) {
@@ -592,28 +599,10 @@ create_columns_component_l <- function(data, colwidth_df) {
592599
)
593600

594601
if (length(stub_layout) > 0) {
595-
596602
# Get the actual number of stub columns for spanners
597-
if ("group_label" %in% stub_layout && "rowname" %in% stub_layout) {
598-
599-
# group_label + rowname
600-
n_stub_cols <- 2
601-
602-
} else if ("rowname" %in% stub_layout) {
603-
604-
stub_df_cols <- dplyr::filter(colwidth_df, type == "stub")
605-
n_stub_cols <- nrow(stub_df_cols)
606-
607-
} else if ("group_label" %in% stub_layout) {
608-
609-
n_stub_cols <- 1
610-
611-
} else {
612-
613-
n_stub_cols <- length(stub_layout)
614-
}
615-
616603
stub_matrix <- matrix(nrow = nrow(spanners), ncol = n_stub_cols)
604+
# retain stub names
605+
colnames(stub_matrix) <- stub_df$var
617606

618607
spanners <- cbind(stub_matrix, spanners)
619608
spanner_ids <- cbind(stub_matrix, spanner_ids)
@@ -629,6 +618,7 @@ create_columns_component_l <- function(data, colwidth_df) {
629618
# We need a parallel vector of spanner labels and this could
630619
# be part of the `spanners_rle` list
631620
spanners_rle$labels <- spanners_i[cumsum(spanners_rle$lengths)]
621+
col_order <- data.frame(var = colnames(spanner_ids))
632622
spanners_rle <- apply_spanner_styles_l(spanners_rle, styles_tbl)
633623

634624
begins <- (cumsum(utils::head(c(0, spanners_rle$lengths), -1)) + 1)[!is.na(spanners_rle$values)]
@@ -637,10 +627,9 @@ create_columns_component_l <- function(data, colwidth_df) {
637627

638628
is_spanner_na <- is.na(spanners_rle$values)
639629
is_spanner_single <- spanners_rle$lengths == 1
640-
641630
firsts <- utils::head(cumsum(c(1L, spanners_rle$lengths)), -1L)
642631
lasts <- cumsum(spanners_rle$lengths)
643-
span_widths <- calculate_multicolumn_width_text_l(begins = firsts, ends = lasts, colwidth_df = colwidth_df)
632+
span_widths <- calculate_multicolumn_width_text_l(begins = firsts, ends = lasts, col_order = col_order ,colwidth_df = colwidth_df)
644633
tex_widths <-
645634
ifelse(
646635
nzchar(span_widths),
@@ -665,30 +654,10 @@ create_columns_component_l <- function(data, colwidth_df) {
665654
# If there is a stub we need to tweak the spanners row with a blank multicolumn
666655
# statement that's the same width as that in the columns row; this is to
667656
# prevent the automatic vertical line that would otherwise appear here
668-
669-
# Get the actual number of stub columns
670-
if ("group_label" %in% stub_layout && "rowname" %in% stub_layout) {
671-
672-
# group_label + rowname
673-
n_stub_cols <- 2
674-
675-
} else if ("rowname" %in% stub_layout) {
676-
677-
stub_df_cols <- dplyr::filter(colwidth_df, type == "stub")
678-
n_stub_cols <- nrow(stub_df_cols)
679-
680-
} else if ("group_label" %in% stub_layout) {
681-
682-
n_stub_cols <- 1
683-
684-
} else {
685-
686-
n_stub_cols <- length(stub_layout)
687-
}
688-
657+
689658
if (n_stub_cols > 1L) {
690659

691-
tex_stub_width <- calculate_multicolumn_width_text_l(begins = 1, ends = n_stub_cols, colwidth_df = colwidth_df)
660+
tex_stub_width <- calculate_multicolumn_width_text_l(begins = 1, ends = n_stub_cols, col_order = col_order, colwidth_df = colwidth_df)
692661
if (tex_stub_width == "") {
693662
mc_stub <- "l"
694663
} else {
@@ -754,16 +723,16 @@ create_body_component_l <- function(data, colwidth_df) {
754723
# (hide repeated values in all columns except the rightmost)
755724
if (has_stub_column) {
756725
stub_vars <- dt_boxhead_get_var_stub(data = data)
757-
726+
758727
if (length(stub_vars) > 1 && !any(is.na(stub_vars))) {
759728

760729
# Get original body data to check for consecutive repeating values
761730
original_body <- dt_data_get(data = data)
762-
731+
763732
# Process all stub columns except the rightmost one
764733
hierarchy_vars <- stub_vars[-length(stub_vars)]
765734
stub_matrix <- as.matrix(original_body[, hierarchy_vars, drop = FALSE])
766-
735+
767736
# Determine which columns to hide based on hierarchical grouping
768737
for (col_idx in seq_along(hierarchy_vars)) {
769738

@@ -772,18 +741,18 @@ create_body_component_l <- function(data, colwidth_df) {
772741
if ("group_label" %in% stub_layout) {
773742
matrix_col_idx <- col_idx + 1
774743
}
775-
744+
776745
for (row_idx in 2:n_rows) {
777746
should_hide <- TRUE
778-
747+
779748
# Check if current value matches previous value (handle NAs properly)
780749
curr_val <- stub_matrix[row_idx, col_idx]
781750
prev_val <- stub_matrix[row_idx - 1, col_idx]
782-
751+
783752
if (!identical(curr_val, prev_val)) {
784753
should_hide <- FALSE
785754
}
786-
755+
787756
# Also check that all columns to the left match
788757
if (should_hide && col_idx > 1) {
789758
for (left_col_idx in 1:(col_idx - 1)) {
@@ -795,7 +764,7 @@ create_body_component_l <- function(data, colwidth_df) {
795764
}
796765
}
797766
}
798-
767+
799768
# Hide the value by making it empty if conditions are met
800769
if (should_hide) {
801770
row_splits_body[[row_idx]][matrix_col_idx] <- ""
@@ -1471,7 +1440,7 @@ create_summary_rows_l <- function(
14711440

14721441
# Get vector representation of stub layout
14731442
stub_layout <- get_stub_layout(data = data)
1474-
1443+
14751444
# Get the actual number of stub columns (including multiple rowname columns)
14761445
stub_vars <- dt_boxhead_get_var_stub(data = data)
14771446
stub_width <- if (length(stub_vars) == 1 && is.na(stub_vars)) 0 else length(stub_vars)
@@ -1902,6 +1871,7 @@ create_colwidth_df_l <- function(data) {
19021871

19031872
n <- dim(boxhead)[1L]
19041873
width_df <- data.frame(
1874+
var = boxhead$var,
19051875
type = boxhead$type,
19061876
unspec = rep.int(0L, n),
19071877
lw = rep.int(0L, n),
@@ -1975,10 +1945,17 @@ create_colwidth_df_l <- function(data) {
19751945
width_df
19761946
}
19771947

1978-
calculate_multicolumn_width_text_l <- function(begins, ends, colwidth_df) {
1979-
1948+
calculate_multicolumn_width_text_l <- function(begins, ends, col_order, colwidth_df) {
19801949
out_text <- rep("", times = length(begins))
19811950

1951+
# order by column order to ensure correct columns are used
1952+
# this is important if data order has changed, or there are hidden columns etc
1953+
colwidth_df <- col_order %>%
1954+
dplyr::left_join(colwidth_df, by = "var") %>%
1955+
dplyr::filter(type != "hidden" )
1956+
1957+
1958+
19821959
for (i in seq_along(begins)) {
19831960
ind <- seq(from = begins[i], to = ends[i])
19841961

0 commit comments

Comments
 (0)