stefan
stefan

Reputation: 124038

Placement of subtitle paragraph before table when exporting multiple tables to DOCX with officer

I want to export multiple tables with a multi row caption or header to the same document using the officer package.

Background: In my company docx template a table or plot has a caption which consists of a title and a subtitle, both with their own styles. While the title corresponds to the caption in docx or officer, the subtitle is simply added as a paragraph.

Thanks to officer this is not a big deal and works great when exporting (gg)plots or just one table to docx.

However, when I export multiple tables the subtitle is placed at the right position only for the first table, while for all subsequent tables the subtitle is placed after the table:

enter image description here

The issue arises both with officer::body_add_table and flexible::body_add_flextable.

I already did some trial and error with changing the order in which I add the elements and had a look at the docs but wasn't able to figure out a solution. Maybe I missed the right argument to fix my issue.

A minimal reproducible example based on the default docx template shipped with officer:

library(officer)

to_docx <- function(docx, value, title, subtitle) {
  # Add table or plot
  if (inherits(value, "ggplot")) {
    pre_label <- seq_id <- "Figure"
    docx <- body_add_gg(docx, value = value)  
  } else {
    pre_label <- seq_id <- "Table"
    docx <- body_add_table(docx, value = value, align_table = "center")  
  }
  
  # Add title above table
  run_num <- run_autonum(seq_id = seq_id, pre_label = pre_label)
  title <- block_caption(title, style = "Image Caption", autonum = run_num)
  docx <- body_add_caption(docx, title, pos = "before")
  # Add subtitle above table
  subtitle <- fpar(ftext(subtitle, prop = fp_text(color = "red")))
  docx <- body_add_fpar(docx, subtitle, style = "Normal", pos = "after")
  docx <- cursor_end(docx)
  # Add a page breaks
  docx <- body_add_break(docx, pos = "after")
  
  invisible(docx)
}

docx <- read_docx()
tab <- head(mtcars[1:3])

to_docx(docx, tab, "Title 1", "Subtitle 1")
to_docx(docx, tab, "Title 2", "Subtitle 2")

fn <- tempfile(fileext = ".docx")
print(docx, fn)

show <- FALSE # Setting to TRUE will open the docx
if (show) fs::file_show(fn)

And as a reference, using the function to export multiple ggplots works as intended:

library(ggplot2)

docx <- read_docx()

gg <- ggplot(mtcars, aes(hp, mpg)) +
  geom_point()

to_docx(docx, gg, "Title 1", "Subtitle 1")
to_docx(docx, gg, "Title 2", "Subtitle 2")

fn <- tempfile(fileext = ".docx")
print(docx, fn)

show <- FALSE
if (show) fs::file_show(fn)

enter image description here

Upvotes: 0

Views: 195

Answers (1)

David Gohel
David Gohel

Reputation: 10675

This was a bug and is now fixed in the dev version (or >= 0.4.5).

With the update, it's important to assign the result of to_docx to docx (there was an internal change with the cursor management):

library(officer)

to_docx <- function(docx, value, title, subtitle) {
  # Add table or plot
  if (inherits(value, "ggplot")) {
    pre_label <- seq_id <- "Figure"
    docx <- body_add_gg(docx, value = value)  
  } else {
    pre_label <- seq_id <- "Table"
    docx <- body_add_table(docx, value = value, align_table = "center")  
  }
  
  # Add title above table
  run_num <- run_autonum(seq_id = seq_id, pre_label = pre_label)
  title <- block_caption(title, style = "Image Caption", autonum = run_num)
  docx <- body_add_caption(docx, title, pos = "before")
  # Add subtitle above table
  subtitle <- fpar(ftext(subtitle, prop = fp_text(color = "red")))
  docx <- body_add_fpar(docx, subtitle, style = "Normal", pos = "after")
  docx <- cursor_end(docx)
  # Add a page breaks
  docx <- body_add_break(docx, pos = "after")
  
  invisible(docx)
}

docx <- read_docx()
tab <- head(mtcars[1:3])

docx <- to_docx(docx, tab, "Title 1", "Subtitle 1")
docx <- to_docx(docx, tab, "Title 2", "Subtitle 2")

fn <- tempfile(fileext = ".docx")
print(docx, fn)

show <- FALSE # Setting to TRUE will open the docx
if (show) fs::file_show(fn)

As an alternative solution, you could also use block_list(), below an illustration (only with the table):

library(officer)
library(ggplot2)

to_docx <- function(docx, value, title, subtitle) {
  # Add table
  pre_label <- seq_id <- "Table"
  run_num <- run_autonum(seq_id = seq_id, pre_label = pre_label)
  
  bl <- block_list(
    block_caption(title, style = "Image Caption", autonum = run_num),
    fpar(subtitle, fp_p = fp_par(word_style = "Normal")),
    block_table(x = value, header = TRUE,
                properties = prop_table(
                  tcf = table_conditional_formatting(
                    first_row = TRUE, first_column = TRUE)
                  ))
  )
  
  docx <- body_add_blocks(docx, bl, pos = "after")
  
  invisible(docx)
}

docx <- read_docx()
tab <- head(mtcars[1:3])


docx <- to_docx(docx, tab, "title-1", "subtitle")
docx <- to_docx(docx, tab, "title-2", "subtitle")

fn <- tempfile(fileext = ".docx")
print(docx, fn)

show <- FALSE # Setting to TRUE will open the docx
if (show) fs::file_show(fn)

Upvotes: 1

Related Questions