Bill Denney
Bill Denney

Reputation: 835

How to Customize figure LaTeX with knitr/rmarkdown

I want to create a footer within the float for a figure created with ggplot2 in an rmarkdown document that is generating a .pdf file via LaTeX.

My question: Is there a way within rmarkdown/knitr to add more LaTeX commands within the figure environment?

Specifically, I'd like to find a way to insert custom LaTeX using either the floatrow or caption* macro as described in https://tex.stackexchange.com/questions/56529/note-below-figure within the figure environment.

When I looked at the chunk options (https://yihui.org/knitr/options/#plots), something like out.extra seems close to what I want, but that is used as an extra option to \includegraphics while I want access to put extra LaTeX within the figure environment, outside of any other LaTeX command.

Upvotes: 7

Views: 833

Answers (2)

Bill Denney
Bill Denney

Reputation: 835

@henrik_ibsen gave the answer that got me here. I made some modifications to the code that I ended up using to make it work a bit more simply:

hook_plot_tex_footer <- function(x, options) {  
  x_out <- knitr:::hook_plot_tex(x, options) 
  if(!is.null(options$fig.footer)) {
    inter <- sprintf("\\floatfoot{%s}\n\\end{figure}", options$fig.footer[1])
    x_out <- gsub(x=x_out, pattern="\n\\end{figure}", replacement=inter, fixed=TRUE)
  }
  x_out
}

knitr::knit_hooks$set(plot=hook_plot_tex_footer)

Overall, this does the same thing. But, it uses knitr:::hook_plot_tex() instead of defOut() so that if rerun in the same session, it will still work. And, since it's going into a \floatfoot specifically, I named the option fig.footer. But, those changes are minor and the credit for the answer definitely goes to @henrik_ibsen.

Upvotes: 2

henrik_ibsen
henrik_ibsen

Reputation: 813

The solution to your question is perhaps quite similar to this one. However, I believe yours is a bit more general, so I'll try to be a bit more general as well...

As far as I know, there's no simple solution to add extra LaTeX code within the figure environment. What you can do is update the knit (or output) hook (i.e. the LaTeX code output generated by the figure chunk).

The source code for the LaTeX figure output hook can be found here (hook_plot_tex). The output generated can be found starting at line 159. Here we can see how the output is structured and we're able to modify it before it reaches the latex engine.

However, we only want to modify it for relevant figure chunks, not all. This is where Martin Schmelzer's answer comes in handy. We can create a new chunk option which allows for control over when it is activated. As an example enabling the use of caption* and floatrow we can define the following knit hook

defOut <- knitr::knit_hooks$get("plot")  
knitr::knit_hooks$set(plot = function(x, options) {  
  #reuse the default knit_hook which will be updated further down
  x <- defOut(x, options) 
  #Make sure the modifications only take place when we enable the customplot option
  if(!is.null(options$customplot)) {  
   
    x  <- gsub("caption", "caption*", x) #(1)
    inter <- sprintf("\\floatfoot{%s}\\end{figure}", options$customplot[1]) #(2)
    x  <- gsub("\\end{figure}", inter, x, fixed=T)  #(3)
  }
  return(x)
})

What we're doing here is (1) replacing the \caption command with \caption*, (2) defining the custom floatfoot text input, (3) replacing \end{figure} with \floatfoot{custom text here}\end{figure} such that floatfoot is inside the figure environment.

As you can probably tell, sky's the limit for what you can add/replace in the figure environment. Just make sure it is added inside the environment and is in the apropriate location. See the example below how the chunk option is used to enable floatfoot and caption*. (You can also split the customplot option into e.g. starcaption and floatfoot by simply dividing up the !is.null(options$customplot) condition. This should allow for better control)

Working example:

---
header-includes:
  - \usepackage[capposition=top]{floatrow}
  - \usepackage{caption}
output: pdf_document
---


```{r, echo = F}
library(ggplot2)

defOut <- knitr::knit_hooks$get("plot")  
knitr::knit_hooks$set(plot = function(x, options) {  
  x <- defOut(x, options) 
  if(!is.null(options$customplot)) {  
   
    x  <- gsub("caption", "caption*", x)
    inter <- sprintf("\\floatfoot{%s}\\end{figure}", options$customplot[1])
    x  <- gsub("\\end{figure}", inter, x, fixed=T)  
  }
  return(x)
})
```

```{r echo = F, fig.cap = "Custom LaTeX hook chunk figure", fig.align="center", customplot = list("This is float text using floatfoot and floatrow")}
ggplot(data = iris, aes(x=Sepal.Length, y=Sepal.Width))+
  geom_point()
```

PS

The example above requires the fig.align option to be enabled. Should be fairly easy to fix, but I didn't have the time to look into it.

Upvotes: 4

Related Questions