45  Slides and presentations

A slide is read from across a room and sits on a coloured background, not white paper. Two things change from a manuscript figure. The type scales up the way a poster’s does, so it carries to the back of the room; and the theme has to match the deck: dark for a projected slide, light or transparent for a printed handout or a light template. hvtiPlotR ships a pair of PowerPoint themes for exactly this, plus save_ppt(), which drops the finished plot onto a slide as an editable vector graphic rather than a flat image.

This chapter is the how. The why, meaning what belongs on the slide around the figure, which colors hold up on a projector, and the evidence behind each rule, is in designing slides. Read that one first if you are planning a deck rather than finishing one figure.

45.1 A slide-styled plot

theme_hv_ppt_dark() styles a plot for a projected deck: white ink, no gridlines, and a large base size so the figure reads from the back of the room. It deliberately leaves the figure background transparent so the slide’s own background shows through, which is what you want on a real deck, but it makes the white axis text vanish on this white page. For the preview below we add a dark plot.background to stand in for the slide; on the actual deck, drop that line and let save_ppt() place the transparent figure on the slide.

p <- plot(hv_survival(sample_survival_data(n = 500, seed = 42)))
p + theme_hv_ppt_dark(base_size = 18) +
  theme(plot.background = element_rect(fill = "grey15", colour = NA))
Figure 45.1: Survival curve under theme_hv_ppt_dark() on a dark gray stand-in for the slide background. The salmon-red step line is the Kaplan-Meier estimate and the shaded band in the same color its confidence limits.

theme_hv_ppt_light() is the same typography on a light or transparent background, for a printed handout or a light template.

p + theme_hv_ppt_light(base_size = 18)
Figure 45.2: The same survival curve under theme_hv_ppt_light(). The salmon-red step line is the Kaplan-Meier estimate and the shaded band in the same color its confidence limits.

Both themes default to a 32 pt Arial base, sized for a slide projected across a room. We drop base_size to 18 here so the figure previews legibly on the page; leave it at the default for the real deck, and raise it further for a large hall.

45.2 One decorator for the whole deck

A theme cannot finish a grouped figure on its own. theme() governs the non-data ink, the text, panel, grid and ticks, and nothing that is drawn from the data. Add theme_hv_ppt_dark() to a four-group trends plot and you restyle the axes while leaving every series in ggplot2’s default hues. Series colour and shape live in the scales, so a finished slide figure is a theme call plus scale_colour_manual() plus scale_shape_manual().

Three lines is nothing once. On a twelve-figure deck it is thirty-six, and the cost that matters is one figure drifting out of step with the rest, usually the one you added last and looked at least.

hv_ppt_series() bundles the three into a single object. It returns a plain list holding the theme and both scales, and ggplot2’s + unrolls a list element by element, so the bundle composes exactly the way a theme does. Define it once near the top of the script, then add that same object to every plot in the deck.

dta_trends <- sample_trends_data(n = 600, seed = 42)
p_trends   <- plot(hv_trends(dta_trends))

ppt <- hv_ppt_series(mode = "dark", base_size = 18)

p_trends +
  labs(x = "Surgery Year", y = "Outcome") +
  ppt +
  theme(plot.background = element_rect(fill = "grey15", colour = NA))
Figure 45.3: Grouped trends plot styled with hv_ppt_series() in dark mode. Smooth lines and filled points mark each group: yellow circles, sky-blue triangles, orange squares and green diamonds for Groups I to IV.

mode names the background the figure will sit on, and it picks both the theme and the colour ordering: "dark" wraps theme_hv_ppt_dark(), "light" wraps theme_hv_ppt_light(). Anything else you pass goes through to the wrapped theme, which is how base_size = 18 got there above. Leave the 32 pt default in place for a real deck.

Two arguments cover the cases where the defaults are wrong. Pass colours when a deck calls for a particular set, and shapes when the default glyphs collide with something else on the slide.

p_trends +
  labs(x = "Surgery Year", y = "Outcome") +
  hv_ppt_series(
    mode      = "light",
    colours   = c("#0072B2", "#D55E00", "#009E73", "#CC79A7"),
    shapes    = c(16, 17, 15, 18),
    base_size = 18
  )
Figure 45.4: The same trends plot with hv_ppt_series() in light mode and custom colors. Smooth lines and filled points mark each group: blue circles, vermillion triangles, green squares and reddish-purple diamonds for Groups I to IV.

Four values here for four groups. The defaults carry six of each, which is the ceiling for a grouping variable before a discrete scale runs out of values and errors. Ask hv_ppt_palette() for a seventh colour and it stops rather than recycling, on the reasoning that two series sharing a hue is worse than a stopped script.

