I am wondering if it is possible to create a number of tabs in flexdashboard which change according to some user input. As a toy example, I am trying to plot histograms of every numeric numeric column of a user-selected dataset, with each numeric column getting its own tab for a histogram. If the user changes the dataset, the dashboard should update and increase/decrease the number of tabs in the tabset, if the number of numeric columns in the newly selected dataset changes. For simplicity, I only included two datasets.
---
title: "Reactive GGPlot Tabs"
output:
flexdashboard::flex_dashboard:
theme:
version: 4
bootswatch: cosmo
runtime: shiny
---
```{r}
pacman::p_load(tidyverse, shiny, flexdashboard)
```
Sidebar {.sidebar}
-------------------------------------
<h5>Required Input</h5>
```{r}
selectInput(inputId = "dataset", label = "Data:",
choices = c('mtcars', 'iris'), selected = 'iris')
dataset = reactive({
set = as.character(input$dataset)
if(set=='iris'){data = iris} else{data = mtcars}
return(data)
})
```
```{r setup}
chart = reactive({
data = dataset()
num_cols <- unlist(lapply(data, is.numeric))
data = data[,num_cols] # only keep numeric columns for histogram
labels <- data %>% names # these will serve as labels for each tab
chart <- purrr::map(.x = data, ~ggplot(data, aes(x=.x))+geom_histogram()+theme_bw()) %>% setNames(labels) # assign names to each element to use later as tab titles
return(chart)
})
```
## Column {.tabset .tabset-fade}
```{r}
out <- reactive({
lapply(seq_along(chart()), function(i) {
a1 <- knitr::knit_expand(text = sprintf("### %s\n", names(chart())[i])) # tab header, auto extracts names of `chart`, %s indicates string
a2 <- knitr::knit_expand(text = "\n```{r, message = F, warning = F}") # start r chunk
a3 <- knitr::knit_expand(text = sprintf("\nchart()[[%d]]", i)) # extract graphs by "writing" out `chart[[1]]`, `chart[[2]]` etc. to be rendered later, %d indicates integer variable
a4 <- knitr::knit_expand(text = "\n```\n") # end r chunk
paste(a1, a2, a3, a4, collapse = '\n') # collapse together all lines with newline separator
})
})
```
`r reactive({knitr::knit(text = paste(out(), collapse = '\n'))})`
When this code is run, the dashboard displays the markdown chunk code, but does not render plots or tabs.
I am able to sightly modify this code to work without reactive, but I need the number of tabs to increase/decrease according to the chosen dataset. Also, I am not allowed to use facet_wrap.
Is this possible?
Related
I would like to render Shiny tabs (and other Shiny features) within an Rmarkdown document. Specifically, I'd like to display a table in each tab, generated with a loop.
We can add {.tabset} tag after a header and subsequent lower level headers will be separate tabs. I tried to make use of this by using cat() and the header level and a unique name at each loop for the tab name. That creates the tabs fine. Also, a table is created within each tab when I call the function with arguments separately.
However, when looping through the function, the tables are not being generated on each tab. In the final tab is output
<shiny.render.function> for each loop.
I know if I was generating a table in Rmarkdown without Shiny, I'd print each table within each loop. However, there's no similar function for printing rendered Shiny objects.
---
output: html_document
runtime: shiny
---
## Tables Work {.tabset}
{r works, results='asis'}
# Function to generate a numbered tab and table within that tab
tbl_display_func <- function(tab_number, df){
cat('### Results', tab_number,' \n\n')
DT::renderDataTable({
DT::datatable(
head(df)
)
})
}
# Works fine to call the function separately
tbl_display_func(1, cars)
tbl_display_func(2, iris)
Tables Don't Work {.tabset}
However, when I loop through the function, tables do not display.
{r do-not-work, results='asis'}
# Does not work here. Generates tabs, then list of <shiny.render.function> in tab 2
purrr::map2(
.x = list(1,2),
.y = list(cars, iris),
.f = tbl_display_func
)
I tried to create the tables as renderTableOutput output, as I might if creating multiple widgets just in shiny, and then calling those within a function, but this didn't help
Tables Don't Work {.tabset}
{r generate-tables-then-print, results='asis'}
# Function just to generate table
tbl_func <- function(df){
DT::renderDataTable({
DT::datatable(
head(df)
)
})
}
# Store rendered table as output
tbl_func_output <- purrr::map(
.x = list(cars, iris),
.f = tbl_func
)
# Loop through each tab and table
display_func <- function(tab_number){
cat('### Results', tab_number,' \n\n')
tbl_func_output[[tab_number]]
}
purrr::map(
.x = 1:2,
.f = display_func
)
How might I loop through and create a table at each tab?
You need to do this
---
output: html_document
runtime: shiny
---
```{r}
do.call(tabsetPanel,
mapply(y = list(cars, iris), x = list(1,2), function(x, y) {
tabPanel(
paste0("Results", x), DT::renderDataTable({DT::datatable(head(y))})
)
}, SIMPLIFY = FALSE)
)
```
return of purrr::map2 is a list, not shiny.tag object. I don't think there is a way to have .tabset work with cat and DT::datatable together. You would choose only flexdashboard or shiny. Here I used shiny syntax.
I'm working on a project to make it easier to create flex/shiny dashboards from qualtrics surveys. I'd really like to be able to write a couple functions that would let co-workers who have less experience with R be able to make similar documents without having to know Rmarkdown syntax.
For example, if someone wanted to make a one page dashboard with a scatterplot, I'd like to be able to have them use a couple functions like (make_dashboard, make_page) etc:
make_dashboard(
title = "Qualtrics Report Dashboard",
page 1 = make_page(header = "Page 1", format = "column", render = "plot",
data = survey_data, variables = c("var1", "var2"))
)
which would then create a rmd file with this:
---
title: "Qualtrics Report Dashboard"
output:
flexdashboard::flex_dashboard:
orientation: columns
vertical_layout: scroll
runtime: shiny
---
Page 1
=====================================
renderPlot( {
ggplot(data = survey_data, mapping = aes_string(x = var1,
y = var2)) +
geom_point() +
labs(x = get_label(get(var1, survey_data)),
y = get_label(get(var2, survey_data)))
}
)
I haven't gotten very far with trying to write these functions / implement this logic, because I'm not even sure if I'm thinking about it in the right way - is it possible to create rmarkdown chunks with functions like this?
I've looked at other posts 1 and 2 about child documents in knitr, but I don't really want every chunk to be the same, rather have the person be able to change certain aspects (e.g. type of plot, data, etc.).
Not sure if this will be useful to anyone else, but I ended up using whisker (https://github.com/edwindj/whisker), which can render strings into documents to construct an Rmd in the style of flexdashboard.
TLDR: Essentially I made functions that create strings of text matching the building blocks of flexdashboard. With whisker, you can pass in variables by encasing words in the string with two bracket parentheses and then assigning their values with a list of var_name = value for each variable in the string, e.g.
template <- "My name is {{name}}."
d <- list(name = "Emily")
cat(whisker.render(template, data = d))
print(d)
My name is Emily
I used a combination of this and the str_c from stringr to construct strings for different elements of the flexdashboard, allowing the user to input variables like title, variable for plots, etc. that then could be rendered into the string using whisker. Then, I joined all of those strings together and render it into an Rmd file. Honestly, I am not sure this is actually easier for people who don't know R to use, and I'll probably end up doing something different, but I wanted to share in case anyone is thinking about this.
Example: running the chunk below creates a file called "test_dashboard.Rmd" with the text format for a flexdashboard with a 1 input sidebar and a single page with one plot.
```
make_dashboard(title = "Test Dashboard",
sidebar = make_sidebar(sidebar_title = "here is the input",
input_type = "multi-select",
input_name = "Interesting Var #1"),
page1 = make_page(page_title = "Cool Plots!",
element_one = make_plot(plot_title = "this is my plot",
type = "bivariate",
vars = c("cool_var1",
"cool_var2"))),
fn = "test_dashboard")
```
OUTPUT:
```
---
title: Test Dashboard
output:
flexdashboard::flex_dashboard:
orientation: columns
vertical_layout: scroll
runtime: shiny
---
\```{r setup, include=FALSE}
library(flexdashboard)
library(tidytext)
library(tidyverse)
library(janitor)
library(DT)
library(gghighlight)
library(knitr)
library(shiny)
library(qualtRics)
library(curl)
library(sjlabelled)
library(naniar)
library(scales)
library(lme4)
library(MASS)
library(snakecase)
\```
\```{r global, include=FALSE}
#setting global options for table scrolling and plot theme
options(DT.options = list(scrollY="100vh"))
theme_set(theme_minimal())
#this fetches all of your survey info
surveys <- all_surveys()
#this saves the survey responses into
docusign_survey <- fetch_survey(surveyID = surveys$id[1],
verbose = TRUE,
label = TRUE,
breakout_sets = TRUE,
force_request = TRUE)
#this saves the question text into a dataframe
questions <- survey_questions(surveyID = surveys$id[1])
rename_df <- rename_variables(docusign_survey)
#this renames all of the variables
docusign_survey <- docusign_survey %>%
rename_at(as.vector(rename_df$old_name), ~ as.vector(rename_df$new_labels))
#new variables
new_var <- rename_df$new_labels
#which are multi_select?
multi_select <- rename_df %>%
filter(ms == 1) %>%
dplyr::select(new_labels)
#relabel those NAs as No
docusign_survey <- docusign_survey %>%
purrr::modify_at(multi_select$new_labels, na_to_y)
\```
Sidebar {.sidebar}
=====================================
here is the input
\```{r}
selectInput("p_var_1", label = "Interesting Var #1",
choices = new_var,
multiple = TRUE)
\```
Cool Plots!
=====================================
Column {.tabset}
-------------------------------------
### this is my plot
\```{r}
renderPlot( {
make_bivariate_plot(docusign_survey, input$cool_var1, input$cool_var2)
})
\```
```
Functions
make_dashboard()
I saved the parts that will repeat every time, probably will want to make them editable for changes in scrolling, etc. but just trying to make proof of concept at the moment.
```
make_dashboard <- function(title, sidebar, page1, fn){
load("data/top_matter.rda")
load("data/libraries.rda")
load("data/main_chunk.rda")
initial_bit <- stringr::str_c(top_matter, libraries, main_chunk, sep = "\n\n")
intermediate <- stringr::str_c(initial_bit, sidebar, sep = "\n\n")
total <- stringr::str_c(intermediate, page1, sep = "\n\n")
data <- list(title = title)
out_fn <- paste0("./", fn, ".Rmd")
writeLines(whisker.render(total, data), con = out_fn)
}
```
make_sidebar()
```
make_sidebar <- function(sidebar_title, input_type, input_name){
top_sidebar <-
'Sidebar {.sidebar}
=====================================
'
sidebar_text <- str_c(top_sidebar, sidebar_title, sep = "\n\n")
if(input_type == "multi-select"){
ms <- "TRUE"
} else {
ms <- "FALSE"
}
input_one <- make_select_input(input_name, ms)
sidebar_total <- str_c(sidebar_text, "```{r}", input_one, "```", sep = "\n\n")
return(sidebar_total)
}
```
make_page()
```
make_page <- function(page_title, element_one){
top_page <-
'{{page_title}}
=====================================
Column {.tabset}
-------------------------------------'
add_element <- stringr::str_c(top_page, element_one, sep = "\n\n")
data <- list(page_title = page_title)
page <- whisker.render(add_element, data = data)
return(page)
}
```
make_plot()
```
make_plot <- function(plot_title, type = c("univariate", "bivariate"), vars){
top_plot_piece <-' {{plot_title}}
\```{r}
renderPlot( {
'
if(type == "univariate"){
plot_piece <-
'make_univariate_plot(docusign_survey, input${{vars}})
})
\```'
total_plot <- stringr::str_c(top_plot_piece, plot_piece, sep = "\n\n")
data <- list(plot_title = plot_title,
vars = vars)
plot_chunk <- whisker.render(total_plot, data = data)
} else{
plot_piece <-
'make_bivariate_plot(docusign_survey, input${{var_1}}, input${{var_2}})
})
\```'
total_plot <- stringr::str_c(top_plot_piece, plot_piece, sep = "\n\n")
data <- list(plot_title = plot_title,
var_1 = vars[1],
var_2 = vars[2])
plot_chunk <- whisker.render(total_plot, data = data)
}
return(plot_chunk)
}
```
I am trying to mimic or figure out how a similar function as leafletProxy works in mapview package inside a Shiny app (flexdashboard). The idea is that I have a parameterized database query that fetches a sf dataset (~4200 polygons) based on user inputs and then plots in mapview. However, it appears that everytime this is done the entire map is redrawn?
Below is a reproducible example using the default franconia dataset and a shiny input to control the line opacity. I also include my code (commented out) to show an example of how it will be used as intended (i.e. to dynamically redraw a polygon layer based on a database fetch)
Is there a way in shiny to draw a "base" map of all the background maps stylings once and then only redraw the new polygon data as they are retrieved?
Thanks!
---
title: "MRE"
output:
flexdashboard::flex_dashboard:
orientation: columns
vertical_layout: fill
runtime: shiny
---
```{r setup, include=FALSE}
library(flexdashboard)
library(mapview)
library(shiny)
library(leaflet)
#library(RSQLite)
#library(sf)
```
Sidebar {.sidebar}
-----------------------------------------------------------------------
```{r}
# This is shiny input that will trigger entire map redraw
sliderInput("gridlines", "Grid Line Opacity", 0, 1, 0, step = 0.1, ticks = FALSE)
```
```{r}
### THIS IS MY ACTUAL CODE...For reference
# sp_grid <- reactive({
# db <- "../data/modeldata.db"
# con <- dbConnect(SQLite(), db )
#
# # set the sql based on user input
# sql <- 'SELECT id, Time,Cell_I, Cell_J, Cell_K, Cell_Botdepth_M, Zmax, Salinity, WKT_GEOMETRY
# FROM vwGridTK
# WHERE Time = :time
# AND Cell_K = :layer'
#
# df.grid <- dbGetQuery(con, sql, params = list(time = 0,
# layer = 1))
# dbDisconnect(con)
#
# st_as_sf(df.grid, wkt = "WKT_GEOMETRY") %>% st_set_crs(4326)
#
# })
```
Column {data-width=500}
-----------------------------------------------------------------------
### Reproducible Example
NOTE the shiny input to control opacity
```{r}
renderLeaflet({
m <- mapview(franconia, zcol = "district", alpha = input$gridlines)
m#map
})
```
Column {data-width=500}
-----------------------------------------------------------------------
### My Example
```{r}
# renderLeaflet({
# m <- mapview(sp_grid(), zcol = "Salinity",
# legend = TRUE, alpha = input$gridlines)
#
# m#map
#
# })
```
I just can't seem to figure out how to change the value of columns in a dataframe within flexdashboard/shiny when multiple = TRUE. In the example below I can change x and y by selecting them individually. What I want to accomplish is when I have both x and y selected df$x becomes 3 and df$y becomes 2.
But when are both selected only the first one selected is changed.
---
title: "Test"
output:
flexdashboard::flex_dashboard:
orientation: columns
social: menu
source_code: embed
runtime: shiny
---
```
library(flexdashboard)
library(shiny)
library(DT)
df <- data.frame(
x = 1:10,
y = 1:10)
selectInput("var", label = "Change:",
choices = list("no change", "x","y"),
multiple =TRUE,
selected = "no change"
)
renderDataTable({
if(input$var == "no change") {df <- df}
if(input$var %in% "x") {df$x <- 3}
if(input$var %in% "y") {df$y <- 2}
return(df)
})
```
Any help would be much appreciated.
For context, below is snapshot of the output from the code above, on which there is no app output:
Image 0
You were almost there--just missing else before the last two if's, and the corresponding line breaks (similarly, see this).
---
title: "Please remember to make [reprex posts](https://stackoverflow.com/help/minimal-reproducible-example) :)"
output: html_document
---
```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = TRUE)
library(flexdashboard)
library(shiny)
library(DT)
library(tidyverse)
```
## R Markdown
This is an R Markdown document. Markdown is a simple formatting syntax for authoring HTML, PDF, and MS Word documents. For more details on using R Markdown see <http://rmarkdown.rstudio.com>.
When you click the **Knit** button a document will be generated that includes both content as well as the output of any embedded R code chunks within the document. You can embed an R code chunk like this:
```{r now-including-else}
df <- data.frame(
x = 1:10,
y = 1:10)
selectInput("var", label = "Change:",
choices = list("no change", "x","y"),
multiple =TRUE,
selected = "no change"
)
renderDataTable({
if(input$var == "no change") {
df <- df
}
else if(input$var %in% "x") {
df$x <- 3
}
else if(input$var %in% "y") {df$y <- 2}
return(df)
})
```
P.S. Reprex posts.
Some snapshots of the output from the code suggested:
Image 1
Image 2
Image 3
I'm having some very strange behavior when knitting a shiny RMarkdown HTML document. In the MWE below I simply (1) include a slider input, (2) include a column in the df as the value of the input and then (3) return the df. The MWE below returns the error Error in : Column "b" must be length 1 (the number of rows), not 0. If I take out the multiplication of the input value when I assign a value to b, the document knits with no problem. MWE:
---
output: html_document
runtime: shiny
---
```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = TRUE)
library(dplyr)
```
Testing
```{r eruptions, echo=FALSE}
# Input slider
sliderInput("test", "Test", value = 0.5, min = 0, max = 1, width = "100%")
# Pre-define df
output <- reactiveValues(data = data.frame(a = 20))
observe({
output$data <- output$data %>%
# Manipulate df
mutate(b = input$test * 2)
})
# Return df
renderDataTable({output$data})
```
Does anyone know what could be causing this strange behavior?
Here's what I suspect is going on ---
I think that the first time your observe is run through, that input$test is NULL.
If your mutate sets a column to NULL, it would just remove that column.
If you use input$test * 2 that will return an empty vector numeric(0). Trying to add a column with an empty vector would give the error.
If you add req(input$test) at the beginning of your observe function, does it work?