I am working on a shiny app that is intended to help students. It generates solution for each student with some equations using student specific input values.
The problem is that the MathJax equations are rendered as regular text, see the minimal example and this image:
Any idea how dynamic MathJax elements could be rendered properly?
My current approach:
use an rmarkdown document (solu_template.Rmd) with input parameter (params$student) to create student specific markdown file;
render the markdown file and include it in the ui.
Reactive, dynamically changing MathJax elements are feasible as this example shows. However, I would like to use more complex files and keep the convenience of preparing the solution template as an rmarkdown file.
Additional attempts:
direct use of the html file generated by render(), but I do not know how to include it in the ui as a dynamically changing component;
read the generated html file with readLines() and use htmlOutput to display it; no success, just a pile of code.
So, using this approach the question is how to render/display a dynamically changing html file?
All ideas, suggestions are welcome!
app.R
library(shiny)
library(markdown)
shinyApp(
ui = fluidPage(
selectInput("student", "Student:",
choices = c("Student1", "Student2", "Student3")),
actionButton("show_solu", "Run!"),
hr(),
withMathJax(),
htmlOutput("solu")
),
server = function(input, output, session) {
output$solu <- eventReactive(input$show_solu, {
rmarkdown::render("solu_template.Rmd",
quiet = F, clean = F,
params = list(student = input$student))
solu <- renderMarkdown("solu_template.knit.md")
}
)
}
)
solu_template.Rmd
---
title: "Solution"
params:
student: Student1
output:
html_document:
theme: readable
---
```{r, echo = FALSE}
S = list(Student1 = 1, Student2 = 2, Student3 = 3)
s = S[[params$student]]
```
## Heading
Student dependent initial value:
$s = `r s`$
Some nice reasoning which yields to this equation:
$R = s^2 + \sqrt{2} = `r signif(s^2 + sqrt(2), 3)`$
Here is the solution with the renderMarkdown() approach:
The following line should be added to the html code generated by renderMarkdown(), this way the browser will know that the output should be rendered considering MathJax elements:
"<script>MathJax.Hub.Queue(["Typeset", MathJax.Hub]);</script>"
Regarding the above example, this should be added to the end of the server function of app.R:
solu = paste(solu, "<script>MathJax.Hub.Queue([\"Typeset\", MathJax.Hub]);</script>")
If you can use knitr I don't see why this wouldn't work. I haven't used 'dynamic-math' but I use a fair amount of notation in Rmd files rendered inside a shiny app used for students. Take a look at the repos linked below and let me know if you have any questions.
https://github.com/vnijs/shiny-site
https://github.com/vnijs/quizr
Related
I want to provide the user a convenient way to define the input file. For this I am using the parameters functionality in markdown. If I "knit with parameters" I get asked for the input file.
Is there any chance to retrieve the file name? Because I am creating during the markdown some different files and I would use the filename of the input file as a prefix. So far, the file gets uploaded in a temp directory and there, the original file name is lost.
How can I get the file name and location via drop down menu into my markdown document? I don't want the user to write the path and filename manually.
---
title: "Untitled"
date: "11/16/2021"
output: html_document
params:
date_file:
label: "date file"
value: 'dates.tsv'
input: file
---
```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = TRUE)
```
## R Markdown
Filename: `r params$date_file`
You could have users select a file in the rendered document by embedding a Shiny application. The caveat is that all expressions involving dependencies of the user's selection have to be wrapped inside of reactive(). That is obviously not optimal if you are trying to teach R, but in case it helps or inspires a better answer, here is an example:
## Create a TSV file for testing
cat("x\ty\n1\t2\n3\t4\n", file = "test.tsv")
---
title: "Untitled"
output: html_document
runtime: shiny
---
```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = TRUE)
```
Users can select a file by interacting with an embedded Shiny application.
```{r ui, echo=FALSE}
inputPanel(
fileInput("file", label = "Select a TSV file:", accept = ".tsv")
)
```
User input is stored in a named list-like object `input`,
but this object can only be accessed inside of a _reactive context_.
```{r test1, error=TRUE}
input$file$name
## Name of file selected by user ... _not_ an absolute or relative path
fn <- reactive(input$file$name)
fn
## Absolute path of temporary copy of file
dp <- reactive(input$file$datapath)
dp
```
Don't be misled by how `fn` and `dp` are printed. They are not
strings, but _reactive expressions_. As as a result, they, too,
must be handled inside of a reactive context.
```{r test2}
class(fn)
reactive(class(fn()))
```
Some more examples:
```{r test3, error=TRUE}
## Define reactive data
dd <- reactive(read.table(file = dp(), sep = "\t", header = TRUE))
## Do stuff with it
reactive(names(dd()))
reactive(nrow(dd()))
## Write object to file in _working directory_
reactive(saveRDS(dd(), file = sub("tsv$", "rds", fn())))
```
As alternative to using the Shiny runtime, you can also use Shiny Gadgets in combination with customized Knit button behavior (To my understanding, this is largely what's happening when you use 'Knit with Parameters')
You'll need two things: a function to run the gadget, a function to run when knitting.
The gadget is essentially a Shiny app, but you can use specialized UI elements from miniUI. As a mini-app, there's a lot more you can do, but here's a basic implementation.
library(shiny)
library(miniUI)
get_file_param <- function(){
ui <- miniPage(
miniContentPanel(
fileInput("dateFile", "Date File")
# ...Other UI elements to collect other parameters...
),
miniTitleBar(
NULL,
right = miniButtonBlock(
miniTitleBarCancelButton(),
miniTitleBarButton("done", "Done", primary = TRUE))
)
)
server <- function(input, output, session){
# Handle the Done button being pressed
observeEvent(input$done, {
# Return the full file info data.frame. See ?shiny::fileInput
stopApp(input$dateFile)
})
}
runGadget(
app = ui,
server = server,
viewer = paneViewer() #dialogViewer("Knit with Parameters - Custom UI")
)
}
The knit function will call the gadget and then use its output in a call to rmarkdown::render
knit_with_file <- function(input, ...){
fileInfo <- get_file_param()
rmarkdown::render(
input,
params = list(
date_file = list(name = fileInfo$name, datapath = fileInfo$datapath)
),
envir = globalenv()
)
}
To customize the knit button, you provide a function to the knit field in the document YAML header. The R Markdown cookbook suggest you keep this in a package, but I put both of the above functions in file ("knitFx.R"), which the rmarkdown document will source.
---
title: "Untitled"
date: "11/16/2021"
output: html_document
params:
date_file: 'dates.tsv'
knit: (function(input, ...) {
source("knitFx.R")
knit_with_file(input, ...)
})
---
```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = TRUE)
```
## R Markdown
**Original Name of Uploaded File**: *`r params$date_file$name`*
**Local Temp File Path**: *`r params$date_file$datapath`*
When the user clicks "Knit", a UI will be displayed to choose the file. Based on implementation above, the name and datapath will then be available to use in the rmarkdown document.
Here's the rendered HTML document:
I am getting crazy with this small reproducible Shiny app:
Basically 3 steps:
I have an input$text which the user can chose
The user triggers an R file create_text.R transforming this text, creating a my_text string. (IRL it is basically a download and data preparation step)
The user triggers the render of an R Markdown where my_text value is printed
My base code looks like:
app.R
library(shiny)
ui <- fluidPage(
selectInput(inputId = "text",
label = "Choose a text",
choices = c("Hello World!", "Good-bye World!")),
actionButton("create_text", "Prepare text"),
downloadButton("report", "Render markdown"))
)
server <- function(input, output) {
observeEvent(input$create_text, {
text_intermediate <- input$text
source('create_text.R')
})
output$report <- downloadHandler(
filename = "report_test.html",
content = function(file) {
rmarkdown::render(input = "report_test.Rmd",
output_file = file)
})
}
shinyApp(ui, server)
create_text.R
my_text <- paste("transfomation //", text_intermediate)
report_test.Rmd
---
title: "My title"
output: html_document
---
```{r}
my_text
```
My problem is the intermediate step (2.), probably because I am confused between environments.
If I run source('create_text.R', local = FALSE), it fails because the R file is run from an empty environment, then does not recognize text_intermediate.
# Warning: Error in paste: object 'text_intermediate' not found
On the opposite way, if I run source('create_text.R', local = TRUE), the created my_text string is not "saved" for the next of the shiny app, then the Rmd cannot be rendered since my_text is not found.
# Warning: Error in eval: object 'my_text' not found
What I have tried:
Two ugly solutions would be:
do not use an intermediate R file and have the whole code inside the app but it will make it very unreadable
or even more ugly, only use hard assigning <-- in the R file, like my_text <<- paste("transfomation //", text_intermediate)
Playing with the env argument of the render() function dit not help neither.
Lastly, starting from scratch I would have used reactive values everywhere, but both my R and Rmd files are very big and "finished", and it would be difficult to adapt the code.
Any help ?
OK, at the end I bypassed the problem with this unelegant solution:
I added a block of code after running the external R script, which stocks all created objects into the global environment. This way, those objects are callable later on in the server function. This allows to go without eventReactive(). Not very pleasant but works.
For this, I use assign within a lapply. In my example, it would be equivalent to write: my_text <<- my_text. The lapply allows to do it for all objects.
observeEvent(input$create_text, {
text_intermediate <- input$text
source('create_text.R')
lapply(X = (ls()), FUN = function(object) {assign(object, get(object), envir = globalenv())})
})
Is there a way to automatically make text color of errors red in R Markdown without manually editing the HTML later.
---
title: ""
---
#### Example 1
```{r e1, error = TRUE}
2 + "A"
```
#### Example 2
```{r e2, error = TRUE}
2 + 2
```
In the above code, output of Example 1 would have to be red. Currently, I edit the generated HTML (add style="color:red;" to the appropriate tag) but I am wondering if there is an automatic way. Assume that it is not known before knitting whether the code will generate error.
1. Use a knitr hook
The preferred solution is to use the output hook for errors:
```{r}
knitr::knit_hooks$set(error = function(x, options) {
paste0("<pre style=\"color: red;\"><code>", x, "</code></pre>")
})
```
Output hooks in general allow us to control the output of different parts of our R code (the whole chunk, the source code, errors, warnings, ...). For details check https://yihui.name/knitr/hooks/#output-hooks.
2. Quick and dirty solution using JS/jQuery
And this is my "quick and dirty" solution using jQuery/Javascript. Just add it beneath the YAML header.
Might not be bulletproof, since it checks for error messages using the string "Error" which might occur in other applications as well.
<script type="text/javascript">
$(document).ready(function() {
var $chks = $("pre:not(.r) > code");
$chks.each(function(key, val) {
cntnt = $(this).html();
if (cntnt.indexOf("Error") != -1) {
$(this).css('color', 'red');
}
})
})
</script>
I stumbled here because I had the same question but for PDF output rather than HTML.
It turns out combining #Martin Schmelzer's Solution with some hints from #Yihui Xie found here helps to achieve the same behavior in a PDF output.
Add \usepackage{xcolor} to your YAML header and the following chunk to your .Rmd file.
```{r}
color_block = function(color) {
function(x, options) sprintf('\\color{%s}\\begin{verbatim}%s\\end{verbatim}',
color, x)
}
knitr::knit_hooks$set(error = color_block('red'))
```
The result is red error messages like
So I'm trying to write an html R markdown document with interactive shiny bits that allow the user to edit a graph and then download the results to a pdf. However, there is something catastrophically wrong with the way that I'm trying to do this because as soon as the html starts, it overwrites the original markdown file with the contents of the pdf - turning it into complete gibberish right in the editor.
I doubt that I've found a completely new way to fail at R but I haven't been able to find where anybody else has had this issue. Additionally, I've looked over the shiny reference material and I'm just going in circles at this point, so any help would be greatly appreciated.
I'm using Rstudio 1.0.44, rmarkdown 1.2 and shiny 0.14.2. A small (not)working example:
---
title: "Minimum Failing Example"
author: "wittyalias"
date: "December 5, 2016"
output: html_document
runtime: shiny
---
```{r echo = FALSE}
library(ggplot2)
today <- Sys.Date()
inputPanel(downloadButton("dnld", label = "Download pdf"))
renderPlot({
# Example code from http://www.cookbook-r.com/Graphs/Multiple_graphs_on_one_page_(ggplot2)/
p1 <<- ggplot(ChickWeight, aes(x=Time, y=weight, colour=Diet, group=Chick)) +
geom_line() +
ggtitle("Growth curve for individual chicks")
p1
})
reactive({
fname <- paste0("Chick Weight - ", today, ".pdf")
output$dnld <- downloadHandler(filename = fname,
content = makethepdf(file))
makethepdf <- function(fname) {
pdf(fname,
width = 14,
height = 8.5)
p1
dev.off()
}
})
```
EDIT: To be clear: I want the user to be able to download multiple pages of graphs, some of which will have different formatting. The user won't be downloading just a pdf version of the markdown document.
This happens because reasons I weren't able to identify makethepdf runs with the file = [name of the file]. Insert a print(fname) to see. The download handler isn't supposed to be inside an observer though. You need to have it outside on its own. I also failed to make pdf() dev.off() combination work for some reason so here's a working version below.
output$dnld = downloadHandler(filename = paste0("Chick Weight - ", today, ".pdf"),
content = function(file){
ggsave(file, plot = p1, width = 14, height = 8.5)
})
Use tempfile() and tempdir() to create a temporary file:
output$downloadReport = downloadHandler(
filename = function() {
normalizePath(tempfile("report_", fileext = ".docx"), winslash = "/")
},
content = function(file) {
out = rmarkdown::render("./report.Rmd",
output_file = file,
output_dir = tempdir(),
output_format = "pdf_document",
intermediates_dir = tempdir(),
envir = new.env(),
params = list( fontSize = 10)
)
})
I usually use a separate .Rmd template for my downloaded reports as the layout and text are usually similar but not identical to what works in an app.
I also find using parameters is a convenient way to pass input settings from my app to my report. See this RStudio post for details
Alright, so there are a number of problems with my code, but using some of the suggestions in the other answers I've been able to work it out.
The primary problem with this little document is that content in the downloadHandler is a function, but in my code I set content equal to the result of a function call. It looks like when the shiny app is first run it compiles content, thinking that it is a function, but actually ends up calling the function. It sends file as an arguement, which doesn't seem to exist except as a base function. Calling makethepdf with just file throws an error when I use it in the console, but for whatever reason in this app it just goes with the call, apparently with file = [name of the .Rmd] (just as OganM said).
To fix, change this:
output$dnld <- downloadHandler(filename = fname,
content = makethepdf(file))
to
output$dnld <- downloadHandler(filename = fname,
content = makethepdf)
To be clear: this code does not overwrite the .Rmd file if content calls makethepdf with any argument other than file. For instance, content = makethepdf(fnm)) causes the download button to display an object not found error and content = makethepdf(fname)) causes the download button to throw an attempt to apply non-function error when pressed.
I have been using rmarkdown/knitr's knit to html capability to generate html code for some blogs. I've found it extremely helpful and convenient, but have been running into some problems lately with file size.
When I knit a script that has graphics that use shapefiles or ggmap images, the html file gets too big for the blog host to make sense of it (I've tried with both blogger and wordpress). I believe this has to do with the relatively large data.frames/files that are the shapefiles/ggmap being put into html form. Is there anything I can do to get a smaller html file that can be parsed by a blog host?
For reference, the html output from an rmarkdown script with one graphic using a ggmap layer, a layer of shapefiles and some data is 1.90MB, which is too big for blogger or wordpress to handle in html input. Thanks for any ideas.
Below are 3 different options to help you reduce the file size of HTML files with encoded images.
1. Optimize an existing HTML file
You can run this Python script on an existing HTML file. The script will:
decode the base64 encoded images
run pngquant to optimize the images
re-encode the optimized images as base64
Usage:
python optimize_html.py infile.html
It writes output to infile-optimized.html.
2. Use the built-in knitr hook for optimizing PNG images
knitr 1.15 includes a hook called hook_optipng that will run the optipng program on generated PNG files to reduce file size.
Here is a .Rmd example (taken from: knitr-examples/035-optipng.Rmd):
# 035-optipng.Rmd
This demo shows you how to optimize PNG images with `optipng`.
```{r setup}
library(knitr)
knit_hooks$set(optipng = hook_optipng)
```
Now we set the chunk option `optipng` to a non-`NULL` value,
e.g. `optipng=''`, to activate the hook. This string is passed to
`optipng`, so you can use `optipng='-o7'` to optimize more heavily.
```{r use-optipng, optipng=''}
library(methods)
library(ggplot2)
set.seed(123)
qplot(rnorm(1e3), rnorm(1e3))
```
3. Write your own knitr hook for any image optimizer
Writing your own hook is also quite easy, so I wrote a hook that calls the pngquant program. I find that pngquant runs faster, and the output files are smaller and look better.
Here is a .R example that defines and uses hook_pngquant (taken from this gist).
#' ---
#' title: "pngquant demo"
#' author: "Kamil Slowikowski"
#' date: "`r Sys.Date()`"
#' output:
#' html_document:
#' self_contained: true
#' ---
#+ setup, include=FALSE
library(knitr)
# Functions taken from knitr/R/utils.R
all_figs = function(options, ext = options$fig.ext, num = options$fig.num) {
fig_path(ext, options, number = seq_len(num))
}
in_dir = function(dir, expr) {
if (!is.null(dir)) {
owd = setwd(dir); on.exit(setwd(owd))
}
wd1 = getwd()
res = expr
wd2 = getwd()
if (wd1 != wd2) warning(
'You changed the working directory to ', wd2, ' (probably via setwd()). ',
'It will be restored to ', wd1, '. See the Note section in ?knitr::knit'
)
res
}
is_windows = function() .Platform$OS.type == 'windows'
in_base_dir = function(expr) {
d = opts_knit$get('base.dir')
if (is.character(d) && !file_test('-d', d)) dir.create(d, recursive = TRUE)
in_dir(d, expr)
}
# Here is the code you can modify to use any image optimizer.
hook_pngquant <- function(before, options, envir) {
if (before)
return()
ext = tolower(options$fig.ext)
if (ext != "png") {
warning("this hook only works with PNG")
return()
}
if (!nzchar(Sys.which("pngquant"))) {
warning("cannot find pngquant; please install and put it in PATH")
return()
}
paths = all_figs(options, ext)
in_base_dir(lapply(paths, function(x) {
message("optimizing ", x)
cmd = paste(
"pngquant",
if (is.character(options$pngquant)) options$pngquant,
shQuote(x)
)
message(cmd)
(if (is_windows())
shell
else system)(cmd)
x_opt = sub("\\.png$", "-fs8.png", x)
file.rename(x_opt, x)
}))
return()
}
# Enable this hook in this R script.
knit_hooks$set(
pngquant = hook_pngquant
)
#' Here we set the chunk option `pngquant='--speed=1 --quality=0-50'`,
#' which activates the hook.
#+ use-pngquant, pngquant='--speed=1 --quality=0-50'
library(methods)
library(ggplot2)
set.seed(123)
qplot(rnorm(1e3), rnorm(1e3))
I prefer to write my reports in R scripts (.R) instead of R markdown documents (.Rmd). See http://yihui.name/knitr/demo/stitch/ for more information on how to do that.
One thing you could do would be to not use embedded image and other resources. To achieve this, you can set the self_contained option in the YAML header for your document to false, e.g.:
---
output:
html_document:
self_contained: false
---
More info here: http://rmarkdown.rstudio.com/html_document_format.html