1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215
|
#' Insert animations into a LaTeX document and compile it
#'
#' Record animation frames and insert them into a LaTeX document with the
#' \code{animate} package. Compile the document if an appropriate LaTeX command
#' is provided.
#'
#' This is actually a wrapper to generate a LaTeX document using R. The document
#' uses the LaTeX package called \code{animate} to insert animations into PDF's.
#' When we pass an R expression to this function, the expression will be
#' evaluated and recorded by a grahpics device (typically \code{\link{png}} and
#' \code{\link{pdf}}). At last, a LaTeX document will be created and compiled if
#' an appropriate LaTeX command is provided. And the final PDF output will be
#' opened with the PDF viewer set in \code{getOption('pdfviewer')} if
#' \code{ani.options('autobrowse') == TRUE}.
#' @param expr an expression to generate animations; use either the animation
#' functions (e.g. \code{brownian.motion()}) in this package or a custom
#' expression (e.g. \code{for(i in 1:10) plot(runif(10), ylim = 0:1)}).
#' @param nmax maximum number of animation frames (if missing and the graphics
#' device is a bitmap device, this number will be automatically calculated);
#' note that we do not have to specify \code{nmax} when using PDF devices.
#' @param img.name basename of file names of animation frames; see the Note
#' section for a possible adjustment on \code{img.name}
#' @param ani.opts options to control the behavior of the animation (passed to
#' the LaTeX macro \code{'\\animategraphics'}; default to be
#' \code{'controls,width=\\linewidth'})
#' @param centering logical: whether to center the graph using the LaTeX
#' environment \verb{\begin{center}} and \verb{\end{center}}
#' @param caption,label caption and label for the graphics in the figure
#' environment
#' @param pkg.opts global options for the \code{animate} package
#' @param documentclass LaTeX document class; if \code{NULL}, the output will
#' not be a complete LaTeX document (only the code to generate the PDF
#' animation will be printed in the console); default to be \code{article},
#' but we can also provide a complete statement like
#' \verb{\\documentclass[a5paper]{article}}
#' @param latex.filename file name of the LaTeX document; if an empty string
#' \code{''}, the LaTeX code will be printed in the console and hence not
#' compiled
#' @param pdflatex the command for pdfLaTeX (set to \code{NULL} to ignore the
#' compiling)
#' @param install.animate copy the LaTeX style files \file{animate.sty} and
#' \file{animfp.sty}? If you have not installed the LaTeX package
#' \code{animate}, it suffices just to copy these to files.
#' @param overwrite whether to overwrite the existing image frames
#' @param full.path whether to use the full path (\code{TRUE}) or relative path
#' (\code{FALSE}) for the animation frames; usually the relative path
#' suffices, but sometimes the images and the LaTeX document might not be in
#' the same directory, so \code{full.path = TRUE} could be useful; in the
#' latter case, remember that you should never use spaces in the filenames or
#' paths!
#' @param ... other arguments passed to the graphics device
#' \code{ani.options('ani.dev')}, e.g. \code{ani.height} and \code{ani.width}
#'
#' @return Invisible \code{NULL}
#' @note This function will detect if it was called in a Sweave environment --
#' if so, \code{img.name} will be automatically adjusted to
#' \code{prefix.string-label}, and the LaTeX output will not be a complete
#' document, but rather a single line like
#' \preformatted{\animategraphics[ani.opts]{1/interval}{img.name}{}{}}
#'
#' This automatic feature can be useful to Sweave users (but remember to set
#' the Sweave option \code{results=tex}). See \code{demo('Sweave_animation')}
#' for a complete example.
#'
#' PDF devices are recommended because of their high quality and usually they
#' are more friendly to LaTeX, but the size of PDF files is often much larger;
#' in this case, we may set the option \code{'qpdf'} or \code{'pdftk'} to
#' compress the PDF graphics output. To set the PDF device, use
#' \code{ani.options(ani.dev = 'pdf', ani.type = 'pdf')}
#'
#' So far animations created by the LaTeX package \pkg{animate} can only be
#' viewed with Acrobat Reader (Windows) or \command{acroread} (Linux). Other
#' PDF viewers may not support JavaScript (in fact the PDF animation is driven
#' by JavaScript). Linux users may need to install \command{acroread} and set
#' \code{options(pdfviewer = 'acroread')}.
#' @author Yihui Xie
#' @family utilities
#' @references Examples at \url{https://yihui.org/animation/example/savelatex/}
#'
#' To know more about the \code{animate} package, please refer to
#' \url{http://www.ctan.org/tex-archive/macros/latex/contrib/animate/}. There
#' are a lot of options can be set in \code{ani.opts} and \code{pkg.opts}.
#' @export
saveLatex = function(
expr, nmax, img.name = 'Rplot', ani.opts, centering = TRUE, caption = NULL,
label = NULL, pkg.opts = NULL, documentclass = 'article', latex.filename = 'animation.tex',
pdflatex = 'pdflatex', install.animate = TRUE, overwrite = TRUE, full.path = FALSE, ...
) {
oopt = ani.options(...)
if (!missing(nmax)) ani.options(nmax = nmax)
on.exit(ani.options(oopt))
file.ext = ani.options('ani.type')
use.dev = ani.options('use.dev')
## detect if I'm in a Sweave environment
in.sweave = FALSE
if ((n.parents <- length(sys.parents())) >= 3) {
for (i in seq_len(n.parents) - 1) {
if ('chunkopts' %in% ls(envir = sys.frame(i))) {
chunkopts = get('chunkopts', envir = sys.frame(i))
if (all(c('prefix.string', 'label') %in% names(chunkopts))) {
## yes, I'm in Sweave w.p. 95%
img.name = paste(chunkopts$prefix.string, chunkopts$label, sep = '-')
ani.options(img.fmt = paste(img.name, ani.options('imgnfmt'), '.', file.ext, sep = ''))
in.sweave = TRUE
break
}
}
}
}
interval = ani.options('interval')
## generate the image frames
ani.dev = ani.options('ani.dev')
num = ifelse(file.ext == 'pdf' && use.dev, '', ani.options('imgnfmt'))
img.fmt = sprintf('%s%s.%s', img.name, num, file.ext)
if (!in.sweave)
ani.options(img.fmt = img.fmt)
if (is.character(ani.dev))
ani.dev = get(ani.dev)
ani.files.len = length(
list.files(path = dirname(img.name), pattern =
sprintf('^%s[0-9]*\\.%s$', basename(img.name), file.ext))
)
if (overwrite || !ani.files.len) {
if (use.dev)
ani.dev(img.fmt, width = ani.options('ani.width'), height = ani.options('ani.height'))
expr
if (use.dev) dev.off()
## compress PDF files
if (file.ext == 'pdf') compress_pdf(img.name)
}
ani.files.len = length(
list.files(path = dirname(img.name), pattern = sprintf('^%s[0-9]*\\.%s$', basename(img.name), file.ext))
)
if (missing(nmax)) {
## count the number of images generated
start.num = ifelse(file.ext == 'pdf' && use.dev, '', 1)
end.num = ifelse(file.ext == 'pdf' && use.dev, '', ani.files.len)
} else {
## PDF animations should start from 0 to nmax-1
start.num = ifelse(file.ext == 'pdf' && use.dev, 0, 1)
end.num = ifelse(file.ext == 'pdf' && use.dev, nmax - 1, nmax)
}
if (missing(ani.opts)) ani.opts = 'controls,width=\\linewidth'
if (install.animate && !in.sweave && length(documentclass)) {
file.copy(system.file('misc', 'animate', 'animate.sty', package = 'animation'),
'animate.sty', overwrite = TRUE)
file.copy(system.file('misc', 'animate', 'animfp.sty', package = 'animation'),
'animfp.sty', overwrite = TRUE)
}
if (!in.sweave && length(documentclass)) {
if (img.name == sub('\\.tex$', '', latex.filename))
stop("'img.name' should not be the same with 'latex.filename'!")
if (!grepl('^\\\\documentclass', documentclass))
documentclass = sprintf('\\documentclass{%s}', documentclass)
cat(sprintf(
'%s
\\usepackage%s{animate}
\\begin{document}
\\begin{figure}
%s
\\animategraphics[%s]{%s}{%s}{%s}{%s}%s%s
%s
\\end{figure}
\\end{document}
', documentclass,
ifelse(is.null(pkg.opts), '', sprintf('[%s]', pkg.opts)),
ifelse(centering, '\\begin{center}', ''),
ani.opts,
1/interval,
ifelse(full.path, gsub('\\\\', '/', normalizePath(img.name)), img.name),
start.num, end.num,
ifelse(is.null(caption), '', sprintf('\\caption{%s}', caption)),
ifelse(is.null(label), '', sprintf('\\label{%s}', label)),
ifelse(centering, '\\end{center}', '')),
'\n', file = latex.filename
)
if ((latex.filename != '') & !is.null(pdflatex)) {
message('LaTeX document created at: ', file.path(getwd(), latex.filename))
if (system(sprintf('%s %s', pdflatex, latex.filename)) == 0) {
message(sprintf('successfully compiled: %s %s', pdflatex, latex.filename))
if (ani.options('autobrowse'))
system(
sprintf('%s %s', shQuote(normalizePath(getOption('pdfviewer'))),
sprintf('%s.pdf', sub('([^.]+)\\.[[:alnum:]]+$', '\\1', latex.filename)))
)
}
else {
message('An error occurred while compiling the LaTeX document; \nyou should probably take a look at the log file: ',
sprintf('%s.log', sub('([^.]+)\\.[[:alnum:]]+$',
'\\1', latex.filename)), ' under ', getwd())
if (Sys.info()['sysname'] == 'Darwin')
message("Mac OS users may also consider saveLatex(..., pdflatex = '/usr/texbin/pdflatex') if pdflatex is not in your PATH variable.")
}
}
} else {
cat(sprintf(
'%s
\\animategraphics[%s]{%s}{%s}{%s}{%s}
%s
',
ifelse(centering, '\\begin{center}', ''),
ani.opts,
1/interval,
ifelse(full.path, gsub('\\\\', '/', normalizePath(img.name)), img.name),
start.num, end.num,
ifelse(centering, '\\end{center}', ''))
)
}
invisible(NULL)
}
|