diff --git a/preview/pr123/.nojekyll b/preview/pr123/.nojekyll deleted file mode 100644 index 8b137891..00000000 --- a/preview/pr123/.nojekyll +++ /dev/null @@ -1 +0,0 @@ - diff --git a/preview/pr123/404.html b/preview/pr123/404.html deleted file mode 100644 index 004d623e..00000000 --- a/preview/pr123/404.html +++ /dev/null @@ -1,128 +0,0 @@ - - -
- - - - -CODE_OF_CONDUCT.md
- We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation.
-We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
-Examples of behavior that contributes to a positive environment for our community include:
-Examples of unacceptable behavior include:
-Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
-Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
-This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
-Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at garrick@adenbuie.com. All complaints will be reviewed and investigated promptly and fairly.
-All community leaders are obligated to respect the privacy and security of the reporter of any incident.
-Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
-Community Impact: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
-Consequence: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
-Community Impact: A violation through a single incident or series of actions.
-Consequence: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
-Community Impact: A serious violation of community standards, including sustained inappropriate behavior.
-Consequence: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
-Community Impact: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
-Consequence: A permanent ban from any sort of public interaction within the community.
-This Code of Conduct is adapted from the Contributor Covenant, version 2.1, available at https://www.contributor-covenant.org/version/2/1/code_of_conduct.html.
-Community Impact Guidelines were inspired by [Mozilla’s code of conduct enforcement ladder][https://github.com/mozilla/inclusion].
-For answers to common questions about this code of conduct, see the FAQ at https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations.
-YEAR: 2023 -COPYRIGHT HOLDER: Garrick Aden-Buie -- -
LICENSE.md
- Copyright (c) 2023 Garrick Aden-Buie
-Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
-The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
-THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-Loading epoxy adds four new knitr
-engines, or chunk types. Each type lets you intermix text with R
-code or data (expr
in the table below), and each is geared
-toward a different output context.
Engine | -Output Context | -Delimiter | -
---|---|---|
epoxy |
-all-purpose markdown | -{expr} |
-
epoxy_html |
-HTML | -{{expr}} |
-
epoxy_latex |
-LaTeX | -<<expr>> |
-
whisker |
-all-purpose | -mustache -template language | -
⚠️ Caution: Previously, epoxy provided a
-glue
engine, but this conflicts with a similar chunk engine
-by the glue package. You can
-update existing documents to use the epoxy
engine, or you
-can explicitly use epoxy’s glue
chunk by including the
-following in your setup chunk.
To use epoxy in your R Markdown document, create a new chunk using -the engine of your choice. In that chunk, write in markdown, HTML, or -LaTeX as needed, wrapping R expressions inside the delimiters for the -epoxy chunk.
-
- ```{epoxy}
- The average speed of the cars was **{mean(cars$speed)} mph.**
- But on average the distance traveled was only _{mean(cars$dist)}_. ```
The average speed of the cars was 15.4 mph. But on -average the distance traveled was only 42.98 ft.
-epoxy
is built around glue::glue()
, which
-evaluates the R expressions in the { }
and inserts the
-results into the string. The chunk above is equivalent to this call to
-glue::glue()
:
-glue::glue("The average speed of the cars was **{mean(cars$speed)} mph**.
-But on average the distance traveled was only _{mean(cars$dist)} ft_.")
-#> The average speed of the cars was **15.4 mph**.
-#> But on average the distance traveled was only _42.98 ft_.
One immediate advantage of using epoxy
instead of
-glue::glue()
is that RStudio’s autocompletion feature works
-inside epoxy
chunks! Typing cars$
in the chunk
-will suggest the columns of cars
.
epoxy provides inline transformations inspired by cli’s inline
-markup. This transformer is enabled by default in
-epoxy()
, epoxy_html()
and
-epoxy_latex()
and their respective knitr chunk engines.
Here’s an example using a small list containing data about a
-movie
(expand the section below to see the full code for
-movie
). We can use the inline transformer to format the
-replacement text as we build up a description from this data.
- ```{epoxy}
- The movie {.emph {.titlecase movie$title}}
- was released in {.strong movie$year}.
- It earned {.dollar movie$domgross}
- with a budget of {.dollar movie$budget},
- and it features movie stars
- {.and movie$actors}. ```
-The movie Back to the Future Part II was released in -1989. It earned $118,450,002 with a budget of -$40,000,000, and it features movie stars Michael J. Fox, Christopher -Lloyd, Lea Thompson, and Thomas F. Wilson. --
Read more about inline transformations in
-?epoxy_transform_inline
.
You can use the epoxy_transform_wrap()
with the
-epoxy_transform
chunk option to wrap the evaluated R
-expression in formatting or templating text. Or you can use the pre-set
-epoxy_transform_bold()
,
-epoxy_transform_italic()
, or
-epoxy_transform_code()
transformers or with
-epoxy_transform()
.
```{epoxy, .transformer = epoxy_transform("bold")}
-All cars stopped between {min(cars$dist)} and {max(cars$dist)} feet
-from a starting speed of {min(cars$speed)}---{max(cars$speed)}
-```
-All cars stopped between 2 and 120 -feet from a starting speed of 4—120 -mph.
-Unlike inline R code, the epoxy
chunks are vectorized.
-This can be something to watch out for or it can be an advantage:
```{epoxy}
-{1:4}. "{letters[1:4]}" is for {c("apple", "banana", "coconut", "donut")}
-```
-You can collapse fields automatically using the
-epoxy_transform_collapse()
transformer. You can then choose
-how vectors are collapsed by adding *
, &
-or |
to the end of the expression.
*
collapses with commas,
-e.g. {letters[1:3]*}
.&
collapses with commas and adds
-" and "
between the last two items|
collapses with commas and adds " or "
-between the last two items.```{epoxy, .transformer = epoxy_transform("collapse")}
-- The first three letters are {letters[1:3]*}.
-- When capitalized, they are {LETTERS[1:3]&}.
-- They're indexed by {1:3|}.
-```
-You can change the separator between entries and between the last
-entry using the sep
, last
and the
-_and
and _or
specific arguments of the
-epoxy_transform_collapse()
function.
It’s also possible to create a reusable template. Use the
-ref.label
chunk option to reuse a template using the values
-in the .data
chunk option, which can be a list or data
-frame.
-mpg <- data.frame(
- manufacturer = c("Chevrolet", "Dodge", "Ford"),
- model = c("Malibu", "Caravan", "Expedition"),
- cty = c(19, 7, 11),
- hwy = c(27, 24, 17)
-)
```{epoxy car-name, eval=FALSE}
-- A {manufacturer} {model} gets {cty} city and {hwy} highway miles per gallon.
-```
-
-```{epoxy ref.label="car-name", .data = mpg}
-```
-Sometimes the epoxy
engine doesn’t quite deliver the
-template power you need. In these cases, you can use the
-whisker
engine instead.
```{r}
-contestant <- list(name = "R User", value = 1000, taxed = 600, in_ca = TRUE)
-```
-
-```{whisker .data = contestant, echo=FALSE}
-Hello {{name}}:
-You have just won ${{value}}!
-{{#in_ca}}
-Well, ${{taxed}}, after taxes.
-{{/in_ca}}
-```
-
-contestant <- list(name = "R User", value = 1000, taxed = 600, in_ca = TRUE)
Hello R User: You have just won $1000! Well, $600, after taxes.
-The epoxy
chunk engine can be used in any output format.
-In practice, it works best in markdown (i.e. generally in R Markdown or
-Quarto)
- ```{epoxy, .data = mpg}
- - **{manufacturer}** _{model}_ ```
where it renders as:
-- **Chevrolet** _Malibu_
-- **Dodge** _Caravan_
-- **Ford** _Expedition_
-If you’re writing for an HTML or LaTeX output, however, you may need
-to write literal HTML or LaTeX in your document. With the
-epoxy
chun, you’d need to escape any {
or
-}
in your text by doubling them, otherwise the content
-within will be treated as a template expression. To avoid this friction,
-epoxy provides two additional chunk engines, epoxy_html
for
-writing raw HTML and epoxy_latex
for writing raw LaTeX.
Use the epoxy_html
block to epoxy (glue) R and HTML
-together. The output is raw HTML. By
-default, expressions in these types of blocks are wrapped in
-{{
and }}
, like whisker templates above.
- <ul>
- ```{epoxy_html, .data = mpg}
- <li><strong>{{manufacturer}}</strong> <em>{{model}}</em></li>
- ``` </ul>
<ul>
-```{=html}
- <li><strong>Chevrolet</strong> <em>Malibu</em></li>
- <li><strong>Dodge</strong> <em>Caravan</em></li>
- <li><strong>Ford</strong> <em>Expedition</em></li>
-```
-</ul>
-Notice that the output is HTML but wrapped in a pandoc raw -html block, which tells pandoc that the content is HTML that -shouldn’t be modified1. It also means that the output of the chunk -will only -be included in HTML documents.
-If your epoxy_html
block is contained within another a
-raw html block, or if you want to force the output to appear, you can
-set the chunk option html_raw = FALSE
.
- ````{=html}
- <ul>
- ```{epoxy_html, .data = mpg, html_raw = FALSE}
- <li><strong>{{manufacturer}}</strong> <em>{{model}}</em></li>
- ```
- </ul> ````
epoxy_html
uses two custom transformers,
-epoxy_transform_inline()
and
-epoxy_transform_html()
, applying the html transformer
-before the inline transformer. With epoxy_transform_html()
-you can use element.class#id
syntax to wrap expressions in
-HTML elements (all parts are optional). Let’s use this syntax to place
-manufacturer and model in <strong>
and
-<em>
elements, each with a custom class.
- <ul>
- ```{epoxy_html, .data = mpg}
- <li>
- {{strong.car-make manufacturer}}
- {{em.car-model model}}
- </li>
- ``` </ul>
<ul>
-```{=html}
- <li>
- <strong class="car-make">Chevrolet</strong><strong class="car-make">Dodge</strong><strong class="car-make">Ford</strong>
- <em class="car-model">Malibu</em><em class="car-model">Caravan</em><em class="car-model">Expedition</em>
- </li>
-```
-</ul>
-Because the epoxy_transform_html()
transformer uses
-.<class>
to create
-<span class="class">
elements,
-epoxy_html()
also recognizes @<inline>
-to access the inline transformers. So
-{{.uppercase manufacturer}}
is assumed to be a CSS class
-and not an inline transformer class.
- ```{epoxy_html .data = mpg[1,]}
- {{.uppercase manufacturer}}
-
- {{@uppercase manufacturer}} ```
```{=html}
-<span class="uppercase">Chevrolet</span>
-
-CHEVROLET
-```
-Similarly, you can also use epoxy_latex
chunks to epoxy
-R and LaTeX together. Wrap expressions in these types of chunks with
-<<
and >>
.
- \begin{itemize}
- ```{epoxy_latex, .data = mpg}
- \item <<.strong manufacturer>> <<.emph model>> gets <<cty>> city and <<hwy>> highway miles per gallon.
- ``` \end{itemize}
In R Markdown knitting into a LaTeX output, this renders as:
-\begin{itemize}
-```{=latex}
-\item \textbf{Chevrolet} \emph{Malibu} gets 19 city and 27 highway miles per gallon.
-\item \textbf{Dodge} \emph{Caravan} gets 7 city and 24 highway miles per gallon.
-\item \textbf{Ford} \emph{Expedition} gets 11 city and 17 highway miles per gallon.
-```
-\end{itemize}
-Note that, like epoxy_html
chunks,
-epoxy_latex
places the output in raw
-latex blocks. This behavior can be disabled by setting the chunk
-option latex_raw = FALSE
.
⚠️ Note: Prior to v1.0.0, epoxy used single
-<
and >
characters for expression
-delimiters in epoxy_latex()
chunks. This can lead to subtle
-but inescapable problems if you need to use these characters
-inside your expression. As a result, epoxy_latex()
-now uses <<
and >>
to delimit
-inline expressions.
epoxy isn’t just for reports and Shiny apps! You can use the
-epoxy()
function just like an epoxy
knitr
-chunk.
-movie <- list(
- year = 1989,
- title = "Back to the Future Part II",
- budget = 4e+07
-)
-
-epoxy(
- "The movie {.titlecase movie$title}",
- "was released in {movie$year}",
- "and was filmed with a budget of",
- "{.dollar movie$budget}.",
- .sep = "\n"
-)
-#> The movie Back to the Future Part II
-#> was released in 1989
-#> and was filmed with a budget of
-#> $40,000,000.
For HTML and LaTeX contexts, check out epoxy_html()
and
-epoxy_latex()
. These work just like epoxy()
,
-but use convenient defaults for HTML and LaTeX settings.
Shiny apps are a great way to -design interactive web applications, and epoxy includes several -functions to help you weave reactive data into your apps.
-Here are some ways you can use epoxy in your Shiny apps:
-Make the text portion of any element in your Shiny UI update -dynamically.
Weave reactive text into prose in your app.
Build powerful templates using the mustache templating -language.
Shiny already includes two reactive text outputs:
-shiny::uiOutput()
(a.k.a.
-shiny::htmlOutput()
) andshiny::textOutput()
.These are great for displaying reactive text in your app, but they -have some limitations:
-uiOutput()
tends to move your UI code into the
-server
function, making it harder to know the final
-structure of your UI.
textOutput()
is great for displaying reactive text,
-but it takes some work to get the spacing around the dynamic text
-just right.
In this article, we’ll learn how to use epoxy in Shiny apps and how -epoxy improves the experience of writing apps with dynamic text and -templates.
-Let’s start with an example Shiny app. It’s a simple but friendly app -that greets the user by name.
-
-library(shiny)
-
-ui <- fluidPage(
- textInput("name", "What's your name?"),
- p("Hello,", textOutput("greeting", inline = TRUE), "!")
-)
-
-server <- function(input, output) {
- output$greeting <- renderText(input$name)
-}
-
-shinyApp(ui, server)
This gets you pretty close to what you want, but you have to remember
-to include inline = TRUE
in textOutput()
.
-There’s also some extra space between the user’s name and the
-exclamation point that you’d probably like to get rid of1.
ui_epoxy_html()
-Here’s how to approach dynamic text with
-ui_epoxy_html()
:
Wrap a portion of your UI in ui_epoxy_html()
and
-give it an .id
.
Use {{ name }}
syntax to define fields where the
-dynamic text should go.
In your server code, assign render_epoxy()
to an
-output matching the UI’s .id
and pass in the reactive data
-as arguments with names matching the dynamic fields.
-library(shiny)
-library(epoxy)
-
-ui <- fluidPage(
- textInput("name", "What's your name?"),
- ui_epoxy_html( #<< Template wrapper
- .id = "greeting", #<< Unique ID
- p("Hello, {{name}}!") #<< Dynamic text
- ) #<<
-)
-
-server <- function(input, output) {
- output$greeting <- render_epoxy( #<< Connect to template
- name = input$name #<< Reactive data field
- )
-}
-
-shinyApp(ui, server)
Another advantage of using ui_epoxy_html()
over
-textOutput()
is that you can set default values that appear
-immediately while your app is loading or that are used when an error
-occurs.
In the next app, name
is set to "friend"
by
-default in ui_epoxy_html()
, and on the server side I’ve
-also added a validate()
call indicating that we need a name
-with at least 2 characters.
-library(shiny)
-library(epoxy)
-
-ui <- fluidPage(
- textInput("name", "What's your name?"),
- ui_epoxy_html(
- .id = "greeting",
- p("Hello, {{name}}!"),
- name = "friend"
- )
-)
-
-server <- function(input, output) {
- name <- reactive({
- validate(need(
- nchar(input$name) > 2,
- "Name must be more than 2 characters."
- ))
- input$name
- })
-
- output$greeting <- render_epoxy(
- name = name()
- )
-}
-
-shinyApp(ui, server)
If the user hasn’t yet entered a name of more than 2 characters, the
-text for the name
field will use the default value and will
-have a red squiggle below it. Hovering over the squiggle reveals the
-error message.
ui_epoxy_html()
-First, you can reference the same reactive value,
-e.g. {{ name }}
, as many times as you want in your
-template. This value can also go just about anywhere in your UI. as long
-as it’s okay to put a <span>
around the text2.
-library(shiny)
-library(epoxy)
-
-ui <- fluidPage(
- textInput("name", "What's your name?"),
- ui_epoxy_html(
- .id = "greeting",
- selectInput(
- inputId = "color",
- label = "What's your favorite color, {{ name }}?",
- choices = c("red", "green", "blue", "purple", "yellow")
- ),
- name = "friend"
- )
-)
-
-server <- function(input, output) {
- output$greeting <- render_epoxy(
- name = input$name
- )
-}
-
-shinyApp(ui, server)
You can use {{ <markup> <expr> }}
syntax3 from
-epoxy_html()
, which makes it possible to determine which
-HTML element and class is used to contain the dynamic text4. If you send an array
-of values to this reactive field, the tag is used as a template, making
-it easy to do things like dynamically update a list.
-library(shiny)
-library(epoxy)
-
-ui <- fluidPage(
- textInput("faves", "What are your favorite fruits?"),
- helpText("Enter a list of comma-separated fruits."),
- ui_epoxy_html(
- .id = "fruit_list",
- tags$ul("{{ li fruits }}"),
- fruits = "favorite fruits"
- )
-)
-
-server <- function(input, output) {
- fruits <- reactive({
- validate(need(
- nzchar(input$faves),
- "Please share your favorite fruits."
- ))
- fruits <- trimws(strsplit(input$faves, ",\\s*")[[1]])
- fruits[nzchar(fruits)]
- })
-
- output$fruit_list <- render_epoxy(
- fruits = fruits()
- )
-}
-
-shinyApp(ui, server)
Three more quick things about ui_epoxy_html()
:
It assumes that bare character strings are HTML, so you don’t
-have to worry about adding HTML()
all over the
-place.
The replacement text is not assumed to be HTML, by
-default, to save you from accidentally injecting unsafe HTML from user
-input into your app. If you’re very certain that a field will only
-contain safe HTML, you can mark it as safe for HTML with three braces,
-e.g. {{{ <expr> }}}
.
The replacement text is sent as bare text or HTML, making it more
-like textOutput()
than uiOutput()
. In
-particular, where uiOutput()
would allow you to send
-arbitary widgets based on htmlwidgets or
-htmltools, ui_epoxy_html()
only ever sends
-the bare text or HTML.
One of my favorite use cases for epoxy’s Shiny functions is to create
-a UI template that’s filled in by a row in a data frame. In this
-pattern, the app’s inputs are combined in a reactive expression that
-filters the data frame down to a single row. Then, that row is sent via
-render_epoxy()
to the UI, where it’s dynamically injected
-into the template UI.
Here’s a small example using epoxy’s built in bechdel
-data set, a small data set with the 10 highest-rated movies that pass
-the Bechdel
-test. In this app, the user picks a movie and the template below is
-filled out with information from the data set for that movie.
-library(shiny)
-library(epoxy)
-
-movie_choices <- bechdel$imdb_id
-names(movie_choices) <- bechdel$title
-
-ui <- fixedPage(
- selectInput("movie", "Pick a movie", choices = movie_choices),
- ui_epoxy_html(
- .id = "movie_info",
- p(
- "{{ em title }} was released",
- "in {{ strong year }}.",
- "It was directed by {{ director }}",
- "and was rated {{ rated }}."
- )
- )
-)
-
-server <- function(input, output, session) {
- movie <- reactive({
- # Use the inputs to filter a single row
- bechdel[bechdel$imdb_id == input$movie, ]
- })
-
- # Pass the reactive data frame to
- # the .list argument of render_epoxy()
- output$movie_info <- render_epoxy(.list = movie())
-}
-
-shinyApp(ui, server)
Notice that instead of passing named arguments for each field to
-render_epoxy()
, we pass the entire data frame to the
-.list
argument.
-render_epoxy(.list = movie())
You can use this same pattern with a list in a
-reactiveVal()
, a reactive()
that returns a
-data frame, a list or a list-like object, or a
-reactiveValues()
list. And .list
can coexist
-with named expressions.
-render_epoxy(
- name = input$name,
- .list = movie()
-)
If you want to build the entire list within a single reactive
-expression, set .list
equal to the expression, wrapped in
-braces:
-render_epoxy(.list = {
- list(
- name = input$name,
- age = input$age
- )
-})
If you’re using epoxy to write data-driven prose, you might want to
-use markdown for your templates, rather than writing in HTML.
-ui_epoxy_markdown()
is a version of
-ui_epoxy_html()
that uses markdown syntax instead of HTML
-syntax5.
Let’s revisit our movie app from the last example, but this time -using markdown for the template.
-
-library(shiny)
-library(epoxy)
-
-movie_choices <- bechdel$imdb_id
-names(movie_choices) <- bechdel$title
-
-ui <- fixedPage(
- selectInput("movie", "Pick a movie", choices = movie_choices),
- ui_epoxy_markdown(
- .id = "movie_info",
- "_{{ title }}_ was released",
- "in **{{ year }}**.",
- "It was directed by {{ director }}",
- "and was rated {{ rated }}."
- )
-)
-
-server <- function(input, output, session) {
- movie <- reactive({
- bechdel[bechdel$imdb_id == input$movie, ]
- })
-
- output$movie_info <- render_epoxy(.list = movie())
-}
-
-shinyApp(ui, server)
For an even more involved example, try the epoxy markdown example -app
-
-run_epoxy_example_app("ui_epoxy_markdown")
For more complex templates, you might want to use a template language -like Mustache. In R, we know -this syntax from the whisker package.
-ui_epoxy_mustache()
6 wraps the Mustache language, letting you
-blend typical shiny and htmltools UI with
-the mustache template.
When would you use ui_epoxy_mustache()
instead of
-ui_epoxy_html()
?
If your template variables are used as HTML attributes, e.g. in
-links or images (via the href
or src
-attributes).
If you want to use mustache’s conditional logic,
-e.g. {{#<expr>}} ... {{/<expr>}}
.
Let’s revist our favorite fruits example app from earlier.
-
-library(shiny)
-library(epoxy)
-
-ui <- fluidPage(
- textInput("faves", "What are your favorite fruits?"),
- ui_epoxy_mustache(
- id = "fruit_list",
- tags$ul(
- # If fruits is not empty, wrap into list items
- "{{#fruits}}",
- tags$li("{{.}}"),
- "{{/fruits}}",
- # If fruits is empty, show a help message
- "{{^fruits}}",
- tags$li(
- class = "text-muted",
- "Enter a list of comma-separated fruits."
- ),
- "{{/fruits}}"
- )
- )
-)
-
-server <- function(input, output) {
- fruits <- reactive({
- req(input$faves)
- fruits <- trimws(strsplit(input$faves, ",\\s*")[[1]])
- fruits[nzchar(fruits)]
- })
-
- output$fruit_list <- render_epoxy(
- fruits = fruits()
- )
-}
-
-shinyApp(ui, server)
This app use’s mustache’s conditional logic to show a help message -when no fruits are entered.
- -And it uses mustache’s looping syntax to show a list of fruits when -fruits are entered.
- -You can find a more detailed example in the epoxy mustache example -app.
-
-run_epoxy_example_app("ui_epoxy_mustache")
One important thing to note about ui_epoxy_mustache()
is
-that, unlike ui_epoxy_html()
, then entire template is
-re-rendered (in the browser) whenever a reactive source updates. So it’d
-be better to use smaller, localized templates than to wrap your entire
-app in ui_epoxy_mustache()
.
Use epoxy to blend data and prose with inline templating and formatting.
-Use epoxy for reactive templating and targeted updates in Shiny apps.
-Use epoxy in your R scripts, anywhere you've used glue.
-This vignette is heavily inspired by Tristan Mahr’s post Lists are -my secret weapon for reporting stats with knitr. Please read -his original for an excellent introduction on how to better organize -your data for inline reporting scenarios with lists. I’m going to borrow -several examples directly from that post.
-Both Tristan and Yihui Xie call inline reporting the act of
-interleaving R expressions in the prose of markdown text. When you click
-the Knit button or call
-rmarkdown::render()
to build your report,
-knitr
evaluates these R expressions, turns them into text
-and plugs them into your output.
The most common use case is for reporting descriptive statistics. To
-illustrate, I’ll use the Orange
-dataset which contains circumference measurements of 5 orange trees
-at 7 points in time.
Here is some R code we might use to summarize the Orange
-data:
And here are some lines we might include in a report about the growth -of these trees:
-
- ```{r setup, include = FALSE}
- library(epoxy)
- ```
-
- ```{epoxy}
- The dataset contains {nrow(Orange)} tree size measurements
- from {n_trees} trees at {n_timepoints} time points in the study. ```
The dataset contains 35 tree size measurements from 5 trees at 7 -timepoints in the study.
-With normal R Markdown inline
-reporting we would have written this in our .Rmd
file
-instead:
- The dataset contains `r nrow(Orange)` tree size measurements from `r n_trees` trees at `r n_timepoints` time points in the study.
The two forms are very similar, but the epoxy
chunk
-approach provides a few advantages, as we’ll discover in this
-vignette.
In the above example, we used normal variables that were available in -the global environment of our document. But a small structural change -can bring great benefits. It’s worth reading Tristan’s blog -post, but to steal his thunder: store your data in lists.
-We could, on the one hand, create variables named
-knitted_when
, knitted_where
and
-knitted_with
that all store facts about the knitting
-process. The knitted_
prefix is helpful as an aid to
-remember that these variables are related.
But you could store those three variables in a single object instead.
-Bundling everything into a list()
allows you to report the
-results by accessing the list elements by name with $
.
-knitted <- list(
- when = format(Sys.Date()),
- where = knitr::current_input(),
- with = format(utils::packageVersion("knitr")),
- doc_url = "https://rdrr.io/pkg/knitr/man/knit.html"
-)
- ```{epoxy}
- Report prepared on {knitted$when} from `{knitted$where}`
- with knitr version {knitted$with} {emo_ji('happy')}.
- Read more about [`knitr::knit()`]({knitted$doc_url}). ```
Report prepared on 2023-09-19 from inline-reporting.Rmd
-with knitr version 1.44 😆. Read more about knitr::knit()
.
This is still essentially equivalent to R Markdown’s inline R chunks.
-But epoxy
chunks include a .data
chunk
-argument, which allows us to reference items in the knitted
-list directly without having to use $
.
- ```{epoxy knitted-2, .data = knitted}
- Report prepared on {when} from `{where}`
- with knitr version {with} {emo_ji('happy')}.
- Read more about [`knitr::knit()`]({doc_url}). ```
Report prepared on 2023-09-19 from inline-reporting.Rmd
-with knitr version 1.44 😆. Read more about knitr::knit()
.
Note that we can still have arbitrary R code in epoxy inline
-expressions: the emo_ji()
function — a vignette-safe
-version of emo::ji()
— exists in my global environment.
Suppose we have some model results that we’ve prepared into a table -(for details, see Tristan’s blog -post). These results summarize a linear mixed model estimating -population averages for trees grown in several ozone conditions. I’ve -copied the resulting data frame into this vignette to avoid taking extra -dependencies for this vignette.
-
-text_ready <-
- data.frame(
- term = c("intercept", "hund_days", "ozone", "hund_days_ozone"),
- estimate = c("4.25", "0.34", "−0.14", "−0.04"),
- se = c(0.131, 0.013, 0.158, 0.015),
- ci = c("[4.00, 4.51]", "[0.31, 0.36]", "[−0.45, 0.17]","[−0.07, −0.01]"),
- stringsAsFactors = FALSE
- )
We can use split()
to make a list of data frames that we
-can index by the values in the term
column.
-stats <- split(text_ready, text_ready$term)
We now have a list of one-row dataframes:
-
-str(stats)
-#> List of 4
-#> $ hund_days :'data.frame': 1 obs. of 4 variables:
-#> ..$ term : chr "hund_days"
-#> ..$ estimate: chr "0.34"
-#> ..$ se : num 0.013
-#> ..$ ci : chr "[0.31, 0.36]"
-#> $ hund_days_ozone:'data.frame': 1 obs. of 4 variables:
-#> ..$ term : chr "hund_days_ozone"
-#> ..$ estimate: chr "−0.04"
-#> ..$ se : num 0.015
-#> ..$ ci : chr "[−0.07, −0.01]"
-#> $ intercept :'data.frame': 1 obs. of 4 variables:
-#> ..$ term : chr "intercept"
-#> ..$ estimate: chr "4.25"
-#> ..$ se : num 0.131
-#> ..$ ci : chr "[4.00, 4.51]"
-#> $ ozone :'data.frame': 1 obs. of 4 variables:
-#> ..$ term : chr "ozone"
-#> ..$ estimate: chr "−0.14"
-#> ..$ se : num 0.158
-#> ..$ ci : chr "[−0.45, 0.17]"
Now we can write up our results with inline reporting:
-```{epoxy}
-The average log-size in the control condition was
-{stats$intercept$estimate} units,
-95% Wald CI {stats$intercept$ci}.
-There was not a statistically clear difference between the
-ozone conditions for their intercepts (day-0 values),
-*B* = {stats$ozone$estimate}, {stats$ozone$ci}.
-For the control group, the average growth rate was
-{stats$hund_days$estimate} log-size units per 100 days,
-{stats$hund_days$ci}. The growth rate for
-the ozone treatment group was significantly slower,
-*diff* = {stats$hund_days_ozone$estimate},
-{stats$hund_days_ozone$ci}.
-```
-The average log-size in the control condition was 4.25 units, 95% -Wald CI [4.00, 4.51]. There was not a statistically clear difference -between the ozone conditions for their intercepts (day-0 values), -B = −0.14, [−0.45, 0.17]. For the control group, the average -growth rate was 0.34 log-size units per 100 days, [0.31, 0.36]. The -growth rate for the ozone treatment group was significantly slower, -diff = −0.04, [−0.07, −0.01].
-What’s extra neat about epoxy — and not readily apparent if you’re
-reading this vignette — is that RStudio’s autocomplete feature kicks in
-when you type stats$
inside a braced expression
-{ }
.
Actually, because the IDE doesn’t know about the epoxy
-knitr engine, the autocomplete tries to help out on every word. It’s
-typically easy to ignore the suggestions for words that are part of the
-prose, and it’s usually outweighed by the usefulness of being able to
-autocomplete the names in your data structures.
Note that you don’t need to write your entire document or even
-paragraph inside an epoxy
chunk; you can wrap only the
-data-heavy parts as needed.
- There was not a statistically clear difference between the
- ozone conditions for their intercepts (day-0 values),
- ```{epoxy}
- *B* = {stats$ozone$estimate}, {stats$ozone$ci}.
- ```
- The growth rate for the ozone treatment group was significantly slower,
- ```{epoxy}
- *diff* = {stats$hund_days_ozone$estimate}, {stats$hund_days_ozone$ci}. ```
There was not a statistically clear difference between the ozone -conditions for their intercepts (day-0 values), B = −0.14, -[−0.45, 0.17]. The growth rate for the ozone treatment group was -significantly slower, diff = −0.04, [−0.07, −0.01].
-Occasionally you may need to re-use the same phrase or document -structure but for different slices of your data.
-Suppose we summarize the orange tree growth (normally I would use a
-combination of dplyr::group_by()
and
-dplyr::summarize()
here.)
-summarize_tree_growth <- function(tree) {
- tree <- Orange[Orange$Tree == tree, ]
- tree <- data.frame(
- tree = tree$Tree[1],
- age_range = diff(range(tree$age)),
- circumference_first = tree$circumference[1],
- circumference_last = tree$circumference[nrow(tree)]
- )
- tree$growth_rate <- with(tree, (circumference_last - circumference_first) / age_range)
- tree
-}
-
-orange_summary <- lapply(1:5, summarize_tree_growth)
-orange_summary <- do.call(rbind, orange_summary)
-orange_summary
-#> tree age_range circumference_first circumference_last growth_rate
-#> 1 1 1464 30 145 0.07855191
-#> 2 2 1464 33 203 0.11612022
-#> 3 3 1464 30 140 0.07513661
-#> 4 4 1464 32 214 0.12431694
-#> 5 5 1464 30 177 0.10040984
epoxy
chunks, like glue::glue()
, are
-vectorized, so if we find ourselves needing to repeat the same thing
-over and over again, we can use this feature to our advantage.
- A quick recap of the growth observed in the orange trees:
-
- ```{epoxy .data = orange_summary}
- - Tree number {tree} started out at {circumference_first}mm and,
- over {age_range} days, grew to be {circumference_last}mm. ```
A quick recap of the growth observed in the orange trees:
-By using knitr’s
-reference labels feature, and the epoxy
-.data
chunk option we saw above, you can create an epoxy
-template that you can re-use like a parameterized chunk.
You start by creating a labelled epoxy
chunk with
-eval = FALSE
- ```{epoxy average-growth, eval=FALSE}
- an average of {signif(growth_rate * 7, 2)}mm per week. ```
that you can later use in your prose by referencing the chunk with
-ref.label
and providing a different slice of data via the
-.data
chunk option.
- The fourth tree was the largest tree at the end of the study, growing
- ```{epoxy ref.label="average-growth", .data = summarize_tree_growth(4)}
- ```
- Meanwhile, the smallest tree was the third, which grew at
- ```{epoxy ref.label="average-growth", .data = summarize_tree_growth(3)} ```
The fourth tree was the largest tree at the end of the study, growing -an average of 0.87mm per week. Meanwhile, the smallest tree was the -third, which grew at an average of 0.53mm per week.
-