yihui / xaringan

Presentation Ninja 幻灯忍者 · 写轮眼
https://slides.yihui.org/xaringan/
Other
1.49k stars 281 forks source link

pdfcrop ignored by xaringan? #285

Closed giabaio closed 3 years ago

giabaio commented 3 years ago

As usual, I may be missing something obvious here - in which case, apologies in advance!

So: I've got a xaringan presentation, for which I've created my css file and I have a R file with the setup. This includes the code

knitr::knit_hooks$set(
  plot = function(x, options) {
    opts <- options$opts
    paste0(
      "<img src=",
      '"', x, '" ',
      if (!is.null(opts)) {
        # Here include the styling for the graphs - this ensure it's centered
        paste('style="display: block; margin: auto;"',
              # And this "glues" the options passed by the user (including the 'alt-text')
              glue::glue_collapse(glue::glue('{names(opts)}="{opts}"'),sep = " ")
        )
      },
      ">\n"
    )
  },
  # This sets up a switch that allows to automatically crop the images generated by R
  # https://bookdown.org/yihui/rmarkdown-cookbook/crop-plot.html
  knitr::knit_hooks$set(crop = knitr::hook_pdfcrop)
)

(I need the former to be able to add a 'title', which shows up as alt_text for a given image). Then I've added the pdfcrop option to be able to (as I understand it!) crop automatically the figures produced when compiling the R code.

The former option works, but the latter doesn't seem to - in other words, the resulting figures are not cropped. Is there some kind of idiosyncrasy between xaringan and pdfcrop? Or should this work straight out of the box and I'm messing it up? I am adding the option crop=TRUE in the chunk set up, eg:

{r,engine='tikz', echo=F, out.width="85%",crop = TRUE,opts=list(width="75%",title="INSERT TEXT HERE")}