45.2.1 Why colour and shape both carry the group

Both scales map the same variable, so each series is told apart twice. On your monitor that looks like belt and braces. In the room it is not. A projector in a dim hall flattens colour differences that are perfectly clean on screen, and a black-and-white handout drops them altogether, but the point shapes survive both. This is the same redundancy the colour chapter argues for in a manuscript figure, and the case for it on a slide is stronger.

The shape scale still earns its keep with no legend drawn. Where two curves cross, the plotted summary points are what tells them apart.

45.2.2 Naming the series

A finished CORR figure carries no legend, on a slide or on a journal page. theme_hv_ppt_dark() and theme_hv_ppt_light() both set legend.position = "none", and hv_ppt_series() deliberately leaves that alone. You name the groups on the panel instead, in the theme’s own ink. Section 37.4 has that recipe and the reasoning behind the ink.

If a working draft does want a key, pass legend.position straight through and give both scales a shared title, so ggplot2 merges them into one legend instead of stacking two:

hv_ppt_series(mode = "dark", legend.position = "top", name = "Group")

45.3 Editable PowerPoint

A figure exported as a PNG is frozen: a reviewer who wants the title reworded or a colour swapped has to come back to you. save_ppt() instead writes the plot onto a slide as a native vector graphic, so its text, lines, and colours stay editable in PowerPoint itself. It anchors the plot in the panel rectangle of a house slide template, so every figure lands in the same place across a deck.

save_ppt(
  p + theme_hv_ppt_dark(),
  template   = "RD-dark.pptx",
  powerpoint = "survival_slide.pptx"
)

The chunk is eval: false because it writes a binary .pptx. The code is shown, not executed during the book build. Pair the dark theme with a dark template and the light theme with a light one, and keep theme_hv_ppt_dark() at its 32 pt default here, since the template is laid out for that size. Pass panel_box to move or resize the plot rectangle, and slide_titles to set the slide heading.

45.3.1 The same panel position on every slide

Flip between two slides of a deck and the eye catches any frame that jumps. It jumps when the placeholder is sized to the whole figure, because one figure’s axis labels are wider than the next one’s and the data region moves to make room. What you want fixed is the panel, the rectangle the data are drawn in. Everything outside it, the axis text and titles, should take whatever room it needs.

hv_ph_location() does that arithmetic. Give it a plot and the panel rectangle you want on the slide, and it returns the placeholder that puts the panel there. Here are the two figures from this chapter, asked for the same panel.

deck <- list(
  Survival = p + theme_hv_ppt_dark(),
  Trends   = p_trends + theme_hv_ppt_dark()
)

placeholders <- lapply(deck, function(plot) {
  hv_ph_location(
    plot,
    panel_width = 8.79, panel_height = 4.422,
    panel_left  = 2.67, panel_top    = 1.29
  )
})
round(do.call(rbind, lapply(placeholders, unlist)), 2)
         width height left  top
Survival 10.69   5.98 0.77 1.29
Trends   10.44   5.98 1.02 1.29

The four arguments are the panel’s width and height and its distance from the left and top edges of the slide, in inches unless you pass units. These are the values save_ppt() uses for its own panel_box default, sized for the 13.33 by 7.5 inch slide of the package’s default template.

Read the two rows against each other. height and top agree, and width and left do not. The survival figure’s y-axis labels are wider, so its placeholder starts further left and is wider by the same amount. Both placeholders end at the same right edge, and on both slides the panel’s left edge sits 2.67 inches in. The figures themselves depend on how the graphics device measures the fonts, so expect them to differ a little between machines and between this page and the PDF. A left that comes back negative means the labels do not fit beside a panel that far left: move the panel right or shorten the labels.

save_ppt() runs this for you, once per slide, whenever it is given a panel_box. Call hv_ph_location() yourself when you build the deck with officer directly, for example to put two plots on one slide.

# `deck` and `placeholders` come from the chunk above.
doc <- officer::read_pptx("RD-dark.pptx")

for (nm in names(deck)) {
  doc <- officer::add_slide(doc, layout = "Title and Content")
  doc <- officer::ph_with(
    doc, nm,
    location = officer::ph_location_type("title")
  )
  doc <- officer::ph_with(
    doc, rvg::dml(ggobj = deck[[nm]]),
    location = do.call(officer::ph_location, placeholders[[nm]])
  )
}

print(doc, target = "deck.pptx")

Each element of placeholders is a list of width, height, left and top, which is what officer::ph_location() takes, so do.call() passes it straight through. This chunk is eval: false for the same reason as the one above: it writes a .pptx.