Automatic positioning of images and tables in PowerPoint slide officer - r

Hi so I have I am trying to write my own function that will automatically size and central align the images/tables that I am generating from officer.
The top/left functions in the add_image and add_table are good when I add each table manually as The differing tables are differing sizes but I wasn't sure how to automatically do it for different shaped images and tables.
I have the following code for images:
Image <- function(PP,title,footer,pageNO,path){
im <- load.image(path)
width <- imager::width(im)
height <- imager::height(im)
ratio <- width/height
if(ratio < 9.15 / 3.6){
y <- 3.6
x <- 3.6 * ratio
}
if(ratio >= 9.15 / 3.6){
y <- 9.15 / ratio
x <- 9.15
}
PP <- PP %>%
add_slide(layout = "Title and Content", master = "Office Theme") %>%
ph_with_text(type = "title", str = title) %>%
ph_with_text(type = "dt", str = format(Sys.Date())) %>%
ph_with_img(type = "body", src = path, height = y, width = x) %>%
ph_with_text(type = "ftr",str = footer) %>%
ph_with_text(type = "sldNum", str = pageNO)
}
I would then like to align this image in the centre of the slide but am not sure how I would do this.
Then the same for tables but I have no idea how to do this as I would have to automatically rescale the width and height of columns based on original width and height of the flextable image as I wouldn't want to lose the aspect ratio unless there is a different way?
Current code for this is: but it doesn't do any resizing:
Slide <- function(PP,title,footer,pageNO,table){
PP <- PP %>%
add_slide(layout = "Title and Content", master = "Office Theme") %>%
ph_with_text(type = "title", str = title) %>%
ph_with_text(type = "ftr",str = footer) %>%
ph_with_text(type = "dt", str = format(Sys.Date())) %>%
ph_with_flextable(value = table, type = "body") %>%
ph_with_text(type = "sldNum", str = pageNO)
}
Many thanks in advance, any help would be greatly appreciated

Old question, but if anyone's looking here's a reproducible example that does the following:
Create a new PowerPoint deck;
Create a flextable table that we'd like to include;
Put the table in the centre of a new slide; and,
Write it to file.
You can extend this to solve more particular problems, e.g. by using PowerPoint templates, using officer::layout_properties() to get more details about layouts, and so on.
library(tidyverse)
library(officer)
library(flextable)
# create a PowerPoint slide deck
deck <- officer::read_pptx()
# make a fletable with the first 5 rows of mtcars
deck_table <- mtcars %>%
head(5) %>%
flextable::flextable()
# get table dimensions
table_width <- sum(dim(deck_table)$widths)
table_height <- sum(dim(deck_table)$heights)
# get slide dimensions in inches
# these are from PowerPoint defaults for 3:4; widescreen width is 13.333 in
slide_width <- 10
slide_height <- 7.5
# find the left and top margins to centre the table
table_leftmargin <- (slide_width - table_width)/2
table_topmargin <- (slide_height - table_height)/2
# add a slide, then add the table at the desired location
deck %>%
officer::add_slide() %>%
officer::ph_with(value = deck_table,
location = officer::ph_location(left = table_leftmargin,
top = table_topmargin))
# save to file
print(deck, "test.pptx")

Related

Alternate portrait and landscape sections using officer package in R