(NB: yes. I intend to use this on a chunk that's executing a tikz picture. Could this also be a problem?)...

And I've also tried to add the option crop=TRUE inside the opts list, but to no avail...

Thanks for the help! Gianluca

cderv commented 3 years ago

Hi @giabaio ,

What tells you that cropping is not happening ? Do you have an error ? Is your file needed cropping ?

Without a reproducible example to run on our side it is difficult to know what is going on. Hooks are run by knitr, and xaringan will run knitr so I believe this will run any hook you define. Can you share an example for which you think this is not working as expected ?

Also did you compare with another format ? Is this something you observe is working with html_document but not with xaringan::moon_reader()

Also pdfcrop utility only apply on pdf files, and it needs to be available on the system, also with ghostscript - but I think you would have seen warnings if you don't have that.

Thanks you for the complement

giabaio commented 3 years ago

Thank you @cderv --- this is helpful. So: I know that cropping happens with other chunks producing images. So for instance, if I have set up this in a chunk at the beginning of my slides

# SET UP
# ... other commands

# This is a hack to insert 'alt-text' in images created by the R chunks
# source: https://ropensci.org/technotes/2020/04/23/rmd-learnings/
# NB: the 'alt-text' needs to be **'title'** attribute that gets included in the list 'opts'
# which is included as part of the set up in the chunk, for example
#    ```{r echo=FALSE,opts=list(title="Alt-text",width="45%"...)}
# (see: https://www.computerhope.com/issues/ch001076.htm). 
# Also, needs to specify the 'width' option (= what would normally be 'out.width') to ensure
# that the resulting HTML code to format the graph
knitr::knit_hooks$set(
  plot = function(x, options) {
    opts <- options$opts
    paste0(
      "<img src=",
      '"', x, '" ',
      if (!is.null(opts)) {
        # Here include the styling for the graphs - this ensure it's centered
        paste('style="display: block; margin: auto;"',
              # And this "glues" the options passed by the user (including the 'alt-text')
              glue::glue_collapse(glue::glue('{names(opts)}="{opts}"'),sep = " ")
        )
      },
      ">\n"
    )
  },
  # This sets up a switch that allows to automatically crop the images generated by R
  # https://bookdown.org/yihui/rmarkdown-cookbook/crop-plot.html
  knitr::knit_hooks$set(crop = knitr::hook_pdfcrop)
)

# Defaults for figure size
# fig.retina=3 improves the quality of the resulting graph (https://arm.rbind.io/slides/xaringan.html#89)
knitr::opts_chunk$set(fig.width=10, fig.height=9, out.width="55%", fig.align='center', fig.retina=3, crop=TRUE) 

a pure R code eg

{r echo=FALSE,dev="tikz",fig.width=7,fig.height=6,opts=list(width="60%",title="INCLUDE TEXT HERE")}
set.seed(14)
x=seq(0,50,.1)
m=8
s=5
n=dnorm(x,m,s)
g=dgamma(x,shape=m^2/s^2,rate=m/s^2)
ln=dlnorm(x,lognPar(m,s)$mulog,lognPar(m,s)$sigmalog)
rg=range(n,g,ln)
plot(x,n,bty="l",xlab="",ylab="Density",ylim=rg,t="l",lwd=2)
points(x,g,bty="l",col="red",t="l",lwd=2)
points(x,ln,bty="l",col="blue",t="l",lwd=2)
legend("topright",c(paste0("Normal$(\\mu,\\sigma)$: mean=",m,", sd=",s,", median=",m),
                    paste0("Gamma$(\\alpha,\\beta)$: mean=",m,", sd=",s,", median=",format(median(rgamma(10000,shape=m^2/s^2,rate=m/s^2)),
                                                                                           digits=3,nsmall=3)),
                    paste0("log-Normal$(\\eta,\\tau)$: mean=",m,", sd=",s,", median=",
                           format(median(rlnorm(10000,lognPar(m,s)$mulog,lognPar(m,s)$sigmalog)),digits=3,nsmall=3))),
       col=c("black","red","blue"),bty="n",lty=1,lwd=rep(2,3),cex=1.1
       )
mtext("$y$",1,line=2.5,cex=1.2)
text(20,.08,"$\\displaystyle\\alpha=\\frac{\\mu^2}{\\sigma^2},\\,\\beta=\\frac{\\mu}{\\sigma^2}$",cex=1.0,pos=4)
text(20,.068,"$\\displaystyle\\eta=\\log(\\mu)-\\frac{1}{2}\\log\\left(1+\\frac{\\sigma^2}{\\mu^2}\\right),\\,\\tau=\\log\\left(1+\\frac{\\sigma^2}{\\mu^2}\\right)$",cex=1.0,pos=4)

does crop the image. (I do have pdfcrop installed and I can check the resulting pdf and then png files with and without the command above and the image is indeed cropped if I turn the switch on).

The issue is when I run the following code on another slide

{r bayesian_hta1,engine='tikz', echo=F, out.width="85%",crop = TRUE,opts=list(width="75%",title="INSERT TEXT HERE"),eval=FALSE}

# This uses pure tikz to create an image. The margins are too large and I'd like to crop the pic so that it doesn't take up unnecessary space in the slide...

\newcommand\blue{\color{blue}}

\begin{tikzpicture}
\draw(-3,0) node[align=center,circle,draw=none,fill=none,font=\sffamily\fontsize{9}{10}\selectfont,minimum width=.2cm,minimum height=.1cm,color=white](1){$c_i$};
\draw(-3,1) node[align=center,circle,draw=none,fill=none,font=\sffamily\fontsize{9}{10}\selectfont,minimum width=.2cm,minimum height=.1cm,color=white](2){$\phi_{ic}$};
\draw(-4,1) node[align=center,circle,draw=none,fill=none,font=\sffamily\fontsize{9}{10}\selectfont,minimum width=.2cm,minimum height=.1cm,color=white](3){$\boldsymbol\tau_{c}$};
\draw(-3,2) node[align=center,circle,draw=none,fill=none,font=\sffamily\fontsize{9}{10}\selectfont,minimum width=.2cm,minimum height=.1cm,color=white](4){$\mu_c$};
\draw(-4,2) node[align=center,circle,draw=none,fill=none,font=\sffamily\fontsize{9}{10}\selectfont,minimum width=.2cm,minimum height=.1cm,color=white](5){$[\ldots]$};
\draw(-1,0) node[align=center,circle,draw=none,fill=none,font=\sffamily\fontsize{9}{10}\selectfont,minimum width=.2cm,minimum height=.1cm](6){$e_i$};
\draw(0,1) node[align=center,circle,draw=none,fill=none,font=\sffamily\fontsize{9}{10}\selectfont,minimum width=.2cm,minimum height=.1cm](7){$\phi_{ie}$};
\draw(-0,0) node[align=center,circle,draw=none,fill=none,font=\sffamily\fontsize{9}{10}\selectfont,minimum width=.2cm,minimum height=.1cm](8){$\boldsymbol\tau_{e}$};
\draw(-1,2) node[align=center,circle,draw=none,fill=none,font=\sffamily\fontsize{9}{10}\selectfont,minimum width=.2cm,minimum height=.1cm](9){$\mu_e$};
\draw(-0,2) node[align=center,circle,draw=none,fill=none,font=\sffamily\fontsize{9}{10}\selectfont,minimum width=.2cm,minimum height=.1cm](10){$[\ldots]$};
\draw(-2.25,2) node[align=center,circle,draw=none,fill=none,font=\sffamily\fontsize{9}{10}\selectfont,minimum width=.2cm,minimum height=.1cm,color=white](11){$\beta_1$};

\draw [->,>=latex,shorten >=-2pt,shorten <=-4pt,auto,node distance=0pt,thin] (7.230) -- (6.60);
\draw [->,>=latex,shorten >=-2pt,auto,shorten <=-4pt,node distance=.0pt,thin] (8.west) -- (6.east);
\draw [->,>=latex,shorten >=-2pt,shorten <=-4pt,auto,node distance=.0pt,thin] (9.320) -- (7.130);
\draw [->,>=latex,shorten >=-4pt,shorten <=-6pt,auto,node distance=.0pt,thin,dotted] (10.south) -- (7.north);

\draw[rounded corners=15pt,dashed,thick,color=blue] (-1.7,-.55) rectangle ++ (2.35,3.1) node[xshift=-1.1cm, yshift=0.15cm]{\fontsize{6}{7}\selectfont \sffamily Marginal model for $e$};

\draw(2.2,2) node[align=center,circle,draw=none,fill=none,font=\sffamily\fontsize{7}{8}\selectfont,minimum width=.2cm,minimum height=.1cm](11){$\blue e_{i} \sim p(e\mid \phi_{ei},\boldsymbol\tau_e)$};
\draw(2.2,1.6) node[align=center,circle,draw=none,fill=none,font=\sffamily\fontsize{7}{8}\selectfont,minimum width=.2cm,minimum height=.1cm](11){$\blue g_e(\phi_{ei}) = \alpha_0 \, [+\ldots]$};
\draw(2.2,1.2) node[align=center,circle,draw=none,fill=none,font=\sffamily\fontsize{7}{8}\selectfont,minimum width=.2cm,minimum height=.1cm](11){$\blue \mu_e = g_e^{-1}(\alpha_0)$};

\draw(-7.45,2) node[align=center,circle,draw=none,fill=none,font=\sffamily\fontsize{7}{8}\selectfont,minimum width=.2cm,minimum height=.1cm,color=white](11){$c_{i} \sim p(c\mid e,\phi_{ci},\boldsymbol\tau_c)$};
\draw(-6.63,1.6) node[align=center,circle,draw=none,fill=none,font=\sffamily\fontsize{7}{8}\selectfont,minimum width=.2cm,minimum height=.1cm,color=white](11){$g_c(\phi_{ci}) = \beta_0 + \beta_1(e_{i}-\mu_e)\, [+\ldots]$};
\draw(-7.8,1.2) node[align=center,circle,draw=none,fill=none,font=\sffamily\fontsize{7}{8}\selectfont,minimum width=.2cm,minimum height=.1cm,color=white](11){$\mu_c=g_c^{-1}(\beta_0)$};

\draw(2.1,.5) node[align=left,circle,draw=none,fill=none,font=\sffamily\fontsize{7}{8}\selectfont,minimum width=.2cm,minimum height=.1cm](11){\blue $\phi_{ei}=$ location};
\draw(2.1,.25) node[align=left,circle,draw=none,fill=none,font=\sffamily\fontsize{7}{8}\selectfont,minimum width=.2cm,minimum height=.1cm](11){\blue $\boldsymbol\tau_{e}=$ ancillary};

\draw(-7.3,.5) node[align=left,circle,draw=none,fill=none,font=\sffamily\fontsize{7}{8}\selectfont,minimum width=.2cm,minimum height=.1cm,color=white](11){$\phi_{ci}=$ location};
\draw(-7.3,.25) node[align=left,circle,draw=none,fill=none,font=\sffamily\fontsize{7}{8}\selectfont,minimum width=.2cm,minimum height=.1cm,color=white](11){$\boldsymbol\tau_c=$ ancillary};

\end{tikzpicture}

Now the code works fine and it makes up the pdf and then png file with the desired tikz image. But in this case, the cropping does not happen --- the image still has a large margin, especially at the top. Incidentally, you can crop this (which I did manually with pdfcrop on the resulting pdf file) and then also crop the png (a simple line of mogrify can do that of course). But for some reason (which may well be due to the fact that I don't fully know what I'm doing under the rmarkdown/xaringan hood...) it fails to do so automatically.

Anyway --- I don't think it's a big deal: in reality, I think, while not very elegant, it's probably more efficient to create the image once (using the tikz graph and outputting on a pdf/png), manually (well, I can automate this with a script...) cropping the two images (pdf and png) and then simply include the graph in the slides (rather than having R process the tikz code every time I recompile the slides)... But I thought I'd mention this as it may be an interesting problem?

Does this make sense?

Thanks for your help, Gianluca

cderv commented 3 years ago

Thanks for the explanation.

In fact, I don't think this is a xaringan issue specifically. I am just not sure you can use this knitr::hook_pdfcrop with 'tikz' engine currently (on any non R engine maybe).

This is a knitr behavior currently : When running the example using engine = 'tikz', plot paths are not registered so the hook won't have access to the plot file path to run pdfcrop on. This would be a feature request in knitr if that is possible to do (I need to look deeper)

You can try in a .Rmd file to knit, with rmarkdown and html_document, you'll observe the same behavior.

Thanks for the report.

cderv commented 3 years ago

I opened an issue in knitr, and so will close this one. Thanks!

giabaio commented 3 years ago

Thank you! This is very helpful!