attr.*
: attributes for output elementscache
: speed up the computingcap.pos
: caption positionchild
: child documentscode
and file
: alternative ways to provide codecollapse
: collapse source code and text outputcomment
: comment out text outputdev
: graphics deviceecho
: visibility of source codeerror
: error behavioreval
: code evaluationfig.*
: figure optionsinclude
: visibility of whole code chunklabel
: chunk labelmessage
: message behaviororder
: order of executionprint
: printing functionpurl
: code extractionref.label
: chunk referencesresults
: text output behaviorstrip.white
: leading / trailing blank lines in codetab.*
: table optionstime
: code timingverbose
: printing verbositywarning
: warning behaviorwd
: working directoryEdit this chapter on GitHub
The litedown package is still new and experimental. The documentation is only about 50% complete at the moment. Besides, litedown was designed for minimalists with a limited scope. Average users should perhaps consider using rmarkdown or Quarto instead.
If you do wish to try litedown, please install from r-universe:
install.packages(c('litedown', 'xfun'), repos = 'https://yihui.r-universe.dev')
You may say I’m a dreamer
But I’m not the only one
I hope someday you’ll join us
And the world will live as one—John Lennon, Imagine
Imagine there’s no PDF. It’s easy if you try. No Word below us. Above us, only HTML.
I do not mean PDF and Word are bad. I only lament the time that human beings spend on various document formats, given how much a static web page can do.
Having worked on the development of R Markdown for nearly 12 years, I have to confess that I have become a little perplexed about my work, although I clearly remember the exciting moments. For example:
The moment when I added syntax highlighting to R code in .Rnw
documents.
Beautiful!
The moment when I managed to generate a base R plot and a ggplot in the same
code chunk and place them side by side in LaTeX. Magic! Not to mention that
ggplot does not require an explicit print()
call in a code chunk—you
probably do not even know what that means now.
The moment when I saw the impossible-to-read diagram that shows all possible conversions among numerous document formats on the Pandoc homepage. Incredible!
The moment when I discovered DZSlides in Pandoc. Wow! So PowerPoint and LaTeX beamer were not the only choices for slides.
The moment when some rstudio::conf attendees started applauding after I told them that the PowerPoint support had been added to the development version of rmarkdown when one person asked about it.
The moment when I came across remark.js. And tufte.css. And GitBook. And Hugo. And distill.pub. And htmlwidgets, reticulate, and so on.
…
I think the work was largely meaningful. The only problem is I do not see an end. The list of exciting things to do goes on and on.
“At forty, I had no more doubts.” Confucius said. Apparently, I’m not Confucius. On the contrary, as I’m turning forty, I’m having more doubts. I have been reflecting on the things that have kept me busy.
If I were to summarize the 700+ episodes of Naruto in one sentence, I would use the question that Gaara (the Fifth Kazekage) asked Onoki (the Third Tsuchikage):
When did you all forsake yourselves?
In my opinion, this question was the most critical turning point in the 700+ episodes, reminding the 79-year-old Onoki and the rest of people of their original dreams. The shinobi world existed to put an end to wars, but it turned out to bring even more and larger-scale wars.
When did we forsake simplicity? We create software to simplify things, but software often ends up in feature wars.
Markdown was originally invented for simplicity. That is, to make it easier to write HTML. I used to take out my wallet and tell people that if they are unable to learn the basics of Markdown in 5 minutes, I’d award them 20 dollars. I also used to say “In HTML I Trust”, which sounds like a joke, but I really love HTML and web technologies. Again, I do not mean other document formats are bad at all. It is just that I feel HTML has the greatest potential, and I hope to take full advantage of its power.1
In other words, I do not expect that “the (software) world will live as one”. I just want to make the HTML world a little bit better.
The litedown package is an attempt to reimagine R Markdown with one primary goal—do HTML, and do it well, with the minimalism principle. Before LaTeX fans walk away in disappointment, let me quickly clarify that LaTeX output is also supported, but please do not expect anything fancy before you learn to customize LaTeX templates. Most other output formats are not supported. No Word, RTF, PowerPoint, or EPUB.
R Markdown is rendered via litedown::fuse()
, which is similar to
rmarkdown::render()
and knitr::knit()
. Markdown is rendered via
litedown::mark()
, which uses commonmark instead of Pandoc.
The commonmark package follows the GFM (GitHub Flavored Markdown) spec, which can be seen as a subset of Pandoc’s Markdown. Therefore the litedown package can be viewed as a small subset of the existing R Markdown ecosystem (the latter is based on Pandoc). It aims at simplicity, lightweight, and speed, at the cost of giving up some advanced features. This package is intended for minimalists. Most users may prefer using tools based on Pandoc instead, such as rmarkdown or Quarto, which offer richer features.
Is litedown really simple? From the developer’s perspective, yes, it is, largely due to the limited scope of the package. From the user’s perspective, some features are definitely not that simple. However, the point is that the core is simple and small, and you can enable or disable most features. What’s more, you can implement features by yourself if you know CSS/JS.
You can view litedown as a minimal re-implementation (1) of some core packages in the existing R Markdown ecosystem (2), such as rmarkdown for reports, xaringan for slides, bookdown for books, blogdown for websites, pkgdown for R package sites, and pagedown for paged HTML documents.
$$\mathrm{litedown} = \min{\{R\}} + \{D_i\} - \{D_e\} + \{J\}$$
\(R\) = knitr + evaluate + rmarkdown + bookdown + blogdown + pagedown + pkgdown + xaringan + tufte + distill
\(D_i\) = (internal dependencies) commonmark + xfun
\(D_e\) = (external dependencies) Pandoc + Bootstrap + jQuery + GitBook + Hugo + paged.js + remark.js + tufte.css + distill.js/.css + …
\(J\) = Lightweight vanilla JS/CSS
classDiagram class litedown { +HTML/LaTeX +min [ * ] R-dep (xfun, commonmark) web-dep (lite.js) } class rmarkdown { +[ * ] } class bookdown { +[ * ] } class blogdown { +[ * ] } class pagedown { +[ * ] } class pkgdown { +[ * ] } class xaringan { +[ * ] } class tufte { +[ * ] } class distill { +[ * ] } litedown *-- rmarkdown litedown *-- bookdown litedown *-- blogdown litedown *-- pagedown litedown *-- pkgdown litedown *-- xaringan litedown *-- tufte litedown *-- distill classDef default fill:none style litedown fill:lightcyan
classDiagram direction BT class rmarkdown { +HTML/LaTeX +RTF/Word/PowerPoint/EPub/... R-dep (knitr, evaluate, ... 25) sys-dep (Pandoc) web-dep (Bootstrap/jQuery/...) } class bookdown { +Books R-dep (26) web-dep (GitBook...) } bookdown --|> rmarkdown class blogdown { +Websites R-dep (33) sys-dep (Hugo) } blogdown --|> bookdown class pagedown { +Paged HTML R-dep (38) web-dep (paged.js) } pagedown --|> bookdown class distill { +Grid layout R-dep (48) web-dep (distill) } distill --|> bookdown class pkgdown { +Package sites R-dep (52) web-dep (Bootstrap...) } pkgdown --|> rmarkdown class xaringan { +HTML slides R-dep (32) web-dep (remark.js) } xaringan --|> rmarkdown class tufte { +Two-column layout R-dep (26) web-dep (tufte.css) } tufte --|> rmarkdown classDef default fill:none style rmarkdown fill:lightskyblue
It is absolutely not the goal for litedown to become a substitute of tools based on knitr and Pandoc, such as rmarkdown and Quarto. If you are not sure if you should choose litedown or rmarkdown/Quarto, you may want to choose the latter (especially Quarto).
Output formats besides HTML and LaTeX are unlikely to be supported.2 If other output formats are desired, you may use Pandoc to do the conversion.
For tables, only pipe tables are supported. Other table formats are not recognized.
At the moment, litedown mainly supports R as the computing language. Other languages might be added in the future, but please keep your expectation low, because the support is unlikely to be as good as R. Back in 2013, I experimented with a simple idea to run program code in other languages, and the outcome was the runr package. However, I forsook simplicity after other sophisticated packages emerged, such as reticulate and JuliaCall. I may revisit the idea in the future.
HTML widgets are not supported yet, but may be reimagined in the future with some minimal support. Meanwhile, you are free to add arbitrary JS libraries to your documents (5.17), so it is entirely possible that you can create HTML widgets on your own.
Almost everything in litedown was written from scratch. The package is very lightweight, with only two R package dependencies: commonmark and xfun.
It is a deliberate design choice to keep this package lightweight, to make it
relatively easy to use and simple to maintain. The functions mark()
and
fuse()
can be viewed as significantly trimmed-down versions of Pandoc and
knitr, respectively.
Just to give you a better idea about the “lightweight” (the numbers below refer to uncompressed software and source code as of today):
As the Markdown converter, Pandoc 3.5 is about 180Mb, and commonmark is about 1Mb on my macOS.
Bootstrap’s CSS is about 12,000 lines (plus 1500 lines of JS), and litedown’s base CSS is about 100 lines (no JS).
For paginating HTML documents, paged.js has 33,000+ lines of code, and my naive implementation, pages.js, has about 400 lines (200 lines of JS and 200 lines of CSS).
Admittedly, pursuing lightweight requires sacrifice in features and quality (e.g., my pages.js is far less sophisticated than paged.js), but overall I feel the trade-off should be reasonable to those who prefer minimalism. With a 2Mb footprint (the total file size of litedown, commonmark, and xfun), you can:
Write basic components of Pandoc’s Markdown (3), including headings, table of contents (TOC), lists, figures, pipe tables, code blocks, quotes, fenced Divs, LaTeX math, footnotes, and citations, etc.
Automatically number and cross-reference arbitrary elements (3.3.8), including but not limited to headings, figures, tables, and equations.
Perform computing with code chunks and inline code expressions (2).
Create single-file reports/slides, or multiple-file books/websites (7).
If you load additional JS/CSS assets, you can create more types of elements, such as folded code blocks, tabsets, side elements, sticky TOC, and callout blocks (5).
To get started, run litedown::roam()
and it will launch a file browser to let
you preview everything that can be rendered to HTML, such as .md
, .Rmd
, and
.R
files (6.1).
The rendering takes place in memory, which means it will not render .html
files on your disk, unless you request so. The page will be automatically
updated as you edit and save a file. This update-on-save feature can be turned
off, and then you can manually refresh the page to re-render the file whenever
you want.
You can open any file in your editor or the default system application by clicking a button in the browser. You can also render a file or a project in a new R session by clicking a button in the browser.
The R Markdown parser stores the precise line and column numbers of code
elements. The location information is used in various places. For example, when
an error occurs, you will get a message that tells you the precise location in
the source. In editors that support ANSI links (such as the RStudio IDE), you
can even click on the message to go to a specific line/column in the source
document, so you can quickly and easily know where the error occurred exactly.
When previewing .Rmd
documents with roam()
, you will see line numbers on the
left side of code blocks. Clicking on these numbers will bring you to the lines
in the source.
Due to the fact that the parser is based on commonmark (instead of solely on regular expressions like knitr’s parser), it can precisely recognize code chunks and inline code, which means code within other verbatim code blocks or comments will be untouched. For example:
````md
Below is not a code chunk but verbatim content
inside a fenced code block (with four backticks).
```{r}
1 + 1
```
Inline code expressions like `{r} 1+1` are not
parsed, either.
````
<!--
Feel free to comment out anything, such as a code chunk:
```{r}
1 + 1
```
-->
By default, litedown produces bare minimal HTML output, but many features can be enabled by adding CSS/JS assets (5). You can freely choose whatever versions that work for you to avoid potential future breakage when the assets are upgraded. The assets are not bundled in the litedown package, but hosted on CDN, so updating litedown will not update your assets.
The CSS and JS code for commonly used features do not depend on any frameworks such as Bootstrap or jQuery. They are simply vanilla code written from scratch. No generator was used (such as SCSS). The code is often relatively short, so you could just fork the code, modify it, publish your own version, and use it if you are not satisfied with the original version.
Chunk options are managed by an environment, i.e., litedown::reactor()
(2.1.2). Using an environment (as compared to a list like knitr’s
opts_chunk
) means you can access the up-to-date chunk options anywhere,
because environments in R are mutable. I cannot explain how awkward knitr’s
opts_current
has been. It is basically a lie—chunk options that you get from
opts_current
are not necessarily “current”.
A document does not have to be compiled in the linear order. With the chunk
option order
, you can specify code chunks and inline code expressions to be
executed in an arbitrary order (2.1.5.16). For example, if you have an
abstract in the beginning of a document, and the abstract needs to use a number
calculated at the end of the document, you can let the abstract be compiled in
the end, although it appears earlier in the document.
I guess some users may want to kill me upon knowing this feature, and some may send me flowers (although I’m not sure if they want to thank me or prepare the flowers for my funeral). For those who want to kill me, please note that this feature does not mean litedown is as awful as Jupyter notebooks. It means you can specify a fixed order to execute code in the document. The order does not have to be from beginning to end, but it is deterministic. In other words, it does not mean you run code chunks in an arbitrary or random order that can detriment reproducibility.
If you want to figure out which code chunks are time-consuming, simply set the
chunk option litedown::reactor(time = TRUE)
(2.1.5.23) in the
beginning, and put litedown::timing_data()
in the last code chunk. It will
tell you detailed information. In the roam()
preview, the data will also
contain links to specific lines of code chunks, so you click to jump to a
specific code chunk.
Rectangular objects such as data frames (including tibbles) and matrices are printed as tables by default (2.1.5.22), and the number of rows is limited to 10 by default to avoid generating huge tables by accident.
File paths (such as image paths) have been a mess in knitr. My deepest apologies for that. I have worked much harder in litedown in this regard.
Functions such as mark()
, fuse()
, and fuse_book()
can operate in memory
without writing to disk. By default, if you pass a file input, you get a file
output; if you pass text input, you get text output.
You can feel more confident with using the chunk option cache = TRUE
in
litedown than in knitr. The new cache system is more robust and
intelligent (2.1.5.2).
Unlike bookdown and Quarto, litedown::fuse_book()
renders multiple input
files to a single output file. Yes, your whole precious book is in a single
(HTML or PDF) file. Grab and go.
The assumption of single-file output for books has made several things a lot
easier. For example, if you want to search within the book, just press
Ctrl + F
or Command + F
in your browser as you usually do on any other web
page. You do not need a client-side search library. It is also quicker to jump
between chapters because they are on the same page. If you want to print the
HTML version of the book to PDF, just press Ctrl + P
or Command + P
.
I know you have a concern: wouldn’t the single HTML file be too heavy for the browser? The answer is: you should be fine if you do not have too many images. If you do, do not base64 encode them (which is the default), and you can also lazy-load images to make the book load faster.
An R script has the same status as an R Markdown document (2.3). You
can write Markdown in #'
comments, and chunk options in #|
comments (both
are optional). These are the only two rules to remember. Compared to
knitr::spin()
, the rules are much simpler. Besides, R scripts are also
carefully parsed into “code chunks”, so their line numbers work as well as R
Markdown’s line numbers.
Any application that you can create with R Markdown can be created with R scripts, such as books and websites.
The HTML output generated from litedown is very clean. For example, code
blocks in HTML output contain plain code, instead of full of <span>
tags with
random attributes. Clean HTML output means the output file size is smaller, and
more importantly, it is easier to inspect the differences between two versions
of output (e.g., in Git). Every time when you update the source document, you
can know more clearly what has changed in the output, which can help you avoid
unexpected changes before publishing the output.
Edit this chapter on GitHub
Things are in the saddle,
And ride mankind.—Ralph Waldo Emerson, Ode, Inscribed to William H. Channing
R Markdown documents need to be compiled to Markdown before being rendered to a target output format such as HTML and LaTeX.
For those who are familiar with R Markdown, you can think of the function
litedown::fuse()
as the new knitr::knit()
, litedown::fiss()
as the new
knitr::purl()
, and litedown::mark()
as the new Pandoc. 3 shows
the conversion process from R Markdown to a target format.
flowchart LR A@{ shape: doc, label: "*.Rmd" }-. <code>fuse()</code> .-o F@{ shape: bolt } F o--- B@{ shape: doc, label: "*.md" } B-. <code>mark()</code> .-o G@{ shape: framed-circle } G --> C@{ shape: doc, label: "*.html" } G --> D@{ shape: doc, label: "*.tex" } G --> E@{ shape: doc, label: "..." } A-. <code>fiss()</code> .-o H@{ shape: cross-circ } --> I@{ shape: lin-doc, label: "*.R" }
The function fuse()
“fuses” program code with narratives, i.e., it executes
all code in the source document and interweaves results with narratives in the
output document; fiss()
splits the document and extracts the code. Similar to
knitr, litedown supports code chunks (2.1) and inline code
(2.2).
We will first introduce fuse()
in 2, and then mark()
in 3
and 4.
If you do not do computing in Markdown, you do not need to learn
fuse()
—mark()
will be enough.
roam()
Before entering the litedown world, I suggest you forget the Knit
button
if it has become your muscle memory to click on that button to view results
after writing every single line of code. The recommended way to work with
litedown is litedown::roam()
, which allows you to preview and render
documents through a web interface.
To help you bid goodbye to the Knit
button, please take 5 minutes to practice
the following mouse-clicking exercise (but try not to be addicted):
If the Knit
button still sticks to your mind after you have practiced for a
few hundred times, I’d like to thank you for your loyalty to knitr, and
please see 6.3 for how to continue to use the Knit
button with
litedown.
You can learn more about the Render
and Run
buttons in 6.1.
Basically, the Render
button renders a document to an output file, whereas the
Run
button renders a document in memory without writing to disk.
Unless you use litedown programmatically (i.e., in scripts), you should
rarely need to call fuse()
or mark()
directly—litedown::roam()
will call
fuse()
and mark()
for you behind the scenes.
To understand what fuse()
does, we look at a minimal example:
---
title: The area of a circle
---
Define the radius of a circle as `x`:
```{r}
x = 1 + 1
```
The area of the circle with a radius of `{r} x` is `{r} pi * x^2`.
---
title: The area of a circle
---
Define the radius of a circle as `x`:
``` {.r}
x = 1 + 1
```
The area of the circle with a radius of 2 is 12.6.
When we fuse()
the source document, the program code will be executed, writing
results to the output.
You can see that the value 2
was assigned to the object x
in a code chunk,
and the inline R expression pi * x^2
was also evaluated to a number.
The advantage of interweaving computing code with text is that you can freely
update the source code according to the purpose of computing, and you will get
the up-to-date results after rebuilding the document. For example, you can
change x
to 3
, and the area of the circle will be automatically updated
after you re-run fuse()
. You do not need to copy and paste computational
outcome manually from one place to another.
A computational document contains both text narratives and code for computing. 4 shows the basic components of a document and how they work together.
flowchart TD O@{ shape: lin-doc, label: "Input document" }--"crack()"--> A@{ shape: f-circ } -.-o B[[YAML]] A -.-o C[[Text chunk]] C -.- C1>Text] --- C12{ } --> H[[Text output]] C -.- C2>Inline code]--> F{Value} --> H C -.- C3>More...] --- C32{ } --> H A -.-o D[[Code chunk]] D -.- D1>Chunk options] D -.- D2>Code]--> G{Results} --> I[[Chunk output]] A -.-o E[[More chunks...]] -.- K>...] --> L{...} --> M[[More output...]] -.-> J H -.-> J@{ shape: doc, label: "Markdown" } I -.-> J B -.-> P>...] --> Q{...} --> R[[YAML]] -.-> J J--"mark()" --> N@{ shape: tag-doc, label: "Output document" } subgraph "fuse()" C1 C2 C3 D1 D2 K P end class O,J,N document classDef document stroke-width:3px class C12,F,C32,G,L,Q fuse-out classDef fuse-out fill:lightyellow class H,I,J,M,N,R output classDef output fill:none
First, the input is split into three types of blocks: the YAML metadata, text chunks, and code chunks.
The YAML metadata contains information such as the title, author, date, and other configurations for the document.
A text chunk could consist entirely of plain-text narratives that do not require computing, or contain both text fragments and inline code expressions. In the latter case, the inline code can be executed to return its value to the output.
A code chunk contains code that often does more sophisticated computing than inline code. Optionally, a code chunk can contain chunk options to control the behavior of the computing and output.
After all code has been executed, the output blocks will be merged to a Markdown document, which can be further rendered to a target output format such as HTML and LaTeX.
Take the following document for example:
---
title: "A Report on the Wall"
author: "`{r} who`"
date: "`{r} Sys.Date()`"
---
```{r, order = 0}
who = 'Humpty Dumpty'
```
`{r} who` sat on a wall.
`{r} who` had a great fall.
```{r}
broken = strsplit(who, ' ') |> unlist()
```
All the king's horses and all the king's men
Couldn't put `{r} broken[1]` together again.
<!--
If the king's horses and men knew R, they could've put
Humpty together again by paste(broken, collapse = ' ').
-->
The YAML metadata is provided between ---
and ---
in the beginning. Note
that it can also contain code to be evaluated, such as the variable who
and
the function call Sys.Date()
.
The document has two code chunks in triple-backtick (```
) fences. The
first code chunk contains a chunk option order = 0
(you may see
2.1.5.16 for its meaning later). You can do any computing in code
chunks and generate text results or graphics.
There are two text chunks: one between the two code chunks, and one after the
second code chunk. These text chunks contain inline R code expressions in
`{r} `
. An inline code expression is typically used to get a single value.
In the end, there is a comment in <!-- -->
. Comments will not be displayed in
the output document.
When I started writing the litedown package, I was thinking of nuclear
fusion reactions (although my knowledge in physics is quite limited): text and
code chunks are like atomic nuclei, and they can be combined to form a report.
The process is controlled by a reactor. That was how I came up with the function
names fuse()
and reactor()
.
To prepare the atomic nuclei, I need to crack()
the source document. When the
input is not a document but a script, I will sieve()
out the elements from
comments. The fusion reaction can take place after crack()
or sieve()
is
done.
Maybe the neurons in my brain are wired in an odd way, but I feel mysteriously
happy that the names crack()
and sieve()
weakly resembles my friend Carson
Sievert’s name. For that reason, Carson will live in my heart forever, even if
he would never send a pull request to litedown.
Since there also exist fission reactions, the function fiss()
is meant to
decompose a document.
As for the package name “litedown”, it has three meanings:
The obvious meaning is the lightweight of the package.
The less obvious meaning is that light comes down from the fusion reactor
like the Sun. This meaning is represented by the symbol for fuse()
, which
is a zigzag arrow (↯).
Finally, it is a play on the phrase “lie down”. I intended to write this package as my last “down” package. After it is done, I wish to lie down.
Let there be light.
Edit this chapter on GitHub
If you give someone a program, you will frustrate them for a day; if you teach them how to program, you will frustrate them for a lifetime.
—David Leinweber
In this chapter, we introduce how to manage computing in computational documents. The computing may be done in code chunks, inline code expressions, script files, and in R or other languages.
A code chunk is a fenced code block that consists of: 1) the language (or engine) name, 2) chunk options that control the behavior of computing and output, and 3) the program code. It is of the following form:
```{lang}
#| chunk options
code
```
Whitespaces are allowed between the opening ```
and {
. The language
name should begin with only alphanumeric characters (a-z
, A-Z
, 0-9
) and
underscores (_
). If any other character is used or there are no curly braces
in the chunk header, the chunk will be treated as a normal fenced code block in
Markdown, instead of a code chunk for computing. Below are some examples of
normal code blocks:
```r
# lack of {} in the header
```
``` {.r, echo = FALSE}
# the language name should not start with "."
```
In this book, we use the term “code block” to refer to plain Markdown code blocks, and “code chunk” to refer to executable code chunks.
The chunk options can be provided in the chunk header after the language name,
or in the chunk body as pipe comments (#|
).
Chunk options in the chunk header must be provided in the comma-separated
tag-value pairs, except for the chunk label, of which the tag label
can be
omitted.
Chunk options in the chunk body must be provided in #|
comments in the
beginning. They can use either the comma-separated or YAML syntax. When
using the comma-separated syntax, you can wrap the comments freely.
For the comma-separated syntax, the option value can be provided via arbitrary R code as long as it is syntactically valid. The code will be evaluated when the value is needed (i.e., the evaluation is lazy).
Below are examples showing different ways to write chunk options:
Options in the chunk header (the chunk label is foo
, which can also be
written as label = "foo"
):
```{r, foo, echo = FALSE, fig.height = 6, fig.cap = "A scatterplot."}
plot(cars)
```
Comma-separated options in pipe comments:
```{r}
#| foo, echo = FALSE, fig.height = 6,
#| fig.cap = "A scatterplot."
plot(cars)
```
YAML options in pipe comments:
```{r}
#| label: "foo"
#| echo: false
#| fig.height: 6
#| fig.cap: "A scatterplot."
plot(cars)
```
You can use #|
for any language, although not all languages use #
as the
comment character. If you prefer, you can use language-specific comments and add
pipes after the comment characters to write chunk options, e.g., //|
for
JavaScript. You can find the supported comment characters for different
languages in xfun:::comment_chars
. Here are a few of them:
str(xfun:::comment_chars[c('css', 'fortran', 'js', 'r')])
#> List of 4
#> $ css : chr [1:2] "/*" "*/"
#> $ fortran: chr "!"
#> $ js : chr "//"
#> $ r : chr "#"
For languages that use opening and closing comment delimiters such as C and CSS, you do not need to repeat the comment pipe on every line, e.g.,
```{css}
/*| label = 'foo',
echo = FALSE */
p { color: red; }
```
In this book, I will write options in the chunk header when they are relatively short, and write them in pipe comments when they are too long to fit one line.
See 2.1.5 for the full list of possible chunk options.
All chunk options are managed internally via litedown::reactor()
. To get an
option value, call reactor("NAME")
, where NAME
is the option name (e.g.,
fig.width
). To set an option globally for all code chunks, call
reactor(NAME = VALUE)
.
If an option is set in both reactor()
and a code chunk (2.1.1),
the value in the chunk wins. For example, if you have set
litedown::reactor(echo = FALSE)
previously, and echo = TRUE
in a chunk
header, the chunk option echo
will be TRUE
for this chunk.
Alternatively, you can manipulate the value returned by reactor()
directly,
which is essentially an environment:
opts = litedown::reactor()
opts$fig.width # get an option
opts$echo = FALSE # set an option via assignment
You can call reactor()
anywhere to get or set chunk options. For example, you
can call it inside a code chunk. Please note that when you call it to change
global chunk options outside an R Markdown document, we recommend that you
restore the original options afterwards to limit the scope of the changes,
otherwise the changes may affect the behavior of other documents unexpectedly.
To restore options, you need to save the old option values, e.g.,
# reactor() returns old values after setting new values
old_opts = litedown::reactor(echo = FALSE, fig.height = 6)
# fuse a document with new global chunk options
litedown::fuse("foo.Rmd")
# restore old option values
litedown::reactor(old_opts)
This is unnecessary if you call reactor()
inside a document, since global
chunk options are always restored when fuse()
finishes compiling the document,
e.g.,
```{r, setup}
litedown::reactor(echo = FALSE)
```
```{r}
# echo = FALSE will be applied to this chunk
1 + 1
```
The chunk option echo
will be restored to TRUE
after fuse()
finishes this
document.
Code chunks inside other code blocks are not parsed or evaluated, which provides a way to write verbatim code chunks, e.g.,
````md
Some verbatim content.
```{r}
1 + 1
```
````
Similarly, code in HTML comments (<!-- -->
) will not be recognized, either,
e.g.,
<!--
Do not run this chunk:
```{r}
1 + 1
```
or the inline code `{r} pi`.
-->
If N + 1
pairs of curly braces are used in the opening fence, the chunk fences
(with N
pairs of curly braces) and chunk options will be shown in the output,
which can be useful for telling readers the full source of a chunk, e.g.,
```{{r}}
#| echo = TRUE, eval = FALSE
1 + 1
```
```{r}
#| echo = TRUE, eval = FALSE
1 + 1
```
All supported chunk options are listed alphabetically below.
attr.*
: attributes for output elementsThe attr.*
options can be used to customize different types of output
elements, such as source code blocks, messages, and plots, etc. To understand
these options, you need to learn the Markdown syntax in 3.3.5 and
3.3.7.
6 illustrates the structure of Markdown output from a code
chunk (the class names in the figure such as .chunk
are not names used in the
actual output but for illustration only).
:::: {.chunk}
``` {.source}
1:2 + 1:3
```
``` {.output}
#> [1] 2 4 4
```
``` {.warning}
#> longer object length is not a multiple of ...
```
``` {.source}
plot(cars)
```
![alt](*__files/*.png){.plot}
::::
The source code, text output, and messages (including warnings and errors) are
formatted as fenced code blocks. Plots are written in ![]()
. The whole chunk
can be wrapped in a fenced Div
(3.3.7). The attr.*
options will
add attributes to these fenced code blocks, plots, and fenced Div
s.
attr.asis
: When provided and the chunk option results = 'asis'
, text
output will be wrapped in a fenced Div
using attributes from this option.
By default, this option is empty, and the fenced Div
is not generated.
attr.chunk
: When provided, the whole chunk will be wrapped in a fenced
Div
with attributes from this option. By default, it is empty.
attr.error
: Attributes for error message blocks. The default is
.plain .error
(i.e., two class names plain
and error
will be added to
error blocks). The class name plain
is to avoid syntax highlighting.
attr.message
: Attributes for message blocks generated by message()
. The
default is .plain .message
.
attr.output
: Attributes for text output blocks. By default, it is empty.
attr.plot
: Attributes for plots. If a code chunk produces multiple plots,
you may provide a vector to attr.plot
so that i-th element of the vector
is applied to the i-th plot. For example, if a code chunk has two plots,
you may use attr.plot = c('width="40%"', 'width="60%"')
to set the width
of the first plot to 40%
, and the second plot to 60%
.
attr.source
: Attributes for the source blocks. By default, it uses the
language name of the code chunk as the class name. For example, for
```{r}
, the attribute .r
will be used.
attr.warning
: Attributes for warning blocks. The default is
.plain .warning
.
Below is an example of using options attr.chunk
, attr.source
, and
attr.plot
:
---
title: The `attr.*` chunk options
---
1. Add an ID `#example-a` to the whole chunk.
2. Add line numbers to source blocks via the `.line-numbers` class.
3. Add the class `.round` to the first plot and set its width to 400px.
4. Add two classes `.dark` and `.img-center` to the second plot.
```{r}
#| example-a,
#| attr.chunk = '#example-a',
#| attr.source = '.line-numbers',
#| attr.plot = c('.round width="400"', '.dark .img-center'),
#| fig.alt = c('A scatterplot of rnorm(100) numbers.',
#| 'A sunflower plot of iris.')
plot(rnorm(100), rnorm(100))
i34 = iris[, 3:4]
smoothScatter(i34)
sunflowerplot(i34, add = TRUE)
```
Define CSS rules for the classes in the `#example-a` chunk:
```{css}
#example-a {
.round { border: solid 1px; border-radius: 50%; }
.dark { filter: invert(1); }
.img-center { display: block; margin: auto; }
}
```
Here is an example of creating a callout (5.5) from a code chunk:
---
title: Create a callout via the option `attr.chunk`
output:
html:
meta:
css: ["@default", "@callout"]
js: ["@callout"]
---
If you use the class name `.callout-*` on a chunk, you can turn it into a callout, e.g.,
```{r}
#| attr.chunk = '.callout-example'
1 + 1
```
Remember to load the `callout` CSS/JS assets in YAML.
cache
: speed up the computingThere are only two hard things in Computer Science: cache invalidation and naming things.
—Phil Karlton
You can cache the computing of code chunks via the chunk option cache = TRUE
.
When a code chunk is cached, the computing will be skipped when the document is
compiled on the next time if the source code in the chunk has not
changed3 and the external dependencies used by the chunk have not
changed, either, otherwise the cache will be invalidated and the results will be
re-computed.
The key to understand cache (in)validation is understanding the “external
dependencies” of a code chunk. A common dependency is external variables. For
example, x
is an external (or global) variable and y
is an internal (or
local) variable in the following chunk:
```{r}
y = x + 1
```
That is because y
is defined inside the code chunk, and x
must come from
elsewhere, otherwise the code chunk will throw an error (“object x
not
found”).
When cache is enabled on a code chunk, the global and local variables will be automatically inferred from the code. If the value of any global variable has changed, the cache will be invalidated. Local variables will be saved in a current run, and (lazy-)loaded4 in the next run, so they can be available to later code chunks in the document.
Another common dependency is external data sources. For example, if the data
file foo.csv
has been updated in the following chunk, we may want to
invalidate the cache and read the file again:
```{r, cache = TRUE}
z = read.csv('foo.csv')
```
7 illustrates how the cache system works. The code expression, global variables, and other dependencies will be summarized into a hash (a character string), which will be used to check if the cache exists. Any change in the input of the hash will lead to a change in the hash value, which in turn invalidates the old cache and creates new cache.
flowchart TD A[[Code chunk]]--"parse()"--> B1>Expression] A--"xfun::find_globals()"--> B2>Global variables] A--"cache.extra"--> B3>Other dependencies] B1--"deparse()"--> C((Hash)) B2 --> C B3 --> C C--find cache--> D@{ shape: f-circ } D--"No (re-run code)"--> E@{ shape: lin-cyl, label: "New cache" } D--"Yes (skip running)"--> F[(Load cache)]--import--> G@{ shape: bow-rect, label: "Local variables" } E--"export"--> G style C stroke-dasharray:5 5,stroke-width:3px,color:orangered style E fill:lightyellow
Besides the option cache
, below are other chunk options that allow you to
customize the cache system:
cache.path
: The path to save the cache. If the path is intended to be a
directory path, please add a trailing slash. By default, the path is set to
INPUT__cache/
, where INPUT
is the input file path to fuse()
. For
example, if you fuse('foo.Rmd')
, the default cache path will be
foo__cache/
. If the input is not a file, litedown__cache/
will be used.
The special value :memory:
means in-memory caching, meaning that the cache
will be stored in memory, which is faster than writing to disk but the cache
will be lost once the R session quits.
cache.vars
: Names of local variables. By default, local variables are
automatically detected from the expression via xfun::find_locals()
.
Locally created variables are cached along with the full output of the code
chunk. They will be re-loaded the next time the code chunk is recompiled,
unless the cache is invalidated.
cache.hash
: Names of R variables to be used to determine if cache should
be loaded or invalidated. By default, it is the names of global variables
automatically detected by xfun::find_locals()
from the code chunk. You can
provide a vector of names to override the automatic detection if you want
some specific global variables to affect caching, or if the automatic
detection is not reliable.
Alternatively, you can also pass a list object containing values that should affect caching (i.e., if any of these values change, the cache should be invalidated). Please avoid using too large objects, since hashing them may be slow.
cache.extra
: Additional information to validate the cache. For example, if
the code reads an external file foo.csv
, and you want the cache to be
invalidated after the file is modified, you may use
extra = file.mtime("foo.csv")
(i.e., use the file modification time for
validation).
cache.keep
: By default, only one copy of the cache is kept, and all other
copies are automatically purged.
If TRUE
, all copies of the cache are kept, which can be useful when
you are experimenting with different changes and have not decided the
version of the change to use. You can always go back to a certain
version, and the cache corresponding to that version still exists.
If FALSE
, all copies are removed, which means the cache is always
invalidated, and can be useful to force re-executing the code, after
which you can clear this option to resume the default behavior of
keeping one copy.
cache.rw
: A list of functions to read/write the cache files. The list is
of the form
list(name = 'xxx', load = function(file) {}, save = function(x, file) {})
.
This argument can also take a character string to use some built-in
read/write methods. currently including:
rds
: The default, using readRDS()
and saveRDS()
.
raw
: Using serialize()
and unserialize()
). Note that the rds
method generates smaller files because it uses compression, but is often
slower than the raw
method, which does not use compression.
qs
: Using qs::qread()
and qs::qsave()
). It requires the qs
package, which can be much faster than the above base R methods and also
supports compression.
A special application of the cache.hash
option is to freeze the computation of
a code chunk, meaning that the cache will not be affected by any variable, even
if the chunk uses global variables. To do this, you can set cache.hash
to any
non-character constant, e.g., cache.hash = FALSE
. Since FALSE
is a constant
that cannot be changed by any variable, the cache will not be invalidated by
changes in any variable.
With great power comes great responsibility. Freezing the cache can make it faster to compile the document, but you may get outdated and/or inconsistent computational results when the old cache should have been invalidated. Please make sure you understand what you are doing before freezing the cache.
cap.pos
: caption positionPossible values are 'top'
and 'bottom'
. By default, figure captions are
placed below figures, and table captions are placed above tables.
1 shows an example of a table caption at the bottom.
```{r}
#| tab-bottom, cap.pos = 'bottom', tab.env = '.table .box',
#| tab.cap = 'A table caption at the bottom via `cap.pos = "bottom"`.'
head(cars, 4)
speed | dist |
---|---|
4 | 2 |
4 | 10 |
7 | 4 |
7 | 22 |
```
You can test different positions for both figures and tables in the following example.
---
title: Caption position
---
## Default caption positions
```{r, fig-bottom, fig.cap = 'Bottom figure caption.'}
plot(cars)
```
```{r, tab-top, tab.cap = 'Top table caption.'}
cars
```
## Change the positions
```{r, fig-top, fig.cap = 'Top figure caption.', cap.pos = 'top'}
plot(cars)
```
```{r, tab-bottom, tab.cap = 'Bottom table caption.', cap.pos = 'bottom'}
cars
```
child
: child documentsThe child
option can take a vector of file paths to other .Rmd
files, which
will be compiled and included as the output the current chunk. This offers a way
to organize a large R Markdown report as smaller child documents.
code
and file
: alternative ways to provide codeWhile you can write source code directly in a code chunk, there are two more ways to provide the code through the chunk options:
code
: A character vector of the source code, e.g.,
code = c('x = 1 + 1', 'x')
.
file
: A vector of file paths to read the code from, e.g.,
file = c('foo.R', 'bar.R')
. If both file
and code
options are provided
and non-empty, the code
option will be ignored.
These options will be ignored (with a warning) if the code chunk is not empty. They can be used in any code chunks (not necessarily R code chunks).
---
title: The `code` option
---
Define a code template `tpl`:
```{r}
tpl = 'lm(mpg ~ %s, data = mtcars) |> summary() |> coef()'
x_vars = names(mtcars)[2:4]
```
We run regressions on three variables one by one:
```{r, code = sprintf(tpl, x_vars)}
```
.
.
.
``` {.r}
lm(mpg ~ cyl, data = mtcars) |> summary() |> coef()
```
| |Estimate|Std. Error|t value|Pr(>\|t\|)|
|---|--:|--:|--:|--:|
|(Intercept)|37.885| 2.074|18.268| 0.000|
|cyl|-2.876| 0.322|-8.920| 0.000|
``` {.r}
lm(mpg ~ disp, data = mtcars) |> summary() |> coef()
```
| |Estimate|Std. Error|t value|Pr(>\|t\|)|
|---|--:|--:|--:|--:|
|(Intercept)|29.600| 1.230|24.070| 0.000|
|disp|-0.041| 0.005|-8.747| 0.000|
``` {.r}
lm(mpg ~ hp, data = mtcars) |> summary() |> coef()
```
| |Estimate|Std. Error|t value|Pr(>\|t\|)|
|---|--:|--:|--:|--:|
|(Intercept)|30.099| 1.634|18.421| 0.000|
|hp|-0.068| 0.010|-6.742| 0.000|
collapse
: collapse source code and text outputWhen the option collapse = TRUE
(it is FALSE
by default), adjacent source
code blocks and verbatim text output blocks will be merged, which can make the
output a little more compact, e.g.,
```{r, collapse = TRUE}
x = 1:5
x
#> [1] 1 2 3 4 5
x + 100
#> [1] 101 102 103 104 105
```
Note that if a source block and an output block are not adjacent to each other, they will not be merged, e.g., when there is a warning block between the source and text output.
comment
: comment out text outputBy default, verbatim text output is commented out with a prefix #>
. This
comment prefix can be set via the chunk option comment
, e.g.,
```{r, comment = '#~~> ', print = NA}
1:9
#~~> [1] 1 2 3 4 5 6 7 8 9
matrix(1:9, 3)
#~~> [,1] [,2] [,3]
#~~> [1,] 1 4 7
#~~> [2,] 2 5 8
#~~> [3,] 3 6 9
```
If you do not want the comment prefix, you may set comment = ''
.
```{r, comment = '', print = NA}
matrix(1:9, 3)
[,1] [,2] [,3]
[1,] 1 4 7
[2,] 2 5 8
[3,] 3 6 9
```
The reason to use comments is that readers will be able to copy multiple code blocks from the chunk output in one go and directly run the copied text as code elsewhere when desired (all text output will be ignored as comments).
dev
: graphics deviceFor a code chunk to generate graphics output, it will need a graphics device to
record the plots. The device can be set via the dev
option. The value can be a
function (e.g., svg
), a function name as a string (e.g., "svg"
), or a string
that can be evaluated to a function (e.g., "grDevices::svg"
).
The default device is cairo_pdf
for LaTeX output, and png
for other types of
output (such as HTML).
The plot file path (specified via the chunk option fig.path
) will be passed to
the first argument of the device function. The chunk option dev.args
can be
used to pass a list of additional arguments to the device, and the default list
is:
list(units = "in", onefile = FALSE, width = 8, height = 8, res = 84)
Note that the default unit for width
and height
is inches instead of pixels.
Any argument in dev.args
that is not available in a device function will be
ignored. For example, the png()
device does not have the onefile
argument,
so it will not be passed to png()
, whereas you can use
dev.args = list(bg = 'yellow')
to pass a custom bg
value (yellow background)
to png()
. Please read the help page of the device function (e.g., ?png
) to
learn the possible arguments that you can use.
---
title: The graphics device
---
The default (png) device with a higher resolution:
```{r}
#| chunk-a, dev.args = list(res = 96),
#| fig.alt = 'png with a resolution of 96 ppi'
plot(cars)
```
The `svg` device with a background color:
```{r}
#| chunk-b, dev = 'svg', dev.args = list(bg = 'lightyellow'),
#| fig.alt = 'svg with a lightyellow background'
plot(cars)
```
echo
: visibility of source codeBy default, the source code blocks are displayed in the output with the chunk
option echo = TRUE
. To suppress source code blocks, you can use
echo = FALSE
.
error
: error behaviorYou can specify how errors in a code chunk should be handled with the error
option. Its possible values are:
NA
(default): No special handling, i.e., if an error occurs, just throw it
and halt the process.
TRUE
: Capture errors with tryCatch()
and show the error messages in the
output.
FALSE
: Suppress errors.
If a certain error cannot be captured by tryCatch()
, the chunk option
error = TRUE
or FALSE
will not work.
Note that in addition to errors in executing the code, syntax errors in the source code can also be captured, e.g.,
```{r, error = TRUE}
x = 1 + 2 +
#> <text>:2:0: unexpected end of input
#> 1: x = 1 + 2 +
#> ^
```
eval
: code evaluationIf you do not want to evaluate a certain code chunk, you can use the chunk
option eval = FALSE
, which is TRUE
by default.
fig.*
: figure optionsThe fig.*
options fall into two categories: one for decorating the images in
the output, and the other for customizing the plot files. Chunk options for
decoration include:
fig.alt
: The alt text for plots, which will be used in image tags
(<img alt="..." />
) in HTML output. By default, it takes value from the
fig.cap
option, which defaults to NULL
. You are encouraged to provide
the alt text, since it improves the accessibility of images on HTML pages.
fig.cap
: The figure caption. By default, it is empty (NULL
).
fig.env
: Attributes for the figure environment. By default, it is a class
name .figure
.
To avoid omitting the alt text inadvertently, you can set
options(litedown.fig.alt = TRUE)
in your .Rprofile
. When this option is set
and the chunk option fig.alt
is unset, fuse()
will emit reminders about the
missing alt text for code chunks containing plots. You can also set this option
inside a particular R Markdown document to receive reminders only for that
document.
When all these options are provided, the Markdown output of figures will be of this form:
:::: {fig.env}
![fig.alt](fig.path){attr.plot}
:::
fig.cap
:::
::::
When a code chunk generates multiple plots, the options fig.alt
and
attr.plot
(2.1.5.1) are vectorized, i.e., they will be recycled to a
length equal to the number of plots, and each value in the vectors will be
applied to each plot. For example, for fig.alt = c('aaa', 'bbb')
, the first
value will be the alt text for the first plot, and the second value is for the
second plot.
When fig.cap
is provided and a chunk generates multiple plots, all plots will
be moved into the same figure environment at the end of the chunk output. As a
result, one code chunk can produce at most one figure environment, which may
contain one or more plots. If you need multiple figure environments, you have to
write separate code chunks.
---
title: Decorating figures
output:
html:
meta:
css: ["@default", "@article"]
js: ["@sidenotes"]
---
Place two plots side by side via the `width` attribute:
```{r}
#| chunk-a, attr.plot = 'width="45%"',
#| fig.alt = c('a histogram', 'a sunflower plot'),
#| fig.cap = 'Exploring the faithful dataset.',
#| fig.env = '.figure .box'
hist(faithful$eruptions, main = '', border = 'white')
sunflowerplot(faithful)
```
A full-width figure (requires the `@article` CSS):
```{r}
#| chunk-b, fig.dim = c(14, 4), fig.env = '.figure .fullwidth',
#| fig.cap = 'Monthly mean relative sunspot numbers from 1749 to 1983.'
par(mar = c(4, 4, .1, .1), bg = 'lightyellow', fg = 'red', las = 1)
plot(sunspots, col = 'red')
grid()
```
Feel free to experiment with other class names provided by the `@article` CSS, such as `.side .side-right` or `.embed-right` in addition to `.fullwidth`.
Chunk options for plot files include:
fig.ext
: The plot file extension. By default, it will be automatically
inferred from the first argument of the graphics device function
(2.1.5.8). For example, the extensions .png
and .svg
can be
detected from the png()
and svg()
devices, respectively:
formals(png)[[1]]
#> [1] "Rplot%03d.png"
formals(svg)[[1]]
#> if (onefile) "Rplots.svg" else "Rplot%03d.svg"
You can manually provide the extension via fig.ext
if the automatic
inference fails.
fig.height
and fig.width
: The physical size of plots to be passed to the
graphics device. By default, a plot is 8 x 8 inches with a resolution of
84ppi (i.e., 672 x 672 pixels). The chunk option fig.dim
(figure
dimension) can be used as a shorthand to provide fig.width
and
fig.height
at the same time, e.g., fig.dim = c(10, 6)
means
fig.width = 10
and fig.height = 6
. These options can be overridden by
the option dev.args
(2.1.5.8), e.g.,
dev.args = list(width = 9, height = 7)
.
fig.path
: A path prefix for plot files. This prefix, joined by the chunk
label and the plot number, will be the actual plot file path. For example,
for fig.path = 'figures/'
and a chunk with the label foo
, the i-th
plot in the chunk will be figures/foo-i.ext
, with the extension .ext
from the chunk option fig.ext
. The default fig.path
is the input file
path with its extension substituted by __files/
, e.g., when running
fuse('path/foo.Rmd')
, the fig.path
will be set to path/foo__files/
by
default.
If fig.path
is not a character value or NULL
(e.g., fig.path = NA
),
plots will not be captured for output.
---
title: Plot files
---
The default extension for the `jpeg()` device is `jpeg`, and you can change it to `.jpg` if desired:
```{r, chunk-a, dev = 'jpeg', fig.ext = '.jpg'}
plot(cars)
```
Set the plot size via `fig.dim`:
```{r, chunk-b, fig.dim = c(5, 4)}
plot(cars)
```
Write plot files to a different folder:
```{r, chunk-c, fig.path = 'figures/'}
plot(cars)
```
include
: visibility of whole code chunkWhen you want to hide the full output of a code chunk, you can use the option
include = FALSE
, instead of trying to hide elements individually like
echo = FALSE
(source), results = 'hide'
(text output), message = FALSE
,
and warning = FALSE
, etc.
Note that even with include = FALSE
, the code is still executed, unless you
also set eval = FALSE
.
label
: chunk labelThe chunk label is an identifier of a code chunk. It is used in plot/cache filenames and figure/table cross-references. If two code chunks have the same label, their plots and cache will overwrite each other, which may lead to unexpected output. If one of these chunks does not produce plots/tables or use cache, it is fine for them to use the same label.
If two code chunks share the same label, and one of the chunks is empty, the empty chunk will copy code from the non-empty one.
---
title: Shared chunk labels
---
```{r, chunk-a}
message("This chunk's label is chunk-a")
```
Repeat `chunk-a` but suppress the message:
```{r, chunk-a, message=FALSE}
```
---
title: Shared chunk labels
---
``` {.r}
message("This chunk's label is chunk-a")
```
``` {.plain .message}
#> This chunk's label is chunk-a
```
Repeat `chunk-a` but suppress the message:
``` {.r}
message("This chunk's label is chunk-a")
```
If the label is not provided in a chunk, a label of the form chunk-i
will be
assigned to the chunk, where i
is the chunk number in the document. For
example, the labels for the following code chunks will be chunk-a
, chunk-2
,
chunk-js
, and project-flowchart
:
```{r, chunk-a}
```
```{css}
```
```{js}
//| label: chunk-js
```
```{mermaid, label = 'project-flowchart'}
```
message
: message behaviorThe message
option is similar to the error
option (2.1.5.10) but is
for handling message()
. Possible values are:
NA
: No special handling (messages will be written to the R console by
default).
TRUE
(default): Capture messages and show them in the output document.
FALSE
: Suppress messages.
order
: order of executionCode chunks and inline code expressions do not have to be executed sequentially.
The chunk option order
can be used to customize the order of execution. It
takes a numeric value and defaults to the chunk number (e.g., 3
for the 3rd
chunk), therefore all chunks are executed in the natural linear order by
default.
A lower order
value indicates earlier execution of the chunk, and vice
versa. If you want to delay the execution of a chunk, assign a higher order
value to it. If you want to prioritize the execution, you may assign a lower
value.
It is the order of these values that matters, instead of the specific values.
For example, if the input contains three chunks in total, order values 1
,
1000
, and 888
for these chunks will be equivalent to 1
, 3
, and 2
,
since the order is calculated via the order()
function:
order(c(1, 1000, 888))
#> [1] 1 3 2
order(c(1, 3, 2)) # same order
#> [1] 1 3 2
You may use two variables, i
(the chunk number) and N
(the total number of
chunks), in the order
value, which can make it easier to specify the relative
order. For example, if a chunk has order = i + 1.5
, its next chunk will be
executed before it, because the order of the next chunk is i + 1
(unless its
order has also been changed), which is smaller than i + 1.5
. Without the
variable i
, you would have to figure out the chunk number by yourself and
assign a fixed value like 8.5
in this case.
If you want an earlier chunk to be executed last, you may use order = N + 1
.
Similarly, to execute a later chunk first, you may use order = 0
.
Note that the order
option also works for text chunks that contain inline code
expressions. To specify the order of a text chunk, set the order
option in any
inline code expression in the chunk.
In the following example, we execute the first text chunk in the end by setting
order = N + 1
, so that the variables x
and n_cyl
will be available
(calculated from later chunks), and we move the last chunk one step earlier via
order = i - 1.5
, so the variable m
will be ready for the text chunk before.
Without the custom order, this example will either throw errors (objects not
found) or use wrong values of these variables (from elsewhere in the session).
---
title: Custom execution order
abstract: "We analyzed `{r, order = N + 1} nrow(x)` `{r} n_cyl`-cylinder cars, with an average MPG of `{r} m`."
---
Subset the data:
```{r}
n_cyl = 8
x = subset(mtcars, cyl == n_cyl)
```
The average MPG `{r} m` is calculated from:
```{r, order = i - 1.5}
m = mean(x$mpg)
```
---
title: Custom execution order
abstract: "We analyzed 14 8-cylinder cars, with an average MPG of 15.1."
---
Subset the data:
``` {.r}
n_cyl = 8
x = subset(mtcars, cyl == n_cyl)
```
The average MPG 15.1 is calculated from:
``` {.r}
m = mean(x$mpg)
```
You can change the value of n_cyl
to 4 or 6, re-run the example, and get a new
report of cars with 4 or 6 cylinders.
print
: printing functionIn a code chunk, if the value of an expression is visible, it will be printed.
You may read the help pages ?invisible
and ?withVisible
to learn more about
the visibility of values.
To print a value, a print function needs to be called. The function can be
provided via the chunk option print
, which defaults to the S3 generic function
xfun::record_print()
, with the following methods:
methods(xfun::record_print)
#> [1] record_print.data.frame* record_print.default*
#> [3] record_print.knitr_kable* record_print.matrix*
#> [5] record_print.record_asis* record_print.tbl_df*
#> see '?methods' for accessing help and source code
These methods are mainly for generating tables from rectangular data objects, such as matrices and data frames.
If a non-function value (such as NA
) is passed to the print
option,
base::print()
(or methods::show()
for S4 objects) will be called, which will
generate text output that you would normally see in the R console. Therefore if
you wish to avoid printing data objects to tables, you may use the chunk option
print = NA
.
The chunk option print.args
can be used to pass additional arguments to the
print function. It should be of the form
list(class_a = args_a, class_b = args_b, ...)
, where class_x
is a class
name, and args_x
is a list of arguments. If the (first) class name of a
visible value in a code chunk is class_x
, args_x
will be passed to the print
function.
In the following example, objects are printed through base::print()
(by
setting the chunk option print = NA
). The argument zero.print = '.'
(see
?print.table
) is used for table
objects, and arguments quote
/
max.levels
(see ?print.factor
) are used for factor
objects.
---
title: Print objects
---
Print objects with `base::print()`, and use different arguments for different objects.
```{r}
#| print = NA,
#| print.args = list(table = list(zero.print = '.'),
#| factor = list(quote = TRUE, max.levels = 3))
X = c('a', 'b', 'c', 'c', 'c', 'a')
Y = factor(c('A', 'B', 'C', 'C', 'D', 'E'))
Y # factor
table(X, Y) # table
```
.
.
.
```
#> [1] "A" "B" "C" "C" "D" "E"
#> 5 Levels: "A" "B" ... "E"
```
``` {.r}
table(X, Y) # table
```
```
#> Y
#> X A B C D E
#> a 1 . . . 1
#> b . 1 . . .
#> c . . 2 1 .
```
If you do not want to pass arguments by class names but pass a list of arguments
to the print function regardless of the object classes, you can wrap the list in
I()
. For example, print.args = I(list(zero.print = '.'))
means that
print(..., zero.print = '.')
is called to print any objects in the chunk in
the above example. However, please note that this “universal” argument list may
not work for all print functions or methods—some arguments may be ignored, and
some may cause errors if the print function does not have certain arguments. It
is often better to define print.args
by class names.
purl
: code extractionWhen using litedown::fiss()
to extract code from a document, all code chunks
are extracted by default. To skip a code chunk, set purl = FALSE
for that
chunk.
This code chunk is not important for the `fiss()` output.
```{r, setup, purl = FALSE}
litedown::reactor(fig.height = 5)
```
This code chunk will be included in the script.
```{r}
if (TRUE) {
x = 1 + 1
}
```
if (TRUE) {
x = 1 + 1
}
You can see that the setup
chunk with the option purl = FALSE
was omitted in
the R script output (generated by fiss()
).
ref.label
: chunk referencesA code chunk can reuse code from other code chunks via the option ref.label
.
It takes a vector of chunk labels, and will copy code from these chunks.
Using chunk references makes it possible to avoid copying code from one chunk to another manually. You can also compose code freely from other chunks.
---
title: Reference chunks by labels
---
We define a function `abs2()`:
```{r, chunk-a, eval = FALSE}
abs2 = function(x)
```
Return `x` if `x >= 0`, otherwise return `-x`:
```{r, chunk-b, eval=FALSE}
ifelse(x >= 0, x, -x)
```
The full function (combining `chunk-a` and `chunk-b`):
```{r, ref.label = c('chunk-a', 'chunk-b')}
```
See if it works:
```{r}
abs2(c(1, -2, 0, -100))
```
.
.
.
The full function (combining `chunk-a` and `chunk-b`):
``` {.r}
abs2 = function(x)
ifelse(x >= 0, x, -x)
```
See if it works:
``` {.r}
abs2(c(1, -2, 0, -100))
```
```
#> [1] 1 2 0 100
```
results
: text output behaviorText output from code chunks can be shown verbatim, hidden, or just written out
as is. The behavior is controlled by the option results
, with possible values:
TRUE
(default): Show text output verbatim (in fenced code blocks).
FALSE
(or "hide"
): Hide text output.
"asis"
: Write raw text (interpreted as Markdown) to the output.
---
title: Text output
---
Default verbatim output:
```{r, test-out}
cat('Hello _world_!\n')
```
Hide output:
```{r, test-out, results = FALSE}
```
Output as is:
```{r, test-out, results = 'asis'}
```
---
title: Text output
---
Default verbatim output:
``` {.r}
cat('Hello _world_!\n')
```
```
#> Hello _world_!
```
Hide output:
``` {.r}
cat('Hello _world_!\n')
```
Output as is:
``` {.r}
cat('Hello _world_!\n')
```
Hello _world_!
strip.white
: leading / trailing blank lines in codeBy default, blank lines at the beginning and end of a source code block are
removed. To keep them, you can use the chunk option strip.white = FALSE
.
---
title: Blank lines in source code
---
Keep blank lines at the beginning/end:
```{r, strip.white = FALSE}
# a blank line above
1 + 1
# and a blank line below
```
.
.
.
``` {.r}
# a blank line above
1 + 1
```
```
#> [1] 2
```
``` {.r}
# and a blank line below
```
tab.*
: table optionsUnless the default print
option (2.1.5.17) is changed, common
rectangular data objects are printed to Markdown tables through
xfun::record_print()
, and tables are generated by a simple function
xfun::md_table()
, which is even much simpler than knitr::kable()
.
The chunk option tab.cap
can be used to provide the table caption. The option
tab.env
can be used to customize the attributes (3.3.7) of the
table environment, and it defaults to .table
, i.e., a class name table
.
Below is a quick example showing where tab.cap
and tab.env
are used in the
Markdown output:
---
title: A simple table
---
See @tab:simple.
```{r}
#| simple, tab.env = '.table .box',
#| tab.cap = 'First 3 rows of the `cars` data.',
head(cars, 3)
```
.
.
.
``` {.r}
head(cars, 3)
```
:::: {.table .box #tab:simple}
::: {.caption}
[](#@tab:simple)
First 3 rows of the `cars` data.
:::
|speed|dist|
|--:|--:|
|4| 2|
|4|10|
|7| 4|
::::
The position of the caption can be changed via the chunk option cap.pos
(2.1.5.3).
To customize the table content, you can pass arguments to xfun::md_table()
via
the chunk option print.args
. Below is an example that limits data frames to at
most 4 rows and 6 columns:
```{r}
#| print.args = list(data.frame = list(limit = c(4, 6)))
mtcars
mpg | cyl | … | gear | am | vs | |
---|---|---|---|---|---|---|
Mazda RX4 | 21.0 | 6 | … | 4 | 1 | 0 |
Mazda RX4 Wag | 21.0 | 6 | … | 4 | 1 | 0 |
⋮ | ⋮ | ⋮ | … | ⋮ | ⋮ | ⋮ |
Maserati Bora | 15.0 | 8 | … | 5 | 1 | 0 |
Volvo 142E | 21.4 | 4 | … | 4 | 1 | 1 |
```
By default, tables are limited to 10 rows. If you want to get rid of this limit,
you may set options(xfun.md_table.limit = Inf)
or limit = Inf
in
print.args
or wrap the data object in I()
.
Please read the help page ?xfun::md_table
to learn the possible arguments, and
also see 2.1.5.17 to learn how the chunk option print.args
works.
time
: code timingWhen the chunk option time
is set to TRUE
, the execution time of the chunk
will be recorded. You can print out litedown::timing_data()
at the end of a
document to check the timing data. The data is sorted by default, so you can
quickly know which code chunks are slow.
You can time specific code chunks by applying time = TRUE
to them, or time all
chunks in the document by setting litedown::reactor(time = TRUE)
in the first
code chunk.
verbose
: printing verbosityBy default, invisible values are not printed (2.1.5.17). However, you
can use the chunk option verbose
to change this behavior. Its possible values
are:
0
(default): Do not print invisible values.
1
: Always print the last value in the code chunk, no matter if it is
visible.
2
: Print all invisble values in the code, except for invisible NULL
(which is often not useful).
Below are examples for different values of verbose
:
```{r, test-verbose, collapse = TRUE}
1:5
#> [1] 1 2 3 4 5
x = 1 + 1
y = x^2
```
```{r, test-verbose, verbose = 1, collapse = TRUE}
1:5
#> [1] 1 2 3 4 5
x = 1 + 1
y = x^2
#> [1] 4
```
```{r, test-verbose, verbose = 2, collapse = TRUE}
1:5
#> [1] 1 2 3 4 5
x = 1 + 1
#> [1] 2
y = x^2
#> [1] 4
```
In case you do not know, an assignment (e.g., x = 1 + 1
) in R returns the
value being assigned invisibly, so the value will not be printed by default. You
can set verbose = 2
to reveal the value. Normally you would have to explicitly
print the variable or use the ()
trick to make it visible:
x = 1 + 1
x # print x explicitly
(x = 1 + 1) # or the () trick
warning
: warning behaviorThe warning
option is similar to the error
option (2.1.5.10) but is
for handling warning()
. Possible values are:
NA
: No special handling (warnings will be sent to the R console by
default).
TRUE
(default): Capture warnings and show them in the output document.
FALSE
: Suppress warnings. Warnings often exist for good reasons. Please
make sure the warnings can be safely suppressed before using
warning = FALSE
.
wd
: working directoryThe working directory when evaluating code chunks and inline code will be
temporarily changed to the directory of the input file to fuse()
. If the input
is not a file path, the working directory will not be changed.
When using relative paths to read/write files in code chunks, these paths are relative to the directory of the input file by default. I know some people hate this default. It is a matter of thinking inside or outside the (R Markdown) box. I tend to think inside—everything is relative to the document that I’m working inside. It is just a habit (following the conventions of HTML and CSS), not necessarily right or wrong.
You are free to specify any directory as the working directory for the code via
the chunk option wd
, e.g., wd = '../'
(up one level from the directory of
the input file) or wd = 'C:/Documents/Project/'
. Please note that using a
hard-coded absolute directory may affect others if they need to re-run your
document on their computers, since the absolute directory may not exist there.
If you have to change the working directory to an absolute directory, you may
consider using functions like xfun::proj_root()
to dynamically find the
absolute directory.
The syntax for inline code expressions is `{lang} code`
, where lang
is
the language name, e.g., r
. Spaces are allowed after the opening backtick and
before the closing backtick. If the code
happens to contain N
backticks, you
will need to use at least N + 1
backticks outside, e.g.,
``{r} paste("`", rnorm(1), "`")``
. An inline code expression inside
another piece of inline code is not parsed or evaluated, which provides a way to
write verbatim inline code expressions, e.g., `` `{lang} code` ``
.
Comma-separated chunk options can also be provided to inline code expressions
after the language name, e.g., `{r, eval = FALSE} code`
. Currently, only
eval
(2.1.5.11) and error
(2.1.5.10) are supported, and
most other chunk options in 2.1.5 are not supported for inline
code. A few additional options are provided for formatting inline numeric output
(2.2.2).
The inline code is expected to return a value of length 1. If it returns an
object of length greater than 1, all elements in the object will be concatenated
by line breaks (\n
) to form a single string.
If the inline expression returns a single number, the number will be formatted.
To bypass the formatting, wrap the inline expression in I()
. We denote the
number by \(x\) for now.
First, \(x\) will be rounded to \(n\) significant digits, where \(n\) is
controlled by the option signif
and its default value is 3
.
Then \(x\) will be formatted using the scientific notation \(\pm m\times 10^n\)
if \(|x| \geq 10^p\) or \(|x| \leq 10^{-p}\), where \(p\) is controlled by the
option power
, which defaults to 6
. If you want to avoid the scientific
notation, you can set this option to Inf
. When \(m = 1\), it will be omitted
(i.e., the output will be \(10^n\) instead of \(1 \times 10^n\).
When a number is formatted in the scientific notation, it will be enclosed with
$ $
as an inline LaTeX math expression. For example, `{r} 1234567`
will
be rendered to \(1.23 \times 10^{6}\).
Options to control the formatting include:
dollar
: Whether to add $ $
to numbers in the scientific notation. By
default (dollar = NA
), dollar signs will be added automatically if the
inline code expression in the source is not directly enclosed with dollar
signs. TRUE
means to always add dollar signs, and FALSE
means to never
add dollar signs. You will need to use dollar = FALSE
for numbers in a
LaTeX math expression because you should not use $ $
inside a math
expression (see the example below).
power
: The threshold for scientific notation.
signif
: The desired number of significant digits.
---
title: Inline output
---
We know that $\pi = `{r} pi`$, and the first 3 letters are `{r} LETTERS[1:3]`. The first 3 variables of `mtcars` are `{r} xfun::join_words(names(mtcars)[1:3])`. You will not see this value `{r, eval = FALSE} 2 * pi`.
Some numbers:
```{r}
x = 1.23456789 * 10^c(0, 5, 6, 9, -6) * c(1, 1, -1, 1, 1)
sprintf('%f', x)
```
| n = signif/p = power | n = 3, p = 6 | n = 5/2/4/1/7 | p = 0/4/Inf/9/5 |
|-----------|-----------:|---------------------:|----------------------:|
| x_1 | `{r} x[1]` | `{r, signif=5} x[1]` | `{r, power=0} x[1]` |
| x_2 10^5 | `{r} x[2]` | `{r, signif=2} x[2]` | `{r, power=4} x[2]` |
| x_3 10^6 | `{r} x[3]` | `{r, signif=4} x[3]` | `{r, power=Inf} x[3]` |
| x_4 10^9 | `{r} x[4]` | `{r, signif=1} x[4]` | `{r, power=9} x[4]` |
| x_5 10^-6 | `{r} x[5]` | `{r, signif=7} x[5]` | `{r, power=5} x[5]` |
An equation: $Y = 300 `{r, dollar = FALSE, signif = 1} x[3]`x$.
---
title: Inline output
---
We know that $\pi = 3.14$, and the first 3 letters are A
B
C. The first 3 variables of `mtcars` are mpg, cyl, and disp. You will not see this value .
Some numbers:
``` {.r}
x = 1.23456789 * 10^c(0, 5, 6, 9, -6) * c(1, 1, -1, 1, 1)
sprintf('%f', x)
```
```
#> [1] "1.234568" "123456.789000" "-1234567.890000"
#> [4] "1234567890.000000" "0.000001"
```
| n = signif/p = power | n = 3, p = 6 | n = 5/2/4/1/7 | p = 0/4/Inf/9/5 |
|-----------|-----------:|---------------------:|----------------------:|
| x_1 | 1.23 | 1.2346 | $1.23 \times 10^{0}$ |
| x_2 10^5 | 123000 | 120000 | $1.23 \times 10^{5}$ |
| x_3 10^6 | $-1.23 \times 10^{6}$ | $-1.235 \times 10^{6}$ | -1230000 |
| x_4 10^9 | $1.23 \times 10^{9}$ | $10^{9}$ | $1.23 \times 10^{9}$ |
| x_5 10^-6 | 0.00000123 | 0.000001234568 | $1.23 \times 10^{-6}$ |
An equation: $Y = 300 -10^{6}x$.
To make it easier to read the numbers in the Markdown table above, we render the table to 2:
n = signif/p = power | n = 3, p = 6 | n = 5/2/4/1/7 | p = 0/4/Inf/9/5 |
---|---|---|---|
x_1 | 1.23 | 1.2346 | \(1.23 \times 10^{0}\) |
x_2 10^5 | 123000 | 120000 | \(1.23 \times 10^{5}\) |
x_3 10^6 | \(-1.23 \times 10^{6}\) | \(-1.235 \times 10^{6}\) | -1230000 |
x_4 10^9 | \(1.23 \times 10^{9}\) | \(10^{9}\) | \(1.23 \times 10^{9}\) |
x_5 10^-6 | 0.00000123 | 0.000001234568 | \(1.23 \times 10^{-6}\) |
For knitr users, please note that the syntax `r code`
is not supported
by default. You have to wrap the language name r
in curly braces. As a
temporary workaround, you may set options(litedown.enable.knitr_inline = TRUE)
in your .Rprofile
to make litedown recognize `r code`
, but we
recommend that you convert the document via litedown:::convert_knitr()
instead
if you decide to stay with litedown in the long run.
Besides R Markdown, you can also pass an R script to fuse()
. You can write
Markdown content in #'
comments, and group lines of code into chunks by #|
comments, e.g.,
#' ---
#' title: An R script
#' output: latex
#' ---
#'
#' A _paragraph_.
#| eval = FALSE
1:10
1 + 1
#| fig.width = 10, dev = 'pdf'
plot(cars)
Both #'
and #|
comments are optional. If no #'
or #|
comments are found
in the script, the whole script will be treated as a single code chunk.
flowchart TD O@{ shape: lin-doc, label: "Input script" }--"sieve()"--> A@{ shape: f-circ } -.-o B[["#' YAML"]] A -.-o C[["#' Text"]] C ---> H[[Text]] A -.-o D[[Code chunk]] D -.- D1>"#| Chunk options"] D -.- D2>Code]--> G{Results} --> I[[Chunk output]] A -.-o E[[More chunks...]] -.- K>...] --> L{...} --> M[[More output...]] -.-> J H -.-> J@{ shape: doc, label: "Markdown" } I -.-> J B --> R[[YAML]] -.-> J J--"mark()" --> N@{ shape: tag-doc, label: "Output document" } class O,J,N document classDef document stroke-width:3px class G,L fuse-out classDef fuse-out fill:lightyellow class H,I,M,R output classDef output fill:none
Currently, the main computing language supported by litedown is R. You can check all supported languages via:
sort(names(litedown::engines()))
#> [1] "css" "embed" "js" "md" "mermaid" "r"
You can get a language engine definition via litedown::engines('LANG')
, where
LANG
is the language name, e.g.,
litedown::engines('md')
You can also set a new language engine via:
litedown::engines(LANG = function(x, inline = FALSE, ...) {
})
The function’s argument x
is an element in the list returned by
litedown::crack()
.
The md
engine will output Markdown text both verbatim and as-is, which can be
useful for showing Markdown examples, e.g.,
```{md}
You can see both the _source_ and _output_ of
this `md` chunk.
```
You can also use `{md} the engine **inline**`.
You can insert CSS/JS to the output via the css
/js
engines, e.g.,
```{css}
a {
color: red;
}
```
```{js}
document.body.classList.add('dark');
```
The mermaid
engine can be used to draw diagrams with
mermaid.js, e.g.,
```{mermaid, flow-abcd, echo = FALSE, fig.cap = 'A flowchart.'}
flowchart TD;
A[WEB] --> B[noweb] --> D[Sweave] --> E[knitr]
A --> C[CWEB]
D --> F[litedown]
```
The JS library mermaid.js has to be loaded for the diagrams to be rendered. By
default, the latest version of mermaid.js will be added to the js
variable
(4.3.2.2) in the YAML metadata automatically. If you prefer using a
specific version, you can add it manually to the YAML metadata, e.g.,
---
title: "A mermaid diagram"
output:
html:
meta:
js: ["@npm/[email protected]/dist/mermaid.min.js"]
---
```{mermaid}
```
The embed
engine can be used to embed text files verbatim in the output. You
can pass the file paths to the chunk option file
or write the paths in the
chunk body (with optional quotes), e.g.,
```{embed, file = "foo.txt"}
```
```{embed}
"foo.txt"
```
You can use multiple paths in either case, e.g.,
```{embed, file = c("foo.txt", "bar.R")}
```
```{embed}
"foo.txt"
bar.R
```
The content of the file(s) will be included in a fenced code block in the
output. For example, if foo.txt
contains a single line “hello world”, the
output will be:
``` {.txt}
hello world
```
By default, the language name of the code block is from the file extension. You
can use the chunk option attr.output
to customize the name, e.g.,
```{embed, file = "foo.txt", attr.output = ".md"}
```
Then the output will be in a Markdown (.md
) block:
``` {.md}
hello world
```
You can use the chunk option results = "hide"
to hide the output, or
results = "asis"
to output the file content as is (i.e., not wrapping it in a
code block).
Major differences between knitr and litedown include:
knitr accepts a variety of input document formats, such as *.Rnw
,
*.Rhtml
, *.Rmd
, *.Rtex
, *.Rrst
, and *.Rasciidoc
,5
whereas litedown only supports Markdown.
knitr supports multiple graphics devices for a chunk via the dev
option, and litedown only supports one device for a chunk (but there are
multiple choices for this device). I hate the
ugly
hacks in
knitr for drawing a plot in one device and rendering it with other
devices later. This feature is not worth the effort due to the daunting
complexity of implementation, not to mention that it doesn’t always work. If
you need multiple file formats for the same plot in litedown, you should
start a new chunk with the same code (2.1.5.19) and a different
dev
option instead.
knitr may output images in Markdown (![]()
) or raw HTML (<img>
) /
LaTeX (\includegraphics{}
), depending on certain chunk options;
litedown always uses the Markdown syntax for figures, which is cleaner
and more portable.
The document parser of knitr is based solely on regular expressions and not robust. Code chunks and inline expressions are not aware of their contexts. The parser of litedown is based on commonmark, which is more robust and makes it straightforward to write verbatim code or comment out code (2.1.3).
knitr supports a large number of chunk options and language engines, and litedown only supports a limited number of chunk options and engines (this number may grow in the future but perhaps not significantly).
Inline code for knitr does not support options or non-R languages, and litedown is much more flexible in this regard.
All code chunks in knitr are executed in the linear order, and litedown can execute code in an arbitrary custom order (2.1.5.16).
knitr supports chunk hooks and output hooks; litedown doesn’t
support hooks but the attr.*
(2.1.5.1) and print
(2.1.5.17) options have provided some flexibility in customizing
the chunk output.
knitr is more than 12 years old and quite mature, and litedown is new and still experimental.
If you feel any indispensable knitr features are missing in litedown, please feel free to suggest them in Github issues. However, please remember that the goal of litedown is not to fully re-implement rmarkdown, knitr or Pandoc. Some features may never be re-implemented, especially when the implementation is not simple enough.
Edit this chapter on GitHub
All the tired horses in a run; how’m I gonna get any writing done!
—Michael Friendly (and Bob Dylan)
A Markdown document consists of a YAML frontmatter providing the document metadata, and a Markdown body. The frontmatter is optional, and (if provided) should be written using the syntax introduced in 3.1. The syntax for the body is introduced in 3.2 and 3.3.
YAML stands for “YAML Ain’t Markup Language”, which aims at being a “human-friendly data serialization language”. You may judge how “human-friendly” it is6 by yourself after reading its specifications at https://yaml.org.
What we introduce in this section is a significantly trimmed-down version of
YAML. Its official name is “TRACY”, a recursive acronym for “TRACY Really Ain’t
Complete YAML” and also in honor of Tracy
Teal since she was once a nice boss
of mine. However, to follow the naming convention of *ML
, we use the name
“TAML” (with apologies for the further recursion) when the audience has some
software background.
The following example shows all types of data that TAML supports:
a: 1
b: "string"
c: true
d: null
# e is also null
e:
# an expression
f: !expr 1 + 1
# vectors
g: [1, 2, 3]
h: ["foo", "bar"]
# a nested list
i:
i1: -1e8
i2: "another string"
i3:
i31: [true, false, true]
any_character.is-okay: string not quoted
The TAML specifications are:
Each line should be of the form field: value
. The field
name can use any
character. The value
cannot span across multiple lines. If a line starts
with #
, it will be ignored (i.e., it is treated as a comment). Any line
that is not of the form field: value
or a comment are ignored.
Values must be character, numeric, logical, expressions, null, or vectors.
Character strings are quoted in either single or double quotes. Quotes are optional when the values cannot be interpreted as other types of values (e.g., numeric or logical).
Numeric values must consist of numbers (0-9), +
, -
, and e
, and can
be converted to numbers.
Logical values must be either true
or false
. No other values are
recognized, such as yes
, YES
, no
, NO
,7 on
, off
,
TRUE
, FALSE
, T
, F
, to be
, or not to be
.
Expressions start with !expr
followed by R code.
Null values can be either literally null
or empty.
A vector consists of comma-separated character/numeric/logical values in
[ ]
.
If value
is empty, and the next line is of the form field: value
indented by \(n\) white spaces (\(n \geq 1\)),8 the next line will
be treated as a child member of the current line. One field can have
multiple chidren indented to the same level, and each child can have its
children (further indented by \(n\) spaces).
TAML is implemented in xfun::taml_load()
and used by litedown. See
4.3 for all possible fields supported in litedown.
If you cannot learn the basic Markdown in this section (i.e., 3.2) in a few minutes, please feel free to file a GitHub issue and I will send you a sponsorship of $20 on GitHub.
You can write these elements inline: **strong**
, _emphasis_
, `code`
,
~~strikethrough~~
, [text](link)
, and ![alt](image/path)
.
For links and images, their URLs should not contain
spaces. If they do, the
URLs must be enclosed in <>
, e.g., ![alt](<some dir/a subdir/foo.png>).
The rest of elements in this section are block-level elements.
Headings start with a number of #
’s, e.g., ## level-two heading
.
You are recommended to use ##
(level-two) as the top heading level in a
document, except for books, in which case each document is a chapter and you
should use #
(level-one) as the top level.
Paragraphs are separated by blank lines.
Code blocks can be indented by 4 spaces, or fenced by ```
. In the
latter case, the code block can have a class name (typically the language name).
For example, below is a JS code block:
```js
x < 0 ? -x : x;
```
There are three types of lists: unordered, ordered, and task lists.
An unordered list item starts with -
, +
, or *
, e.g., - item
.
A task list item is a regular list item with [ ]
(unchecked) or [x]
(checked) in the beginning, e.g., - [ ] item
.
An ordered list item starts with a number followed by a period. The number of the first item is used as the starting number of the list, and the rest of numbers can be arbitrary. The examples on the left below are equivalent to those on the right:
1. Get up!
1. Write a book!
1. Go to bed!
1. Get up!
2. Write a book!
3. Go to bed!
5. Get up!
100. Write a book!
1. Go to bed!
5. Get up!
6. Write a book!
7. Go to bed!
Block quotes start with >
followed by a space and then the quote. A quote can
contain any number of any Markdown elements. If it contains multiple block-level
elements, each line needs to start with >
. For example, below is a block quote
with multiple paragraphs:
> "You're an oaf. I told you quite distinctly to make his discharge papers out."
>
> And all the bile which had accumulated in the judge advocate's soul in the course of that day because of Captain Linhart and Švejk poured out like a wild torrent on the head of the staff warder. At the end of it Bernis said:
>
> "And now do you understand that you are a prize royal oaf?"
>
> This is something which should only be said to kings and emperors, but even this simple staff warder, who was no royal personage, was not very pleased about it.
>
> ---Jaroslav Hašek, _The Good Soldier Švejk_ (Chapter 9)
Tables are created with |
as the column separator, i.e., Pandoc’s pipe table,
which can be generated by xfun::md_table(x)
or knitr::kable(x, "pipe")
if
you want to create a table from a rectangular object x
in R.
| col 1 | col 2 | col 3 |
|:------|------:|:-----:|
| row 1 | row 1 | row 1 |
| row 2 | row 2 | row 2 |
The position of the colon below the table header controls the column alignment, e.g., a colon on the right means the column is right-aligned, and colons on both ends means the column is center-aligned.
In addition to the basic features above (supported by commonmark), the litedown package also supports the following features.
Raw LaTeX and HTML blocks can be written as fenced code blocks with language
names =latex
and =html
, e.g.,
```{=latex}
This only appears in \LaTeX{} output.
```
Raw LaTeX blocks will only appear in LaTeX output, and will be ignored in other output formats. Similarly, raw HTML blocks will only appear in HTML output. One exception is raw LaTeX blocks that are LaTeX math environments, which also work for HTML output (see the next section).
You can write both $inline$
and $$display$$
LaTeX math, e.g.,
\(\sin^{2}(\theta)+\cos^{2}(\theta) = 1\).
$$\bar{X} = \frac{1}{n} \sum_{i=1}^n X_i$$
$$|x| = \begin{cases} x &\text{if } x \geq 0 \\ -x &\text{if } x < 0 \end{cases}$$
For expressions in pairs of single or double dollar signs to be recognized as
LaTeX math, there must be no spaces after the opening dollar sign, or before the
closing dollar sign. The math expression should either start from the very
beginning of a line, or have a space or (
before the opening dollar sign.
For a pair of single dollar signs, they must be on the same line in the text, and the closing dollar sign should not be followed by a number (to avoid detecting math mistakenly from text like “a $5 bill and a $10 bill”). The inner math expression should not be wrapped in backticks.
For $$ $$
expressions, they can span over multiple lines, in which case
the closing $$
must have at least one non-space character before it, and
no spaces after it.
Valid examples:
$x + y$
($x + y$)
$x + y$
text $x + y$ text
$$x + y$$
$$x + y$$
text $$x + y$$ text
$$x +
y$$
Invalid examples:
$ x + y$ <- space after the opening `$`
text$x + y$ <- lack of space before the opening `$`
text $x + y$10 text <- number after closing `$`
$x +
y$ <- multi-line `$ $` expressions
$`x + y`$ <- expression wrapped in backticks
$$x +
y
$$
^- lack of non-space character before closing `$$`
LaTeX math environments are also supported, e.g., below are an align
environment and an equation
environment:
\begin{align}
a^{2}+b^{2} & = c^{2}\\
\sin^{2}(\theta)+\cos^{2}(\theta) & = 1
\label{eq:pyth-identity}
\end{align}
\begin{equation}
\begin{split}
(a+b)^2 &=(a+b)(a+b)\\
&=a^2+2ab+b^2
\end{split}
\end{equation}
\begin{align} a^{2}+b^{2} & = c^{2}\\ \sin^{2}(\theta)+\cos^{2}(\theta) & = 1 \label{eq:pyth-identity} \end{align}
\begin{equation} \begin{split} (a+b)^2 &=(a+b)(a+b)\\ &=a^2+2ab+b^2 \end{split} \end{equation}
These math environments can be written as either nake LaTeX code or raw LaTeX
blocks (```{=latex}
), but we recommend that you use raw LaTeX blocks
because they are more robust. LaTeX math environments work for both LaTeX and
HTML output.
For HTML output, it is up to the JavaScript library (MathJax or KaTeX) whether a math environment can be rendered (4.1.6).
Write superscripts in ^text^
and subscripts in ~text~
(same syntax as
Pandoc’s Markdown), e.g., 210 and H2O. Currently only alphanumeric
characters, *
, (
, and )
are allowed in the scripts. For example, a^b c^
will not be recognized as a superscript (because the space is not allowed). Note
that GFM supports striking out text via ~text~
, but this feature has been
disabled and replaced by the feature of subscripts in litedown. To strike
out text, you must use a pair of double tildes.
Insert footnotes via [^n]
, where n
is a footnote number (a unique
identifier). The footnote content should be defined in a separate block starting
with [^n]:
. For example:
Insert a footnote here.[^1]
[^1]: This is the footnote.
The support is limited for LaTeX output at the moment, and there are two caveats if the document is intended to be converted to LaTeX:
The footnote content must be a single paragraph.
Only numbers9 are supported as identifiers, and other types of identifiers are not recognized.
The two limitations do not apply to HTML output, e.g., you can write arbitrary elements in footnotes and not necessarily one paragraph.
Attributes on images, links, fenced code blocks, and section headings can be
written in {}
. ID and class are two common attributes. An ID can be written
after the #
character, and a class can be written after .
. Attributes are
typically written in the form name="value"
, and separated by spaces (in fact,
you can also write IDs and classes explicitly like other attributes, e.g.,
id="foo" class="bar"
). Certain attributes do not require values, and you can
provide the attribute name only, e.g., disabled
or contenteditable
, although
it is harmless to write disabled="true"
.
Only lowercase letters (a-z), digits (0-9), hyphens (-
), and colons (:
) are
allowed in ID and class strings. For example, sec:intro
and fig-cars
are
valid IDs, but sec_intro
and tab cars
are not.
For example, ![text](path){.foo #bar width="50%"}
will generate an <img>
tag
with attributes in HTML output:
<img src="path" alt="text" id="bar" class="foo" width="50%" />
and ## Heading {#baz}
will generate:
<h2 id="baz">Heading</h2>
Links of the form [text](url){...}
will generate:
<a href="url" ...></a>
When the url
is empty, <a>
will be converted to <span>
, e.g.,
[text](){.foo .bar}
will generate:
<span class="foo bar">text</span>
This provides a way to create <span>
elements, which is similar to bracketed
Span
s (i.e.,
[text]{...}
) in Pandoc’s Markdown.
For fenced code blocks, a special rule is that the first class name will be
treated as the language name for a block, and the class
attribute of the
result <code>
tag will have a language-
prefix. For example, the following
code block
```{.foo .bar #my-code style="color: red;"}
```
will generate the HTML output below:
<pre>
<code class="language-foo bar" id="my-code" style="color: red;">
</code>
</pre>
Most attributes in {}
are ignored for LaTeX output except for:
The width
attribute for images, e.g., ![text](path){width="50%"}
will be
converted to \includegraphics[width=.5\linewidth]{path}
.
The .unnumbered
attribute, which will make a heading unnumbered, e.g.,
# Hello {.unnumbered}
will be converted to \section*{Hello}
.
The .appendix
attribute on a heading, which will start the appendix, e.g.,
# Appendix {.appendix}
will be converted to \appendix
.
The ID attributes on headings, which will be converted to \label{}
, e.g.,
# Introduction {#sec:intro}
will be converted to
\section{Introduction}\label{sec:intro}
.
When a top-level heading has the attribute .appendix
, the rest of the document
will be treated as the appendix. If section numbering is enabled
(4.1.9), the appendix section headings will be numbered
differently.
Div
sA fenced Div
can be written in :::
fences. Note that the opening fence must
have at least one attribute, such as the class name. For example:
::: foo
This is a fenced Div.
:::
::: {.foo}
The syntax `::: foo` is equivalent to `::: {.foo}`.
:::
::: {.foo #bar style="color: red;"}
This div has more attributes.
It will be red in HTML output.
:::
A fenced Div
will be converted to <div>
with attributes in HTML output,
e.g.,
<div class="foo" id="bar" style="color: red;">
</div>
For LaTeX output, it can be converted to a LaTeX environment if both the class
name and an attribute data-latex
are present. For example,
::: {.tiny data-latex=""}
This is _tiny_ text.
:::
will be converted to:
\begin{tiny}
This is \emph{tiny} text.
\end{tiny}
The data-latex
attribute can be used to specify arguments to the environment
(which can be an empty string if the environment doesn’t need an argument). For
example,
::: {.minipage data-latex="{.5\linewidth}"}
will be converted to:
\begin{minipage}{.5\linewidth}
The data-latex
attribute is optional for fenced Div
s with class names
figure
or table
. They will be converted to figure
or table
environments.
For example,
:::: {.figure}
![](foo.png)
::: {.caption}
This is a caption.
:::
::::
will be converted to:
\begin{figure}
\includegraphics{foo.png}
\caption{This is a caption.}
\end{figure}
Other fenced Div
s will be ignored if they don’t have the data-latex
attribute, and their inner content will be written out normally without a
surrounding environment.
If a fenced Div
has multiple class names (e.g., {.a .b .c}
), only the first
class name will be used as the LaTeX environment name. However, all class names
will be used if the output format is HTML (e.g., <div class="a b c">
).
To cross-reference an element, it must be numbered first. Then we can refer to it by its ID.
Section heading IDs can be either manually assigned or automatically generated
(4.1.1). Section numbers are automatically generated if the
number_sections
option is true (4.1.9).
Figures and tables are automatically numbered if their captions are provided
(via the chunk options fig.cap
/ tab.cap
), e.g.,
```{r}
#| nice-plot, fig.cap = "A nice caption"
plot(cars)
```
To refer to an element in the text, use the syntax @ID
, where ID
is the ID
of the element to be referenced, which typically consists of a prefix (e.g.,
sec:
, fig:
, tab:
, or eq:
) and a label. For example:
Please see @fig:nice-plot for an overview of the `cars` data.
Hyphens (-
) are also allowed in place of colons in the ID prefix, e.g.,
@fig-nice-plot
.
LaTeX math environments such as align
and equation
are numbered by default.
To refer to an expression (e.g., an equation) in a math environment, a label
needs to be assigned to the expression first via \label{}
, and it must start
with the prefix eq:
or eq-
, e.g.,
```{=latex}
\begin{equation}
\sin^2(x) + \cos^2(x) = 1 \label{eq:pyth-identity}
\end{equation}
```
Then we can use either @eq:*
or @eqn:*
to cross-reference the equation,
e.g., @eqn:pyth-identity
. Under the hood, the prefix @eq
will be resolved to
\ref{}
, and @eqn
will be resolved to \eqref{}
. If you are not familiar
with LaTeX commands \ref{}
and \eqref{}
, the main difference is that
\eqref{}
will render the equation number in parentheses, e.g.,
\(\eqref{eq:pyth-identity}\), whereas \ref{}
will only generate the equation number,
e.g., \(\ref{eq:pyth-identity}\).
In HTML output, \eqref{}
will also add a label “Equation” before the number by
default. If you prefer writing the label manually and having control over the
parentheses, you can use @eq:
instead of @eqn:
, e.g., Eq. [@eq:*]
(using
the label “Eq.” and square brackets).
In addition to equation numbers, you can also specify a tag for an equation via
\tag{}
and refer to the equations by its tag, e.g.,
For a right-angled triangle, we are all familiar with @eqn:pyth-theorem.
\begin{equation}
a^2 + b^2 = c^2 \label{eq:pyth-theorem} \tag{PT}
\end{equation}
For a right-angled triangle, we are all familiar with \(\eqref{eq:pyth-theorem}\).
\begin{equation} a^2 + b^2 = c^2 \label{eq:pyth-theorem} \tag{PT} \end{equation}
You can cross-reference any other type of elements by adding empty anchors of
the form [](#@ID)
into them, e.g.,
:::: {#lst:example .box}
::: caption
[](#@lst:example) A code listing demonstrating how to fold code blocks with JS.
:::
```js
document.querySelectorAll('pre').forEach(pre => {
const d = document.createElement('details');
d.innerHTML = '<summary>Details</summary>';
pre.before(d); // insert <details> before <pre>
d.append(pre); // move <pre> into <details>
});
```
::::
We can cross-reference @lst:example.
document.querySelectorAll('pre').forEach(pre => {
const d = document.createElement('details');
d.innerHTML = '<summary>Details</summary>';
pre.before(d); // insert <details> before <pre>
d.append(pre); // move <pre> into <details>
});
We can cross-reference 1.
Note that we also added the ID (#lst:example
) to the fenced Div to make it the
target of the cross reference, i.e., when you click on the reference, the
browser will navigate to the Div.
For HTML output, we can style the numbers and references with CSS. Element
numbers are wrapped in <span class="ref-number-*"></span>
, and references are
wrapped in <span class="cross-ref-*"></span>
, where *
is the ID prefix
(e.g., fig
, tab
, and eqn
). For example, we can add the label “Listing” to
the number and reference of the block above:
.ref-number-lst::before, .cross-ref-lst::before {
content: "Listing ";
}
.ref-number-lst {
font-style: italic;
}
.ref-number-lst::after {
content: ". "
}
This feature requires the R package rbibutils. Please make sure it is installed before using citations.
xfun::pkg_load2("rbibutils")
To insert citations, you have to first declare one or multiple bibliography databases in the YAML metadata, e.g.,
bibliography: ["papers.bib", "books.bib"]
Each .bib
file contains entries that start with keywords. For example,
R-base
is the keyword for the following entry:
@Manual{R-base,
title = {R: A Language and Environment for Statistical Computing},
author = {{R Core Team}},
organization = {R Foundation for Statistical Computing},
address = {Vienna, Austria},
year = {2024},
url = {https://www.R-project.org/},
}
Then you can use [@R-base]
or @R-base
to cite this item. The keywords must
consist of only alphanumeric characters (a-z
, A-Z
, 0-9
) and -
. You can
include multiple keywords in [ ]
separated by semicolons.
For HTML output, the citation uses the author-year style. The syntax
[@keyword]
generates the citation in parentheses (e.g., (Author, 2024)
), and
@keyword
only puts the year in parentheses (e.g., Author (2024)
).
For LaTeX output, the citation style depends on the LaTeX package, which you can
set via the citation_package
option of the output format in YAML metadata,
e.g.,
output:
latex:
citation_package: biblatex
The default is natbib
. The table below shows the LaTeX commands corresponding
to the Markdown citation syntax:
citation package | [@key-1; @key-2] |
@key |
---|---|---|
none | \cite{key-1, key-2} |
\cite{key} |
natbib | \citep{key-1, key-2} |
\citet{key} |
biblatex | \parencite{key-1, key-2} |
\cite{key} |
Note that litedown actually generates \citep
and \citet
regardless of
the citation package, but will redefine them to \cite
or \parencite
according to the citation package. For example, it will insert
\let\citep\parencite
in the LaTeX preamble when citation_package
is
biblatex
.
“Smart” HTML entities can be represented by ASCII characters, e.g., you can
write fractions in the form n/m
. Below are some example entities:
1/2 |
1/3 |
2/3 |
7/8 |
1/7 |
1/9 |
1/10 |
(c) |
(r) |
(tm) |
---|---|---|---|---|---|---|---|---|---|
½ | ⅓ | ⅔ | ⅞ | ⅐ | ⅑ | ⅒ | © | ® | ™ |
As mentioned earlier, a lot of features in Pandoc’s Markdown are not supported in the litedown package. Any feature that you find missing in previous sections is likely to be unavailable. In addition, a lot of R Markdown and Quarto (both are based on Pandoc) features are not supported, either. Some HTML features have been implemented via JavaScript and CSS.
Pandoc can convert Markdown to many output formats, such as Word, PowerPoint, LaTeX beamer, and EPUB. The litedown package is unlikely to support output formats beyond HTML and LaTeX.
Edit this chapter on GitHub
Mature mental health demands, then, an extraordinary capacity to flexibly strike and continually restrike a delicate balance between conflicting needs, goals, duties, responsibilities, directions, et cetera. The essence of this discipline of balancing is “giving up.”
—M. Scott Peck, The Road Less Traveled
The main function to convert Markdown to other formats is litedown::mark()
.
The output format can be specified in the output
(or format
) field in YAML
metadata (4.3), e.g.,
---
output:
html:
options:
js_math:
package: "katex"
version: "0.16.4"
number_sections: true
embed_resources: ["local", "https"]
meta:
css: "custom.css"
---
The options
argument of mark()
can be used to enable/disable/set options to
control Markdown rendering. This argument can take either a list, e.g.,
list(toc = TRUE, smart = FALSE)
, or a character vector, e.g.,
c("+toc", "-smart")
, or equivalently, +toc-smart
, where +
means to enable
an option, and -
means to disable an option. The options can also be set in
YAML metadata in 4.3 (recommended). Available options are listed
below.
auto_identifiers
Add automatic IDs to headings, e.g.,
# Hello world!
## Introduction
will be converted to
<h1 id="chp:hello-world">Hello world!</h1>
<h2 id="sec:introduction">Introduction</h2>
The prefix chp:
(chapter) will be added to the automatic IDs of level-one
headings, and sec:
(section) will be added for other levels of headings.
You can override the automatic ID by providing an ID manually via the ID attribute, e.g.,
# Hello world! {#hello}
An automatic ID is generated by substituting non-alphanumeric characters in the
heading text with hyphens. If the result is empty, the ID will be section
. If
any ID is duplicated, a numeric suffix will be added to the ID, e.g.,
example_1
and example_2
.
cleveref
Whether to use the LaTeX package cleveref
for “clever” cross-references (3.3.8). This option is for LaTeX
output only. If enabled, cleveref will be loaded and references will use the
command \cref{}
instead of \ref{}
, which will automatically add the type of
reference before the reference number, e.g., \cref{sec:intro}
may generate
Section 1
, so you do not have to write Section \ref{sec:intro}
.
embed_cleanup
Whether to clean up plot files after they have been embedded in HTML output (see 4.1.4).
embed_resources
Embed resources (images, CSS, and JS) in the HTML output using their base64-encoded data (images) or raw content (CSS/JS). Possible values are:
null
or false
: Do not embed any resources.
"local"
or true
: Embed local image/CSS/JS files.
"https"
: Embed web resources (links that start with https://
).
"all"
: An alias to the union of "local"
and "https"
.
The default is "local"
, i.e., local resources are embedded, whereas https
resources are not. This means the output document may not work offline. If you
have to view the output offline, you need to use the option value "https"
(or
"all"
) and render the document at least once before you go offline.
For https
resources, after you have embedded them successfully once, they will
be cached locally (via xfun::download_cache
) and will not require an Internet
connection again.
js_highlight
Specify the JavaScript library to syntax highlight code blocks. Possible values
are highlight
(highlight.js) and prism
(Prism.js). The default is prism
. This option can also
take a list of the form list(package, version, style, languages)
, which
specifies the package name (highlight
or prism
), version, CSS style/theme
name, and names of languages to be highlighted.
You can find information about Prism.js from its CDN at
https://cdn.jsdelivr.net/npm/prismjs/. Available styles are under the
themes/
directory (e.g., prism-dark
), and languages are under the
components/
directory (e.g., prism-c
). You can omit the prefix prism-
,
e.g.,
js_highlight:
package: prism
style: dark
languages: [r, latex, yaml]
The CDN of highlight.js is at
https://cdn.jsdelivr.net/gh/highlightjs/cdn-release/build/. Themes are
under the styles/
directory (e.g., github
), and you can find demos of
themes at https://highlightjs.org/static/demo/. Supported language are
under the languages/
directory (e.g., latex
).
By default, languages are automatically detected and the required JS files are
automatically loaded. Normally you need to specify the languages
array only if
the automatic detection fails.
Technically this option is a shorthand for setting the metadata variables css
and js
in 4.3. If you want full control, you may disable this
option (set it to false
or null
) and use metadata variables directly, which
requires more familiarity with the JS libraries and the jsdelivr CDN.
js_math
Specify the JavaScript library for rendering math expressions in HTML output.
Possible values are "mathjax"
and "katex"
(the default). Like the
js_highlight
option, this option is also essentially a shorthand for setting
the metadata variables css
and js
.
For MathJax, the js
variable is
set to tex-mml-chtml.js
.
For KaTeX, the js
variable is set
to katex.min.js
and the css
variable is set to katex.min.css
. KaTeX’s
auto-render
extension (auto-render.min.js
) is also enabled by default,
so math expressions can be immediately rendered when the page is loaded.
If you want finer control, you can provide a list of the form
list(package, version, css, js)
. This will allow you to specify the package
name, version, and css/js files. For example, if you want to use MathJax’s
tex-chtml.js
instead, you may set:
js_math:
package: mathjax
version: 3
js: es5/tex-chtml.js
By default, MathJax version 3 is used. If you want to use the older v2, you may set:
js_math:
package: mathjax
version: 2
js: MathJax.js?config=TeX-AMS-MML_CHTML
Please visit the MathJax CDN to know which versions and JS files are available.
For KaTeX, the version is not specified by default, which means the latest
version from the CDN. Below is an example
of specifying the version 0.16.4 and using the mhchem
extension:
js_math:
package: katex
version: 0.16.4
js: [dist/katex.min.js, dist/contrib/mhchem.min.js]
Note that if you want the HTML output to be self-contained via the
embed_resources
option, KaTeX can be embedded and used offline, but MathJax
cannot be fully embedded due to its complexity. MathJax v3 can be partially
embedded and used offline, but currently only its fonts can be embedded, and
extensions cannot. If you must view HTML output offline, we recommend using
KaTeX, but please also note that KaTeX and MathJax do not fully cover each
other’s features.
keep_yaml
Whether to keep the YAML metadata in the output. When TRUE
, the original YAML
in the Markdown input (if exists) will be written to the output. Note that when
this option is enabled, templates (4.2) will be disabled and mark()
will only generate a document fragment. This option was introduced mainly for
Hugo websites to use litedown instead of Hugo’s Markdown engines to render
pages.
latex_math
Whether to identify LaTeX math expressions in pairs of single ($ $
) or double
dollar signs ($$ $$
), and transform them so that they could be correctly
rendered by MathJax (HTML output) or LaTeX.
number_sections
Whether to number section headings. To skip numbering a specific heading, add a
class attribute .unnumbered
(or use the shorthand -
) to it. For example:
## Preface {.unnumbered}
## About the author {-}
smartypants
Whether to translate certain ASCII strings into smart typographic characters
(see ?litedown::smartypants
).
superscript
Whether to translate strings between two carets into superscripts, e.g.,
text^foo^
to text<sup>foo</sup>
.
subscript
Whether to translate strings between two tildes into subscripts, e.g.,
text~foo~
to text<sub>foo</sub>
.
toc
Whether to generate a table of contents (TOC) from section headings. If a
heading has an id
attribute, the corresponding TOC item will be a link to this
heading. You can also set a sub-option:
depth
: The number of section levels to include in the TOC (3
by
default). Setting toc
to true
is equivalent to:
toc:
depth: 3
To exclude a certain heading from the TOC, assign a class name unlisted
to it.
For example:
## Acknowledgments {.unlisted}
top_level
The desired type of the top-level headings in LaTeX output. Possible values are
'chapter'
and 'part'
. For example, if top_level = 'chapter'
, # heading
will be rendered to \chapter{heading}
instead of the default
\section{heading}
.
Options not described above can be found on the help pages of commonmark,
e.g., the hardbreaks
option is for the hardbreaks
argument of
commonmark::markdown_*()
functions, and the table
option is for the table
extension in commonmark’s extensions.
litedown::markdown_options()
#> [1] "-cleveref" "-hardbreaks" "-number_sections"
#> [4] "-smartypants" "-tagfilter" "-toc"
#> [7] "+auto_identifiers" "+autolink" "+cross_refs"
#> [10] "+embed_cleanup" "+embed_resources" "+js_highlight"
#> [13] "+js_math" "+latex_math" "+smart"
#> [16] "+strikethrough" "+subscript" "+superscript"
#> [19] "+table" "+tasklist"
# commonmark's arguments
opts = formals(commonmark::markdown_html)
opts = opts[setdiff(names(opts), c('text', 'extensions'))]
unlist(opts)
#> hardbreaks smart normalize sourcepos footnotes
#> FALSE FALSE FALSE FALSE FALSE
# commonmark's extensions
commonmark::list_extensions()
#> [1] "table" "strikethrough" "autolink" "tagfilter"
#> [5] "tasklist"
By default, mark()
generates a document fragment (i.e., the body) if the input
does not contain YAML metadata at the beginning. To generate a full document,
you need to specify YAML metadata. A full document is generated with a template.
Below is a simple HTML template example:
<html>
<head>
<title>$title$</title>
</head>
<body>
$body$
</body>
</html>
It contains two variables, $title$
and $body$
. All variables will be
substituted by metadata values, except for $body$
, which is from the body of
the input document (after conversion to a target output format).
The litedown has provided default templates for
HTML
and
LaTeX
output. To pass metadata to templates, use the meta
argument, e.g.,
litedown::mark(..., meta = list(title = "My Title"))
If you want to use a custom template file, you can set the path in the global
option litedown.FORMAT.template
(where FORMAT
is the output format name
(html
or latex
), e.g., in .Rprofile
:
options(litedown.html.template = 'path/to/my/template.html')
The global option will be applied to all documents to be converted by mark()
.
Alternatively, you can pass a template path to the template
argument of the
output format html
or latex
in an individual document, e.g.,
---
output:
html:
template: "path/to/my/template.html"
---
The template path can also take a logical value: TRUE
means to use the default
template, and FALSE
means to generate only a fragment document without using
any template.
Alternatively, the meta
argument can read YAML metadata in the Markdown
document.
The following variables can be set in the top-level fields in YAML:
abstract
: The abstract.
author
: The document author(s).
date
: The date.
subtitle
: The document subtitle.
title
: The document title.
For example:
---
title: "My Title"
author: "[Frida Gomam](https://example.com)"
date: "2023-01-09"
---
The values are treated as Markdown text, and will be converted to the target
output format before being passed to the template. For the above example, the
variable $author$
will be <a href="https://example.com">Frida Gomam</a>
in
the HTML template.
For top-level variables, mark()
will also create the “underscore” versions for
templates, which contain HTML and LaTeX markups. For example, for the $title$
variable, $title_$
will also be available to the template. The following table
shows the values of the underscore variables, assuming the original value of a
variable is TEXT
:
Variables | HTML | LaTeX |
---|---|---|
$title_$ |
<div class="title"> <h1>TEXT</h1> </div> |
\title{TEXT} |
$subtitle_$ |
<div class="subtitle"> <h2>TEXT</h2> </div> |
\subtitle{TEXT} |
$author_$ |
<div class="author"> <h2>TEXT</h2> </div> |
\author{TEXT} |
$date_$ |
<div class="date"> <h3>TEXT</h3> </div> |
\date{TEXT} |
$abstract_$ |
<div class="abstract"> <p>TEXT</p> </div> |
\begin{abstract} TEXT \end{abstract} |
If an original variable is empty or missing, its underscore version will also be
empty. For the $author_$
variable, if the $author$
variable contains
multiple author names as an array, each name will be in a separate <h2>
in
HTML output, and all names will be concatenated by \and
in LaTeX output, e.g.,
for
author: ["Jane X", "John Y"]
in YAML, the HTML output will be:
<div class="author">
<h2>Jane X</h2>
<h2>John Y</h2>
</div>
and the LaTeX output will be:
\author{Jane X \and John Y}
If you design your own template, you are free to use either the original or the
underscore versions of these variables. For example, you could put the title in
an <h1>
without the <div>
wrapper via <h1 class="title">$title$</h1>
instead of using $title_$
.
When these top-level variables are also provided as meta
variables for an
output format, the latter will override the former, e.g.,
title: "Global Title"
output:
html:
meta:
title: "Title for HTML output"
latex:
meta:
title: "Title for LaTeX output"
Other variables need to be specified under output -> * -> meta
, where *
can
be html
or latex
, e.g.,
---
title: "My Title"
output:
html:
meta:
css: "style.css"
js: "script.js"
latex:
meta:
documentclass: "book"
header_includes: "\usepackage{microtype}"
---
The following metadata variables are supported for both HTML and LaTeX templates:
header-includes
, include-before
, include-after
: Either a vector of
(HTML/LaTeX) code or a code file to be included in the header, before the
body, or after the body of the output.The following variables are for HTML templates:
css
variableA vector of CSS files to be included in the output. If the variable is not
provided, the
default.css
will
be used.
If you want to use built-in CSS files in this package, you can only specify the
base name, e.g., default
means default.css
in this package, and snap
means
snap.css
.
You can also use web resources via a full URL, e.g.,
https://example.org/style.css
. One special case is
jsdelivr resources: if a css
value starts with
@
, it will be treated as a jsdelivr resource. if you are not familiar with
jsdelivr, you may read its documentation to understand the following example
URLs. The shorthand syntax is as follows (*
stands for
https://cdn.jsdelivr.net
) and summarized in 3:
@foo
(without a filename extension) will be converted to
*/npm/@xiee/utils/css/foo.min.css
, e.g., @default
means
*/npm/@xiee/utils/css/default.min.css
. If you prefer the .css
extension
over .min.css
, you can use @default.css
.
@foo@version
(a filename followed by a version number) will be converted
to */npm/@xiee/utils@version/css/foo.min.css
, e.g., @[email protected]
means */npm/@xiee/[email protected]/css/article.min.css
.
@path/to/file
(i.e., a value that contains slashes) will be converted to
*/path/to/file
, e.g., @npm/@xiee/utils/js/center-img.js
will be
converted to */npm/@xiee/utils/js/center-img.min.js
.
@path/to/file-1,file-2
(comma-separated values and later values do not
contain slashes) will be converted to
*/combine/path/to/file-1,path/to/file-2
(this can be useful to
combine
multiple resources and load all at once).
@path-1/to/file-1,path-2/to/file-2
(comma-separated values and later
values contain slashes) will be converted to
*/combine/path-1/to/file-1,path-2/to/file-2
.
syntax | actual URL |
---|---|
@foo |
*/npm/@xiee/utils/css/foo.min.css |
@foo.css |
*/npm/@xiee/utils/css/foo.css |
@foo@version |
*/npm/@xiee/utils@version/css/foo.min.css |
@path/to/file |
*/path/to/file |
@path/to/file-1,file-2 |
*/combine/path/to/file-1,path/to/file-2 |
@path-1/to/file-1,path-2/to/file-2 |
*/combine/path-1/to/file-1,path-2/to/file-2 |
This provides a way to reduce the output HTML file size by loading CSS from the web instead of embedding inside HTML, at the cost of requiring Internet connection when viewing the HTML file. If you need the external web resources to work after you go offline, please see 4.1.4.
js
variableA vector of JavaScript files to be included in the output. The syntax is the
same as the css
variable, e.g., snap
means snap.js
in this package,
@snap
means a “jsdelivr” resource, and you can use arbitrary paths or URLs to
other JS files.
body-class
variableA class name for the main body (the default value is body
).
The following variables are for LaTeX templates:
classoption
variableA string containing options for the document class.
documentclass
variableThe document class (by default, article
).
A variable name must consist of alphanumeric characters and hyphens only. You
could use underscores in variable names in the metadata, but please note that
underscores will be normalized to hyphens internally, e.g., header_includes
will be converted to header-includes
. This means if you use a custom template,
you must use hyphens instead of underscores as separators in variable names in
the template.
The above are variables supported in the default templates. If you use a custom
template, you can use arbitrary variable names following the naming convention
(except for body
, which is a reserved name and cannot be used in metadata),
and the values of variables in the metadata will be passed to your template.
Besides metadata variables, the aforementioned Markdown options
(4.1) can also be set in YAML under output -> * -> options
,
e.g.,
output:
html:
options:
toc: true
js_highlight:
package: highlight
theme: github
languages: [diff, latex]
See the help page ?litedown::html_format
for possible fields in addiction to
meta
and options
that can be specified under the format name, e.g.,
output:
latex:
latex_engine: xelatex
keep_md: true
template: custom-template.tex
Edit this chapter on GitHub
And it seemed as though in a little while the solution would be found, and then a new and splendid life would begin; and it was clear to both of them that they had still a long, long road before them, and that the most complicated and difficult part of it was only just beginning.
—Anton Chekhov, The Lady with the Dog
The litedown package aims at lightweight with a minimal number of features
at its core, but you are free to add more features by yourself. In this chapter,
we introduce some CSS/JS assets from the GitHub repository
https://github.com/yihui/lite.js. You can load arbitrary external JS and CSS
files via the js
and css
meta variables. There are numerous JS libraries and
CSS frameworks on the web, and you do not have to use the ones mentioned in this
chapter. You can also write CSS/JS by yourself to enrich your HTML applications.
Remember that the CSS and JS are introduced under the output format html
in
YAML metadata, e.g.,
---
output:
html:
meta:
css: ["one.css", "two.css"]
js: ["three.js", "four.js"]
---
For the sake of brevity, we will omit the full YAML fields in examples
throughout this chapter but only use the css
and js
fields. A file name
foo.js
denotes the file under the js/
directory of the aforementioned
lite.js
repository. Similarly, foo.css
is under the css/
directory.
All these CSS/JS resources can be used offline, and there are two ways to do it. One way is to clone the GitHub repo to your working directory, and use the files you need, e.g.,
js: ["repo/path/js/callout.js"]
Another way is to enable embedding https
resources (4.1.4).
4 provides a quick reference of existing CSS/JS assets in the
lite.js
repository. See 4.3.2.1 for the meaning of the @
character in
css
/ js
names.
feature | description | css | js | section |
---|---|---|---|---|
article | side notes, floats, and full-width elements for articles | @article |
@sidenotes, @appendix |
5.7 |
book | cover and chapter pages for books | @book |
5.8 | |
callout | frames with legends | @callout |
@callout |
5.5 |
center-img | center images in paragraphs | @center-img |
5.14 | |
chapter-toc | add TOC to each chapter | @chapter-toc |
5.9 | |
copy-button | copy buttons | @copy-button |
@copy-button |
5.2 |
external-link | open external links in new windows | @external-link |
5.15 | |
fold-details | fold elements in <details> |
@fold-details |
5.4 | |
heading-anchor | add anchor links to headings | @heading-anchor |
@heading-anchor |
5.6 |
key-buttons | style keyboard shortcuts | @key-buttons |
@key-buttons |
5.16 |
pages | paginate HTML for printing | @pages |
@pages |
5.11 |
right-quote | right-align quote footers | @right-quote |
5.13 | |
slides | snap slides | @snap |
@snap |
5.12 |
tabsets | create tabsets from bullet lists or sections | @tabsets |
@tabsets |
5.3 |
toc-highlight | highlight TOC items on scroll | @toc-highlight |
5.10 |
If the source document is R Markdown instead of Markdown, an alternative way (in
addition to configuring YAML metadata) to add assets is to call the function
litedown::fuel()
in a code chunk. You can either pass vectors of css
/ js
asset names to the css
/ js
arguments, or the feature
names in 4
to the feature
argument, e.g.,
```{r, echo = FALSE}
# add assets by feature
litedown::fuel(feature = c('callout', 'copy-button'))
# or add css/js directly
litedown::fuel(css = c('@callout', '@book'), js = '@callout')
```
You can call fuel()
multiple times in a document to keep adding assets.
Normally you would have to select the text before copying it, and copy buttons can save the effort of text selection when you want to copy the text from an HTML element. You just click on the button, and the text is automatically copied. Copy buttons can be added via CSS/JS:
css: ["@copy-button"]
js: ["@copy-button"]
By default, this will add copy buttons to all code blocks (<code>
in <pre>
)
and all elements with the class name copy-this
. For example, below is a quote
inside a fenced Div ::: copy-this
and you will see a copy button if you hover
over it with the cursor:
Love like you’ve never been hurt
Dance like nobody’s watching
Sing like nobody’s listening
Work like you don’t need the money
Live like it’s heaven on earth
—Alfred D’Souza
Copy like you understand the code…
—Yihui Xie
All code blocks in this book also have copy buttons in them.
In fact, you can add the copy button to any element on an HTML page. You may read this post if you are interested in generalizing the copy button to other elements.
A tabset provides a compact way to arrange content in tabs, which can save a lot of vertical space on the page. To create a tabset, you need to load the following assets:
css: ["@tabsets"]
js: ["@tabsets"]
There are two ways to create a tabset in Markdown.
The first way is to write a bullet inside a fenced Div with the class name
tabset
. The first element of each bullet item will become the tab title, and
the rest of elements will become the tab pane, e.g.,
::: tabset
- Tab one
Content: -OOOO0OOO⍥
Another paragraph.
- Tab two <!--active-->
Content: -OOOOOOOO⍥
- A normal bullet list.
:::
Tab one
Content: -OOOO0OOO⍥
Another paragraph.
Tab two
Content: -OOOOOOOO⍥
By default, the first tab is the active tab initially. To specify a different
initial active tab, add a comment <!--active-->
to the bullet item.
You can use the Left
(Left) and Right
(Right) Arrows on your keyboard to
navigate through the tabs when the tabset is on focus (e.g., after you have
clicked on it once). Now keep pressing the Left or Right Arrow key, and you will
realize that there is a caterpillar crawling in the example tabset above (and
also looking at you). It’s cute, isn’t it?
Since bullet lists can be nested, you can also nest one tabset in another. To do
that, you just wrap a sub-list in a tabset
div, e.g.,
::: tabset
- Tab one
::: tabset
- Child tab one
- Child tab two
:::
- Tab two
:::
I’m not sure why anyone would want to create nested tabsets, but it sounds like a great way to create a maze. Perhaps you can write a detective story or game with it?
Wait a minute. I just came up with an idea! We can use nested tabsets to
navigate through a recursive list. I have implemented this idea in the function
xfun::tabset()
, and felt for the first time of my life that I dare to peruse
the huge recordPlot()
list now! That said, I don’t want to scare you with
recordPlot()
, but let’s take a look at the config file of this book as an
example instead:
cfg = xfun::taml_file('_litedown.yml')
xfun::tabset(cfg, dput)
book
chapter_before
"[Edit this chapter](https://github.com/yihui/litedown/edit/main/$input$) on GitHub"
chapter_after
""
output
html
options
toc
depth
4L
meta
css
c("@default", "@article", "@book", "@book-litedown", "@copy-button",
"@heading-anchor", "@key-buttons", "@callout", "@tabsets", "@pages"
)
js
c("@sidenotes", "@appendix", "@toc-highlight", "@chapter-toc",
"@copy-button", "@external-link", "@heading-anchor", "@right-quote",
"@key-buttons", "@callout", "@tabsets", "@pages")
I have never loved YAML, but this time, I feel that YAML is adorable.
The second way to create a tabset is through section headings. If you add a
class name tabset
to a heading, headings of the next (lower) level will be
used as the tab titles, e.g.,
## Demo tabs {.tabset}
### Tab one
Content.
### Tab two {.active}
Content.
If a heading has the active
class, it will be the active tab initially.
You can also create a tabset by providing an empty fenced Div with the class
name tabset
, and subsequent headings will be converted to tabs, e.g.,
::: tabset
:::
## Tab one
Content.
## Tab two
Content.
The tabset will end before it hits a higher-level heading. The tabset in the
first example above can be ended by a ##
heading, and the second example can
be ended by a #
heading.
To explicitly end a tabset without using a higher-level heading, you can insert
a comment of the form <!--tabset:ID-->
to the end of the tabset, where ID
is
the ID of the starting element of the tabset, e.g.,
## Demo tabs {.tabset #foo}
### Tab one
### Tab two
<!--tabset:foo-->
### Not a tab
::: {.tabset #foo}
:::
## Tab one
## Tab two
<!--tabset:foo-->
## Not a tab
When a tabset is created from sections, the tab titles (from section headings) will appear in the TOC of the document if TOC is enabled (4.1.13).
js: ["@fold-details"]
You can fold any element with the JS. See this post for how to configure the script.
A callout block is a fenced Div with the class name callout-*
. Callouts
require callout.css
and callout.js
:
css: ["@callout"]
js: ["@callout"]
For example:
::: callout-tip
This is a tip.
> You can write arbitrary content, such as a blockquote.
You can even _nest another callout_ into this one!
Is that cool?
:::
This is a tip.
You can write arbitrary content, such as a blockquote.
You can even nest another callout into this one!
Is that cool?
The stylesheet callout.css
supports styling tip
, note
, caution
,
warning
, important
, and example
callouts. For example:
Thank you for your notice! Your notice has been noted.
Be careful when testing for strict equality of floating point numbers.
seq(0, 1, .2) == c(0, .2, .4, .6, .8, 1)
#> [1] TRUE TRUE TRUE FALSE TRUE TRUE
Never try to out-stubborn a cat!
For the sake of reproducibility, please remember to render an R Markdown document in a new R session before publishing or submitting the output document.
::: {.callout-example data-legend="Demo"}
Change the title of this example to "Demo".
:::
Change the title of this example to “Demo”.
You do not have to use callout.css
but can define your own CSS rules, e.g.,
.callout-important {
background: red;
color: yellow;
}
Under the hood, callout.js
turns the fenced Div into a <fieldset>
for the
form:
<fieldset class="callout-*">
<legend>Title</legend>
Content.
</fieldset>
The content comes from the original fenced Div. The title comes from the class
name (converted to uppercase) by default. You can provide a custom title via the
data-legend
attribute of the Div, e.g.,
::: {.callout-tip data-legend="Information"}
:::
The icons before the callout titles can be defined via CSS, e.g., you can add
two exclamation marks before the title of important
callouts:
.callout-important legend::before {
content: "!! ";
}
The default icons defined in callout.css
are essentially UTF-8
characters. In theory,
there are hundreds of thousands of characters that you can choose from. Each
character is 1 to 4 bytes. For example, you can define a music
callout with
the music note symbol ♫ in the CSS:
.callout-music {
background: springgreen;
border-color: yellow;
}
.callout-music legend::before {
content: "♫ ";
}
Then you can insert a music
callout in your document:
::: callout-music
Please listen to this lovely song.
:::
Alternatively, if you are using callout.css
, you can also use CSS variables to
define the border color, background, and icon of a callout, e.g.,
.callout-music {
--callout-background: springgreen;
--callout-border: yellow;
--callout-icon: "♫ ";
}
The CSS is necessary only if you want to hide the anchors by default and reveal them on hover.
css: ["@heading-anchor"]
js: ["@heading-anchor"]
You can style an HTML page in an article format via the following CSS and JS:
css: ["@default", "@article"]
js: ["@sidenotes", "@appendix"]
The article.css
is mainly for styling the article frontmatter, body, and side
content.
The sidenotes.js
is required only if you want to place certain elements on
the left or right side, such as the table of contents (TOC), footnotes, and
sidenotes.
The appendix.js
is required only if you have an appendix in the article.
The web version of this book is also based on the article format, so you know what an article format looks like when you read the HTML version of the book.
The maximum width of the article body is 800px. For larger screens, this means there will be extra space in the left/right margin, where we can place auxiliary information, such as the TOC and footnotes. On smaller screens, the side content will be collapsed into the body.
The article frontmatter, body, and optionally the appendix are placed in separate boxes.
The default typeface is sans-serif, and you can customize it by supplying an
external CSS file (via the css
meta variable) or just embedding CSS in the
document body, e.g.,
```{css, echo = FALSE}
body {
font-family: Palatino, "Book Antiqua", Georgia, serif;
font-size: 1em;
}
```
The TOC and footnotes are automatically placed in the margin if space permits.
You can also write arbitrary content in the margin via a fenced Div
.
The TOC is sticky on the left side as you scroll down the article. If you do not like this behavior, you may cancel it via CSS:
#TOC {
top: unset;
}
Footnotes are moved to the right side. When you move your cursor over a footnote number in the body, the footnote will be moved next to your cursor. This can be convenient when you have multiple footnotes on a line, since you do not need to look for a specific footnote in the margin.
You can write anything in the margin by using a fenced Div
with the classes
.side
and .side-left
or .side-right
.
Notice
Here is a note on the left side. Anything permitted by law is permitted here. Math? No problem!
$$e^{i\theta}=\sin{\theta}+i\cos{\theta}$$
When you have this sidenote “hammer”, I’m sure you will hit a lot of nails into the margin, even if you do not have to.
::: {.side .side-left}
**Anything** on the left.
:::
::: {.side .side-right}
_Anything_ on the right.
:::
Inside the article body, you can write a few special elements.
When an element is wider than the article body, you can show it in its full
width by enclosing the element in a fenced Div
with the class .fullwidth
,
e.g.,
::: {.fullwidth}
![text](path/to/image)
:::
If you use R Markdown, you can generate a wide plot or table from an R code chunk, e.g.,
::: {.fullwidth}
```{r}
#| sunspots, echo = FALSE, fig.dim = c(14, 4),
#| fig.cap = 'Monthly mean relative sunspot numbers from 1749 to 1983.'
par(mar = c(4, 4, .1, .1), bg = 'lightyellow', fg = 'red', las = 1)
plot(sunspots, col = 'red')
grid()
```
:::
If you want to show code (echo = TRUE
) but do not want the code to be in the
full-width container, you can apply the .fullwidth
class to the plot only,
e.g.,
```{r}
#| sunspots, fig.dim = c(14, 4), fig.env = '.fullwidth .figure .box',
#| fig.cap = 'Monthly mean relative sunspot numbers from 1749 to 1983.'
par(mar = c(4, 4, .1, .1), bg = 'lightyellow', fg = 'red', las = 1)
plot(sunspots, col = 'red')
grid()
```
Whenever you find that you are on the side of the majority, it is time to pause and reflect.
Sometimes you may want to add a quote but do not want it to take the full width
in the body. You may use a fenced Div
with the class .quote-left
or
.quote-right
.
Despite the class names, the content does not have to be a quote. If you do want
a quote, just use the blockquote syntax >
, e.g.,
::: {.quote-right}
> This is a boring quote.
>
> ---Someone
:::
You can embed elements on the left or right margin using a fenced Div
with the
class .embed-left
or .embed-right
. These elements will float to the left or
right and exceed the margin by about 200px, which can save some space in the
article body. You can use the extra space to explain the embedded element with
text.
We have embedded a table of the first 4 rows of the mtcars
data on the right
margin, which you can see if the browser window is wide enough.
css: ["@book"]
js: ["@chapter-toc"]
js: ["@toc-highlight"]
css: ["@pages"]
js: ["@pages"]
With snap.css
and snap.js
, you can create lightweight HTML slides:
css: ["@default", "@snap"]
js: ["@snap"]
You can learn more in vignette('slides', package = 'litedown')
.
You can use the script right-quote.js
to right-align a blockquote footer if it
starts with an em-dash (---
).
js: ["@right-quote"]
Without a little madness, life is not worth living. Let us follow the guidance of our inner voice. Why should we fry each of our actions like a piece of cake on a sensible frying pan?
—Milan Kundera, Immortal
js: ["@center-img"]
js: ["@external-link"]
The script key-button.js
identifies keys and the CSS styles them, which can be
useful for showing keyboard
shortcuts.
css: ["@key-buttons"]
js: ["@key-buttons"]
Of course, you can combine any number of JS scripts and CSS files if you want multiple features.
You can include arbitrary CSS frameworks and JS libraries in your documents, not
limited to aforementioned ones in the GitHub repository yihui/lite.js
. Below
is a quick example of using the DataTables library to create interactive tables:
---
title: Display Tables with the JS Library DataTables
output:
html:
meta:
css: ["@default", "@npm/datatables/media/css/jquery.dataTables.min.css"]
js: ["@npm/jquery/dist/jquery", "@npm/datatables/media/js/jquery.dataTables.min.js"]
---
::: {#mtcars-table}
```{r}
I(mtcars)
```
:::
```{js}
window.addEventListener('load', () => {
$('#mtcars-table table').DataTable();
});
```
Edit this chapter on GitHub
I don’t hide from you that I don’t detest the countryside—having been brought up there, snatches of memories from past times, yearnings for that infinite of which the Sower, the sheaf, are the symbols, still enchant me as before. But when will I do the starry sky, then, that painting that’s always on my mind?
—Vincent van Gogh, The Letters
Unless it has become your muscle memory to click on the Knit
button in
RStudio, you may try to switch to litedown::roam()
to preview your HTML output
by clicking on the run
buttons in the user interface. Note that the preview
takes place in memory only. Although you see an HTML page rendered from a file
in the preview, the page is not rendered to disk, unless you click on the
render
button (6.1.3), or call litedown::fuse()
(2) or
mark()
(4) on the file.
By default, the preview will automatically refresh the content after you edit
and save a file. If you prefer building the document only when you want to, you
can turn off the live preview via litedown::roam(live = FALSE)
. In this case,
the document will be rebuilt only when you refresh the page by yourself.
After launching the preview via litedown::roam()
, a file listing will be
displayed, which shows the first few lines of *.R
, *.Rmd
, and *.md
files
in boxes, followed by the list of other files.
By clicking on plain-text files, you will see their full content. For binary files, they may be opened in your browser; if they cannot be opened, the browser may prompt to download the file.
Each file will have its size displayed after the filename, with a link attached to the file size. The link points to the raw file (the behavior of the link is up to the browser—the file may be opened or downloaded), and litedown will not process it at all.
The full path of the file or directory being previewed is displayed at the top left.
For .R
, .Rmd
, and .md
files, you can click on the “Run” button
(⏵) to render them to HTML in memory and preview the output. This
button is displayed at the top right of each file box on the listing page, and
at the top right of the preview page of an individual file.
Rendering .R
and .Rmd
files via the Run
button means the full R code in
them will be executed. If the R code involves intensive computing, it may not be
a good idea to run the whole file, unless you have taken measures to speed up
the computing (e.g., via caching).
There is a button group at the top right of the preview page.
The render
button (with the lightning or voltage icon, ↯): It
renders a document or project in a new R session and save the output file(s)
to disk, which is similar to what the Knit
button does in RStudio.
You can also use the keyboard shortcut Ctrl + K
(or Command + K
on
macOS).
The edit
buttons (with a pencil icon, ✎): Clicking on this
button will open a plain-text file in your default editor.
The back
/forward
buttons (with arrow icons, ← and
→): They are similar to the back/forward buttons on web browser
toolbars, and can be used to go back/forward through the browsing history.
You can also use the keyboard shortcuts Alt + Left
/ Alt + Right
.
The refresh
button (with the refresh icon, ⟳): It can be used
to refresh the page. If you are previewing an Rmd file, refreshing the page
will rebuild it.
You can also use the keyboard shortcut Ctrl + R
(or Command + R
on
macOS).
The printer
button (with a printer icon, ⎙): It brings up the
printer dialog, e.g., you can print the page to PDF.
Code blocks in the preview mode will have line numbers automatically added to their left. If you click on a line number, it will bring you to that line in the source document.
Note that the keyboard shortcuts require the page to be currently on focus before they can take effect. This is important when you are viewing a page inside RStudio or other IDEs, because the viewer may not gain focus automatically, and you will have to explicitly click on it.
Previewing .Rmd
and .R
files that generate plots will leave *__files/
directories (containing plot files) on disk by default. If you want to clean up
such directories when closing or navigating away from the preview page, you may
set the option
options(litedown.roam.cleanup = TRUE)
before you run litedown::roam()
or in your .Rprofile
. Note that it will not
clean up a *__files/
directory if it has existed before you preview a file.
This is to make sure roam()
will not delete existing files by accident. If you
are certain that a *__files/
directory can be safely deleted, you can always
delete it by hand. After that, roam()
will automatically clean it up when you
preview the file again.
Please also note that when caching is enabled (via the chunk option
cache = TRUE
) in the file being previewed, the *__files/
directory will not
be cleaned up, because when a code chunk is cached, it will not be re-evaluated
or re-generate plots next time (unless the cache is invalidated).
Since the Markdown syntax of litedown can be viewed as a subset of Pandoc’s Markdown, you can use RStudio’s visual Markdown editor to author documents. Please bear in mind that most common, but not all, Markdown features are supported.
Knit
buttonIf you use the RStudio IDE, the Knit
button can render R Markdown to a
litedown output format specified in YAML (e.g., html
or latex
). Please
also remember to add a top-level setting knit: litedown:::knit
in YAML,
otherwise RStudio will either throw an error if you use the output format html
or latex
, or use rmarkdown::render()
instead of litedown::fuse()
to render
the document.
---
output: html
knit: litedown:::knit
---
Edit this chapter on GitHub
I can at least listen without indignation to the critic who is of the opinion that when one surveys the aims of cultural endeavour and the means it employs, one is bound to come to the conclusion that the whole effort is not worth the trouble, and that the outcome of it can only be a state of affairs which the individual will be unable to tolerate.
—Sigmund Freud, Civilization and Its Discontents
Books and websites are usually based on multiple input files under a directory.
For a directory to be recognized as a book or website project, it needs to
contain a configuration file named _litedown.yml
.
If you want to customize the output formats for books or websites, you should do
it in _litedown.yml
, e.g.,
output:
html:
options:
toc:
depth: 4
latex:
meta:
documentclass: "book"
The _litedown.yml
file should contain a top-level field named book
, which
currently supports these options:
book:
new_session: false
subdir: false
pattern: "[.]R?md$"
chapter_before: "Information before a chapter."
chapter_after: "This chapter was generated from `$input$`."
You can choose whether to render each input file in a new R session, whether to
search subdirectories for input files, the types of input files (e.g., you can
use .md
or .R
files if you want), and additional information to be included
before/after each chapter, in which you can use some variables such as
$input$
, which is the path of each input file.
Rendering a whole book may be time-consuming10 and unnecessary when
you work on a book. It may be easier to only preview the single chapter that you
currently work on. The preview can be done with litedown::roam()
:
If you run the index file (e.g., index.Rmd
), the whole book will be
previewed.
If you run other chapter files, only the specific chapter is previewed.
When a chapter contains dependencies on certain elements in other chapters (e.g., a chapter includes cross-references to other chapters), we recommend that you run the index file to preview the whole book at least once before you preview individual chapters, to make litedown know the book elements fully.
Similarly, if you use the configuration new_session: false
to render all
chapters in the same R session, and a later chapter uses computed results from a
previous chapter, you will need to preview the full book at least once to make
sure the results are computed and exist in the R session.
You may also choose to run the index file to preview the whole book but then
work on an individual chapter. In this case, roam()
will try to detect changes
in chapter files. When a certain chapter file has been updated, its output on
the full book page will be updated. That is, the full book page is partially
updated without being reloaded. This method lets you preview changes in one
chapter while presenting the whole book. However, please note that certain JS
libraries may not work well in this preview mode. When in doubt, refresh the
page.
The _litedown.yml
file should contain a top-level field named site
, and you
are likely to customize the meta
variables css
, include_before
, and
include_after
for the html
format, e.g.,
site:
rebuild: "outdated"
pattern: "[.]R?md$"
output:
html:
meta:
css: ["@default"]
include_before: "[Home](/) [About](/about.html)"
include_after: "© 2024 | [Edit]($input$)"
Basically, include_before
can take a file or text input that will be used as
the header of each web page, and include_after
will be the footer.
R package developers can build the full package documentation as either a book or a website.
pkg_*()
helper functionsA series of helper functions have been provided in litedown to get various
information about the package, such as the package description (pkg_desc()
),
news (pkg_news()
), citation (pkg_citation()
), source code (pkg_code()
),
and all manual pages (pkg_manual()
). You can call these functions in code
chunks to print out the desired information.
For example, you may call them in the appendix (3.3.6) of a book:
# Appendix {.appendix}
# Package Metadata
```{r, echo = FALSE}
litedown::pkg_desc()
```
To cite the package:
```{r, echo = FALSE}
litedown::pkg_citation()
```
# News
```{r, echo = FALSE}
litedown::pkg_news(recent = 0) # show full news
```
# Manual pages
```{r, echo = FALSE}
litedown::pkg_manual()
```
# Source code
```{r, echo = FALSE}
litedown::pkg_code()
```
Alternatively, you can build a package website by calling these functions in
separate .Rmd
files, e.g., pkg_desc()
in index.Rmd
, pkg_news()
in
news.Rmd
, and so on.
You may use GitHub Actions to automatically build and deploy package websites.
The key is to call litedown::fuse_site()
. I have provided a simple GitHub
action in the
litedown repository, and you can add a step uses: yihui/litedown/site@HEAD
to your GitHub workflow to use it. Below is the full
workflow
for building and publishing litedown’s site to GitHub Pages:
name: Build and deploy package site
on:
push:
branches: ["main"]
permissions:
contents: read
pages: write
id-token: write
jobs:
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/configure-pages@v5
- uses: r-lib/actions/setup-r@HEAD
with:
use-public-rspm: true
- uses: r-lib/actions/setup-r-dependencies@HEAD
- uses: yihui/litedown/site@HEAD
with:
site-dir: 'site'
- uses: actions/upload-pages-artifact@v3
with:
path: 'site'
- id: deployment
uses: actions/deploy-pages@v4
You can copy it to the .github/workflows/
directory of the R package’s Git
repository and commit/push it via Git. Within a few minutes, the package site
should be ready at https://user.github.io/pkg/
, where user
is your GitHub
username, and pkg
is the repository name, e.g.,
https://yihui.github.io/litedown/.
The action yihui/litedown/site
supports a few options that you can configure
under the with
field. In the above example workflow, we used the option
site-dir
. Below are the options currently supported:
site-dir
: Directory of the site source (default: site
).
pkg-root
: Root directory of the R package in the repository (default: .
).
exclude
: Pages to be excluded (filenames separated by spaces) (default: code.Rmd
).
cleanup
: Command to clean up the site directory before publishing, e.g., you can delete the .Rmd
source files (default: rm -f *.Rmd *.yml _*
).
By default, the site is built under the site/
directory of the package. If you
change this path, please remember to adjust the path
option for the
actions/upload-pages-artifact
action accordingly.
If the site source directory in your package does not exist or is empty, the
action will copy the site
template from litedown,
which contains the following .Rmd
files (explained in 5):
list.files('../site/', '[.]Rmd$')
#> [1] "_footer.Rmd" "articles.Rmd" "code.Rmd" "index.Rmd" "manual.Rmd"
#> [6] "news.Rmd"
source | description | content |
---|---|---|
index |
home page | pkg_desc() , pkg_citation() , and README.md |
articles |
package vignettes | the doc/ folder of the installed package |
code |
source code (R, C, C++, etc.) | pkg_code() |
manual |
help pages | pkg_manual() |
news |
news | pkg_news() |
_footer |
page footer | package authors |
You can freely customize this template according to your own needs (e.g., modify, add, or delete files), and add the folder to your repository. Then the action will use your custom template to build the site.
The exclude
option allows you to exclude certain pages from the site. By
default, code.Rmd
is excluded. You can exclude more pages. For example, if
your package does not have vignettes, you can specify
exclude: 'code.Rmd articles.Rmd'
(if you do not want code.Rmd
, either).
The cleanup
option allows you to run a command before publishing the site,
e.g., you can delete the .Rmd
source and .yml
config files.
Edit this chapter on GitHub
The litedown package can partially recognize the output format names
html_document
, html_vignette
, and pdf_document
from rmarkdown: Some
options of these output formats are mapped to litedown’s options internally.
For example, for an R Markdown document with the following output format:
output:
html_document:
toc: true
number_sections: true
anchor_sections: true
self_contained: false
It will be transformed to:
output:
html:
options:
toc: true
number_sections: true
embed_resources: false
meta:
css: ["default", "@heading-anchor"]
js: ["@heading-anchor"]
Note that not all rmarkdown options are supported, and not even all
supported options have exactly the same effects in litedown. The supported
options include:
toc
, toc_depth
, number_sections
, anchor_sections
, code_folding
, self_contained
, math_method
, css
, and includes
.
Edit this chapter on GitHub
To build package vignettes with litedown, first add this to the package
DESCRIPTION
file:
VignetteBuilder: litedown
Then use the vignette engine litedown::vignette
in the YAML metadata of a
.Rmd
or .md
vignette file:
vignette: >
%\VignetteEngine{litedown::vignette}
%\VignetteIndexEntry{Your vignette title}
%\VignetteEncoding{UTF-8}
The output format of a vignette can be specified in the output
field of the
YAML metadata, e.g., html
(for HTML vignettes) or latex
(for PDF vignettes).
If no output format is specified, the default is HTML.
The vignette file can be either .Rmd
or .md
. The former is processed by
litedown::fuse()
, and the latter is converted by litedown::mark()
. Please
avoid using the same base filename for two .Rmd
and .md
files, otherwise
their output files will overwrite each other.
Edit this chapter on GitHub
When https
resources needs to be embedded (via the embed_resources
option),
only these elements are considered:
<img src="..." />
<link rel="stylesheet" href="...">
<script src="..."></script>
Background images set in the attribute style="background-image: url(...)"
are
also considered. If an external CSS file contains url()
resources, these
resources will also be downloaded and embedded.
It’s quite simple to move an element into the margin using CSS. For example, the
.side-right
class in this article is roughly defined as:
.side-right {
width: 200px;
float: right;
margin-right: -200px
}
That basically means the width of the element is 200px and it floats to the
right. Now its right side will touch the right margin of its parent element (the
article body). What we need to do next is move it further to the right by 200px
(i.e., its width), which is done by the -200px
right margin. Remember, a
positive right margin in CSS moves an element to the left, and a negative right
margin moves it to the right.
At one night, as I was thinking about Pandoc Lua filters, an obvious fact suddenly came to my mind: suppose all I care about is HTML output, then the good old JavaScript can actually play a perfect role of Lua filters, because you can manipulate the DOM arbitrarily with JavaScript. ↩
In fact, xml,
man
, text
, and commonmark
output formats are
supported (thanks to the commonmark package), but perhaps they are not
very useful to average users. ↩
Cosmetic changes such as adding/deleting spaces and blank lines or
modifying comments do not matter. The code is considered unchanged if it
still parses to the same expressions via parse()
. For example,
parse(text = '1+1')
and parse(text = '1 + 1')
generate the same result. ↩
Lazy-loading means that the value of a variable will not be read from the cache until the variable is actually used somewhere. This can save the read time, especially if a large variable is not actually used later in the document. ↩
Perhaps Paul Murrell is the only person in the world who uses the
.Rhtml
format. I wonder if anyone uses any of the rest of formats, except
for .Rnw
and .Rmd
. ↩
At least it was not quite friendly to Norwegian until YAML 1.2. ↩
TAML is a good friend of Norwegian. ↩
Most people may want to use 2 or 4 spaces. Avoid 8, because that has been reserved for a VIP in the R club, Roger Peng. You may also choose to use tabs. If you really want to, you can mix tabs with spaces (then \(n\) is the total number of them). If people tell you that you are crazy by mixing up things, don’t listen and never follow. TAML is human-friendly and respects the space right of all human beings. Guess what happens if you indent one line by 2 spaces and the next line by 3 spaces when you meant to use 4 spaces? ↩
The specific number doesn’t matter, as long as it’s a unique
footnote number in the document. For example, the first footnote can be
[^100]
and the second can be [^64]
. Eventually they will appear as [1]
and [2]
. If you use the RStudio visual editor to edit Markdown documents,
the footnote numbers will be automatically generated and updated when new
footnotes are inserted before existing footnotes. ↩
Remember that you can cache time-consuming code chunks to make the rendering faster. ↩