I would like to make a .pdf report that alternates between portrait (for text) and landscape (for large figures) sections. I use the officer package in R which generates a .docx, that I can convert into .pdf using Word or LibreOffice.
I made some attempts, but I have the following problems: I have a blank portrait page at the end, which I would like to remove, and if I convert into pdf it adds blank pages between the portrait and landscapes pages. You can also detect these blank pages in word by numbering the pages (they switch for 1 to 3 will skipping the 2), or looking at the impression viewer. This problem is explained in http://wordfaqs.ssbarnhill.com/BlankPage.htm for how to deal with them in Word, but I would like a solution to remove those blank pages using officer because I will have hundreds of sections alternating between portrait and landscape to deal with.
Here is my attempt:
library(officer)
doc_1 <- read_docx()
doc_1 <- body_add_par(doc_1, value = "Portrait")
doc_1 <- body_end_block_section(doc_1, block_section(prop_section()))
doc_1 <- body_add_par(doc_1, value = "Landscape")
doc_1 <- body_end_section_landscape(doc_1)
temp <- tempfile(fileext = ".docx")
temp
print(doc_1, target = temp)
# system(paste0('open "', temp, '"'))
The answer of David (underneath) improves my problem, but it does remove some of the portrait orientations when I try to iterate it using body_add_docx (which I use for efficiency reasons, see https://github.com/davidgohel/officer/issues/184):
library(officer)
portrait_section_prop <- prop_section(page_size = page_size(orient = "portrait"))
landscape_section_prop <- prop_section(page_size = page_size(orient = "landscape"))
core <- function(i){
doc_1 <- read_docx() |>
body_add_par(value = paste("Portrait", i)) |>
body_end_block_section(value = block_section(portrait_section_prop)) |>
body_add_par(value = paste("Landscape", i)) |>
body_end_block_section(value = block_section(landscape_section_prop)) |>
body_set_default_section(landscape_section_prop)
return(doc_1)
}
accu <- core(1)
for(i in 2:10){
doc_1 <- core(i)
temp <- tempfile(fileext = ".docx")
print(doc_1, temp)
accu <- body_add_docx(accu, temp)
}
print(accu, target = tempfile(fileext = ".docx")) |> browseURL()
Here is the code you need, you need to define the same default section than the one you want to end the document so that Word agree to not add a page:
library(officer)
portrait_section_prop <- prop_section(page_size = page_size(orient = "portrait"))
landscape_section_prop <- prop_section(page_size = page_size(orient = "landscape"))
doc_1 <- read_docx() |>
body_add_par(value = "Portrait") |>
body_end_block_section(value = block_section(portrait_section_prop)) |>
body_add_par(value = "Landscape") |>
body_end_block_section(value = block_section(landscape_section_prop)) |>
body_set_default_section(landscape_section_prop)
temp <- tempfile(fileext = ".docx")
print(doc_1, target = temp)

Using officer in R to hyperlink to another slide within a flextable cell

Using officer in R, I've used ph_slidelink() to hyperlink a text box to another slide in the presentation, and I've used compose() and hyperlink_text() to hyperlink a cell within a flextable. My question is: is there a way to combine these, and to hyperlink to another slide in the presentation within the cell of a flextable?
Here's a very simple example of code I'd like to transform:
library(officer)
library(flextable)
library(magrittr)
ft <- data.frame(slide_number = seq(3)) %>%
flextable() %>%
width(width = 3)
doc <- read_pptx() %>%
add_slide() %>%
ph_with("Table of Contents", location = ph_location_label("Title 1")) %>%
ph_with(ft, location = ph_location_label("Content Placeholder 2"))
for (i in seq(3)) {
doc <- doc %>%
add_slide() %>%
ph_with(paste("Slide", i), location = ph_location_label("Title 1"))
}
print(doc, target = "~/Desktop/officer_example.pptx" )
...and in this case I'd like the 1/2/3 in the table of contents (here) to link to slides 1/2/3.
Is this possible?

Setting line height in table created using flextable inserted into powerpoint using officer

I have drafted some dataframes with text inside that linebreaks are present in each cell, the dataframe is converted to flextable and then inserted into a powerpoint slide using officer. I found the line height a bit too much, I tried using the height_all function in flextable to make reduce the line height, but it is not working. Please find the sample code as below:
library(officer)
library(dplyr)
pptx.output.st00 <- read_pptx()
data(iris)
data.df <- head(iris) %>%
as_tibble %>%
mutate_all(.,as.character) %>%
mutate_all(.,~paste0(.,'\ntesting'))
pptx.tbl <- data.df %>%
flextable %>%
height_all(height = 0.01) # this line is not working
pptx.output.st01 <- pptx.output.st00 %>%
add_slide(.,layout = 'Title and Content',master = 'Office Theme') %>%
ph_with(.,value=pptx.tbl,location=ph_location(type='body'))
print(pptx.output.st01,'presentation.output.pptx')
Currently I need to manually change the paragraph settings for table as the screen capture below:
Is there a way in officer of flextable to set up line height for table? Thanks!
I didn't satisfied by this ad hoc way, but padding(padding.top = 0, padding.bottom = 0.5) and height_all(0.45) gives a bit better output.
pptx.output.st01 <- pptx.output.st00 %>%
add_slide(.,layout = 'Title and Content',master = 'Office Theme') %>%
ph_with(.,
value=pptx.tbl %>%
padding(padding.top = 0, padding.bottom = 0.5) %>%
height_all(0.45),
location=ph_location(type='body'))

Keep aspect ration image with body_replace_img_at_bkm function officeR package

Is it possible to keep the original aspect ration while inserting an image in a .docx document with the function body_replace_img_at_bkm() from the package officer?
my code looks like this:
library(officer)
library(magrittr)
img.file <- file.path( R.home("doc"), "html", "logo.jpg" )
doc <- read_docx() %>%
body_add_par("centered text", style = "centered") %>%
slip_in_text(". How are you", style = "strong") %>%
body_bookmark("text_to_replace") %>%
body_replace_img_at_bkm("text_to_replace", value = external_img(src = img.file, width = .03, height = .03)) %>%
print(target = "yourpath/KeepAspectRatio.docx")
I tried this:
...
body_replace_img_at_bkm("text_to_replace", value = external_img(src = img.file)) %>%
...
This did not work. It shows the image with (i believe) the correct aspect ration but the image is not the desired size. I want to make it smaller without changing the aspect ratio.
Thank you very much in advance
p.s. I had to use width = .03, height = .03 in my case because the image was huge for some reason.
For png files I found the following solution/alternative:
library(png)
ImgZoom <- 3
temp <- readPNG("filepath.png",native = TRUE,info = TRUE)
body_replace_img_at_bkm("text_to_replace",
value = external_img(src = img.file
,width = ImgZoom*(attr(temp,"info")$dim[1]/attr(temp,"info")$dim[2])
,height = ImgZoom*(attr(temp,"info")$dim[2]/attr(temp,"info")$dim[2])))
But this is a small detour and does only work for png files so far.
I will also try this for other file types.
The best way would be an extra option in the function: body_replace_img_at_bkm().
Something like: body_replace_img_at_bkm(...,keep_aspect_ratio = TRUE)

Table and Figure cross-reference officer R

I would like to be able to cross-reference a table or figure in a word document using the officer R package.
I have come across these materials so far but they do not seem to have a solution:
https://davidgohel.github.io/officer/articles/word.html#table-and-image-captions
and a similar question
add caption to flextable in docx
In both of these I can only insert a caption as a level 2 header and not a true table caption.
What I want to be able to do in Word is Insert -> Cross-reference and go to Reference type: Table and see my caption there. Right now I can only see the caption under Numbered item.
Does this functionality exist in officer or anywhere else?
In word, the table numbers use the { SEQ \\# arabic } pattern, but references to them use { REF bookmark \h }. We can use this to make new code which can reference a SEQ field.
code:
ft <- regulartable(head(iris)) # create flextable
str <- paste0(' REF ft \\h ') # create string to be used as reference to future bookmark
doc <- read_docx() %>%
body_add_par('This is my caption' , style = 'Normal') %>% # add caption
slip_in_seqfield(str = "SEQ Table \\# arabic",
style = 'Default Paragraph Font',
pos = "before") %>% # add number for table
body_bookmark('ft') %>% # add bookmark on the number
slip_in_text("Table ",
style = 'Default Paragraph Font',
pos = "before") %>% # add the word 'table'
body_add_flextable(value = ft, align = 'left') %>% # add flextable
body_add_break() %>% # insert a break (optional)
slip_in_text('As you can see in Table',
style = 'Default Paragraph Font',
pos = 'after') %>% # add the text you want before the table reference
slip_in_seqfield(str = str,
style = 'Default Paragraph Font',
pos = 'after') %>% # add the reference to the table you just added
slip_in_text(', there are a lot of iris flowers.',
style = 'Default Paragraph Font',
pos = 'after') %>% # add the rest of the text
print('Iris_test.docx') # print
Hope this helps :)
Just for the record, you can do this a bit easier now by using some helper functions from the {crosstable} package.
Disclaimer: I am the developer of that package and these functions were highly inspired by #morgan121's answer. Thanks Morgan!
Here is an example:
library(officer)
library(crosstable)
library(ggplot2)
options(crosstable_units="cm")
ft = regulartable(head(iris))
my_plot = ggplot(data = iris ) +
geom_point(mapping = aes(Sepal.Length, Petal.Length))
doc = read_docx() %>%
body_add_title("Dataset iris", 1) %>%
body_add_normal("Table \\#ref(table_iris) displays the 6 first rows of the iris dataset.") %>%
body_add_flextable(ft) %>%
body_add_table_legend("Iris head", bookmark="table_iris") %>%
body_add_normal("Let's add a figure as well. You can see in Figure \\#ref(fig_iris) that sepal length is somehow correlated with petal length.") %>%
body_add_figure_legend("Relation between Petal length and Sepal length", bookmark="fig_iris") %>%
body_add_gg2(my_plot, w=14, h=10, scale=1.5)
print(doc , 'Iris_test.docx')
More info on https://danchaltiel.github.io/crosstable/articles/crosstable-report.html.
As with morgan121's code, you have to select all the text in MS Word and press F9 twice for the numbers to update properly.

Resources