Create general purpose "go back" button in shiny - r

I want the user to input a value in a text box in shiny and then be able to click on a button to review the value s/he typed before. I got this to work with the code below, but it is far from ideal. For one thing it requires me to click the "go back" button twice to work (no idea why). Any suggestions are appreciated. (Also, note that I included the table of iterations in the output just for reference).
library(shiny)
q <- new.env() # create new environment
assign("iteration",1,envir = q)
log <- data.frame(iteration=NULL,id=NULL)
assign("log",log,envir = q)
ui <- fluidPage(
actionButton("back", "go back"),
textInput("id","enter id",value=1),
tableOutput("x")
)
server <- function(input, output,clientData, session) {
observeEvent(input$id,{
id <- input$id
iteration <- get("iteration", envir = q) # get iteration
log <- get("log",envir = q) #'gets df'
temp <- data.frame(iteration=iteration,id=id,stringsAsFactors = F)
if (!nrow(log)) log <- rbind(log,temp) # for first iteration only
else log[iteration,] <- temp
assign("log",log,envir = q)
iteration <- iteration+1
assign("iteration",iteration,envir = q)
})
# back button
observeEvent(input$back,{
iteration <- get("iteration", envir = q) # get iteration
iteration <- iteration-1
assign("iteration",iteration,envir = q)
log <- get("log",envir = q) #'gets df'
#get data
id <- log$id[iteration]
updateTextInput(session,"id", value=id)
})
# for visualising table
x <- eventReactive(input$id,{
get("log",envir = q)
})
output$x <- renderTable({x()})
}
shinyApp(ui = ui, server = server)
I want a "go back" button than one clicked shows the value typed previously in the text box.
(A similar question exist, but I do believe mine is different enough).

Related

How to force evaluation in shiny render when generating dynamic number of elements?

I generate a dynamic number of valueBox in my shiny, and this number can change depending of the user input.
I managed to handle this with a renderUI where I put the wanted number of valueBoxOutput, and I have an observe that will feed them with the content using renderValueBox.
My problem is: the code in the renderValueBox, for some reason, is actually executed after the observe is finished, so because the renderValueBox is in a loop (to have a dynamic number of them) but the code is executed for all the output after the loop, all my output will get the last value of the loop.
Here is a min reprex:
library(shiny)
library(shinydashboard)
library(shinyWidgets)
# Function
compute <- function(id)
{
print(paste("Compute ", id))
return(id)
}
# UI
ui = shinyUI(fluidPage(
titlePanel("Compare"),
useShinydashboard(),
sidebarLayout(
sidebarPanel(
numericInput("numitems", label = "Number of items", min = 1, max = 10, value = 2)
),
mainPanel(
uiOutput("boxes")
)
)
))
# Server
server = shinyServer(function(input, output, session) {
data <- reactiveValues(
ids = list()
)
output$boxes <- renderUI({
print("boxes")
box_list <- list()
id_list <- list()
for(id in 1:(input$numitems)) {
id_box <- paste0("box_", id)
print(paste("boxes - ", id_box))
id_list <- append(id_list, id_box)
box_list <- append(
box_list,
tagList(
shinydashboard::valueBoxOutput(id_box)
)
)
data$ids <- id_list
}
print("boxes end")
fluidRow(box_list)
})
observe({
print("observe")
for(id_box in data$ids) {
print(paste("observe - ", id_box))
output[[id_box]] <- shinydashboard::renderValueBox(valueBox(id_box, compute(id_box), icon = icon("circle-info"), color = "teal"))
}
print("end observe")
})
})
# Run
shinyApp(ui = ui , server = server)
Here is the result:
And the console output:
As you can see the compute (and the render in general) is done after the end of the observe function, and both output will use the last id_box that were set (so the last loop, box_2), instead of correctly using box_1 and box_2.
I tried using force, computing valueBox outside the render, using reactive lists, nothing worked, because whatever I do the render is evaluated after the observe so only the last loop values will be used no matter what.
Do anyone know a way to force execution during the loop ? Or see another way of achieving the same result ?
Why it's always after spending hald a day on a problem, looking for dozens of posts and forum, don't find anything, finally decide to ask a question... that a few minutes later I finally find an answer.
Anyway, one way to correct this (found here) is to encapsulate the render inside the local function, like this:
observe({
print("observe")
for(id_box in data$ids) {
print(paste("observe - ", id_box))
local({
tmp <- id_box
output[[tmp]] <- shinydashboard::renderValueBox(valueBox(tmp, compute(tmp), icon = icon("circle-info"), color = "teal"))
})
}
print("end observe")
})
Now the compute is still called after the end of the observe, but the tmp variable has the correct value:
The result is what I wanted:
For the record, I had already tried to use the local function, but if you don't copy the id_box inside another variable just for the local bloc, it won't work.

Continue running code if user input is not given

I apologyse in advance if the question is a bit unclear!
I created a for-loop that generates a plot every 0.1 seconds (to simulate a video of a moving object). The code works smoothly, but I would like to allow the user to pause and resume the "video" when he/she wants to inspect in more detail one of the video frames.
I thought about reading an input from the console using readline() or scan() functions at the end of the loop. For example, the user types "p"+enter to pause the video. However, readline() would expect an input at the end of each iteration. In my case, the user would only provide an input in some of the iterations, so the loop must continue running when no input is given.
This would be a simplified version of the loop (printing a value in the console instead of plotting an image):
for(index in c(1:10)){
print(index) # In my script it generates a plot
Sys.sleep(0.1)
input = read.line() # If user types an input in the command, execution is paused
# If no input is given, the loops continues with the next iteration
...
...
}
Do you have any ideas/suggestions of how to deal with this?
Thanks :)
Something like this using library(shiny) to provide a pause/resume button could work:
ui = fluidPage(
actionButton("pause", "Pause"),
plotOutput("myplot")
)
server = function(input, output, session) {
rv <- reactiveValues(i = 0, go = TRUE)
maxIter = 1000
timer = reactiveTimer(100)
output$myplot = renderPlot({
x = seq_len(1000)
y = sin(x/20 + rv$i) * cos(x/50 + rv$i/2)
plot(x, y, type = "l", main = rv$i, ylim = c(-1,1))
})
observeEvent(input$pause, {
rv$go = !rv$go
updateActionButton(session, inputId = "pause",
label = c("Resume", "Pause")[rv$go + 1L])
})
observeEvent(timer(), {
req(rv$i < maxIter)
req(rv$go)
rv$i = rv$i + 1
})
}
shinyApp(ui = ui, server = server)

track closest values in a table Shiny r

I am building a Shiny App that does random simulations of some stuff in three ways and saves the results in a table. I want the table to (1) fill the cell green for the closest value to the correct answer, and (2) include a line on bottom tracking total number of times each test group has been the closest.
what I have:
what I want:
Here's the code I'm using:
By the way, in this example there are ties, but that won't really be possible in the real thing, so probably not necessary to deal with.
#
# This is a Shiny web application. You can run the application by clicking
# the 'Run App' button above.
#
# Find out more about building applications with Shiny here:
#
# http://shiny.rstudio.com/
#
library(shiny)
# Define UI for application that draws a histogram
ui <- fluidPage(
# Application title
titlePanel("test"),
# Sidebar with a slider input for number of bins
sidebarLayout(
sidebarPanel(
actionButton("random_select",
"Generate Random Numbers",
width = 'auto')
),
# Show a plot of the generated distribution
mainPanel(
tableOutput("results_table_output")
)
)
)
# Define server logic required to draw a histogram
server <- function(input, output) {
counter <- reactiveValues(countervalue = 0)
observeEvent(input$random_select,{
counter$countervalue = counter$countervalue + 1
}
)
results <- reactiveValues(
table = list(trial = NA,
answer =NA,
test_1 = NA,
test_2 = NA,
test_3 = NA)
)
observeEvent(counter$countervalue,{
results$table$trial[counter$countervalue] <- as.integer(counter$countervalue)
results$table$answer[counter$countervalue] <- sample(1:10,1)
results$table$test_1[counter$countervalue] <- sample(1:10,1)
results$table$test_2[counter$countervalue] <- sample(1:10,1)
results$table$test_3[counter$countervalue] <- sample(1:10,1)
})
output$results_table_output <- renderTable({
results$table
})
}
# Run the application
shinyApp(ui = ui, server = server)
Disclaimer
I would also fall back to a more advanced table rendering engine like DT. However, in the following I show another solution which works with renderTable from "base" shiny.
renderTable + JS Solution
If you don't mind using some JavaScript you can use the following snippet:
library(shiny)
library(shinyjs)
js <- HTML("function mark_cells() {
$('.mark-cell').parent('td').css('background-color', 'steelblue');
}
function add_totals() {
const ncols = $('table th').length;
const $col_totals = Array(ncols).fill().map(function(el, idx) {
const $cell = $('<td></td>');
if (idx == 1) {
$cell.text('total:');
} else if (idx > 1) {
$cell.text($('table tr td:nth-child(' + (idx + 1) + ') .mark-cell').length);
}
return $cell;
})
$('table tfoot').remove();
$('table > tbody:last-child')
.after($('<tfoot></tfoot>').append($('<tr></tr>').append($col_totals)));
}
function mark_table() {
mark_cells();
add_totals()
}
")
make_run <- function(i, answer, tests = integer(3)) {
cn <- c("trial", "answer", paste0("test_", seq_along(tests)))
if (is.null(i)) {
line <- matrix(integer(0), ncol = length(cn))
colnames(line) <- cn
} else {
line <- matrix(as.integer(c(i, answer, tests)), ncol = length(cn))
colnames(line) <- cn
}
as.data.frame(line)
}
mark_best <- function(row) {
truth <- row[2]
answers <- row[-(1:2)]
dist <- abs(answers - truth)
best <- dist == min(dist)
answers[best] <- paste0("<span class = \"mark-cell\">", answers[best], "</span>")
c(row[1:2], answers)
}
ui <- fluidPage(
useShinyjs(),
tags$head(tags$script(js)),
sidebarLayout(
sidebarPanel(
actionButton("random_select",
"Generate Random Numbers")
),
mainPanel(
tableOutput("results_table_output")
)
)
)
server <- function(input, output, session) {
results <- reactiveVal(make_run(NULL))
observeEvent(input$random_select, {
res <- results()
results(rbind(res, make_run(nrow(res) + 1, sample(10, 1), sample(10, 3, TRUE))))
})
output$results_table_output <- renderTable({
res <- results()
if (nrow(res) > 0) {
res <- as.data.frame(t(apply(res, 1, mark_best)))
session$onFlushed(function() runjs("mark_table()"))
}
res
}, sanitize.text.function = identity)
}
shinyApp(ui = ui, server = server)
Explanation
In the renderTable function, we call mark_best where we surround the "winning" cells with <span class = "mark-cell">. This helps us on the JS side to identify which cells are the winners.
In order to not escape the HTML in it, we use the argument sanitize.text.function which is responsible for, well, sanitizing strings in the cell. Because we want to print them as is, we supply the identity function.
We include 3 JavaScript functions in the <head> of the document, which
color the parent <td> of our marked cells (mark_cells())
add column totals to the table. This is done by counting the .mark-cell marked cells in each column (add_totals)
a convenience wrapper to call both functions (mark_table())
In order to be able to actually call the JS function we rely on shinyjs. This is however, merely syntactic sugar and could be achieved otherwise as well (if you mind the additional library). To make shinyjs work, we need to include a call to useShinyjs in the UI.
All what is left to do is to call mark_table in the renderTable function. To make sure that the table is rendered properly, we do not call the JS function right away but use session$onFlushed to register the call to be run after the next flush happens.

Using Observe or other reactive function to output single dataframe cell in Shiny

I'm stuck on the final, and most critical step in building out my first pretty basic web-app using Shiny, and I'm struggling with what I think should be a pretty basic task. The idea is for the app to observe two inputs and then output a single value from a dataframe based on both of them.
Example Code to make it very clear.
Server
DF X Y Z
1 A B C
2 C D E
3 F G H
UI
InputA: Row - 2
InputB: Column - Z
Output: E
At the moment I've run into a total wall with this because I'm not getting an error. The App starts without a problem, and every other widget on this tab and others are working. At the moment, the text boxes just aren't doing anything. One can type values into the first two, but then nothing outputs and no error message is provided. It's frustrating because it feels at though I'm missing something very obvious.
Here is the actual code
Server
server <- function(input, output, session) {
#Download Data and create data table.
rlwin <- read.csv("rlwinClean.csv")
...
observe({
Lead <- as.character(input$Lead)
CalcTime <- as.character(input$CalcTime)
addtext <- paste(rlwin[rlwin$Time == CalcTime, Lead])
updateTextInput(session,"winProbability", value=addtext)
})
}
...
The UI:
ui <- (navbarPage(theme=shinytheme("sandstone"), title=h3("Rocket League Win Probability"),
#Tab1 ----
tabPanel("Win Probability Model",
#The Plot
plotOutput("modPlot", height="800px"),
#Probabililty Calculator
h4("Win Probability Calculator"),
textInput(inputId="CalcTime", label="Enter Time on Clock Remaining in Game", placeholder="0:00 to 5:00"),
textInput(inputId="Lead", label="Enter Lead or Deficit", placeholder="-4 to 4"),
br(""),
textInput(inputId="winProbability",label="Win Probability",placeholder="50%"),
br("")
),
...
Edit: Found an answer to this. It's probably a bit long winded
Server
...
observeEvent(input$runCalc,{
time <- subset(react, GameClock == input$CalcTime)
all <- subset(time, select = input$Lead)
val <- paste(all)
updateTextInput(session, inputId = "probText", value = val)
})
....
UI was effectively unchanged
Try assigning your observer
my_observer <- observe({ .... })
The following code works for me. Note that I have renamed some of your inputs because it was difficult to keep track of where your inputs were supposed to be used.
The error was occurring because addText in your original code was empty (the logical conditions returned no records). You will see below how I printed output to the console to debug this.
library(shiny)
ui <- (navbarPage( title=h3("Rocket League Win Probability"),
#Tab1 ----
tabPanel("Win Probability Model",
textInput(inputId="column_name", label="Column name", placeholder="X, Y or Z"),
textInput(inputId="row_number", label="Row number", placeholder="1, 2 or 3"),
br(""),
textInput(inputId="winProbability",label="Win Probability",placeholder="50%"),
br("")
)
))
server <- function(input, output, session) {
#Download Data and create data table.
rlwin = data.frame(DF = c(1,2,3), X = c("A","B","C"), Y = c("B","D","G"), Z = c("C","E","H"))
observe({
col_name <- as.character(input$column_name)
row_num <- input$row_number
addtext <- paste(rlwin[row_num, names(rlwin) == col_name])
# print("debug print out")
# print(col_name)
# print(row_num)
# print(addtext)
updateTextInput(session,"winProbability", value=addtext)
})
}
shinyApp(ui, server)

Dynamic Tabs with R-Shiny app using the same output function

Goal: I'm working on a bioinformatics project. I'm currently trying to implement R code that dynamically creates tabPanels (they are essentially carbon copies except for the data output).
Implementation: After doing some research I implemented this solution. It works in a way (the panels that I'm "carbon copying" are created), but the data that I need cannot be displayed.
Problem: I'm sure that the way I'm displaying my data is fine. The problem is that I can't use the same output function to display the data as seen here. So let me get to the code...
ui.R
library(shiny)
library(shinythemes)
library(dict)
library(DT)
...# Irrelevant functions removed #...
geneinfo <- read.table(file = "~/App/final_gene_info.csv",
header = TRUE,
sep = ",",
na.strings = "N/A",
as.is = c(1,2,3,4,5,6,7))
ui <- navbarPage(inverse = TRUE, "GENE PROJECT",
theme = shinytheme("cerulean"),
tabPanel("Home",
#shinythemes::themeSelector(),
fluidPage(
includeHTML("home.html")
)),
tabPanel("Gene Info",
h2('Detailed Gene Information'),
DT::dataTableOutput('table')),
tabPanel("File Viewer",
sidebarLayout(
sidebarPanel(
selectizeInput(inputId = "gene", label = "Choose a Gene", choice = genes, multiple = TRUE),
selectInput(inputId = "organism", label = "Choose an Organism", choice = orgs),
selectInput(inputId = "attribute", label = "Choose an Other", choice = attributes),
width = 2),
mainPanel(
uiOutput('change_tabs'),
width = 10))),
tabPanel("Alignment")
)
I'm using uiOutput to generate tabs dynamically on the server side....
server.R
server <- function (input, output, session) {
# Generate proper files from user input
fetch_files <- function(){
python <- p('LIB', 'shinylookup.py', python=TRUE)
system(sprintf('%s %s %s', python, toString(genie), input$organism), wait = TRUE)
print('Done with Python file generation.')
# Fetch a temporary file for data output
fetch_temp <- function(){
if(input$attribute != 'Features'){
if(input$attribute != 'Annotations'){
chosen <- toString(attribute_dict[[input$attribute]])
}
else{
chosen <- toString(input$sel)
extension <<- '.anno'
}
}
else{
chosen <- toString(input$sel)
extension <<- '.feat'
}
count = 0
oneline = ''
f <- paste(toString(genie), toString(input$organism), sep = '_')
f <- paste(f, extension, sep = '')
# Writes a temporary file to display output to the UI
target <- p('_DATA', f)
d <- dict_fetch(target)
temp_file <- tempfile("temp_file", p('_DATA', ''), fileext = '.txt')
write('', file=temp_file)
vectorofchar <- strsplit(toString(d[[chosen]]), '')[[1]]
for (item in vectorofchar){
count = count + 1
oneline = paste(oneline, item, sep = '')
# Only 60 characters per line (Find a better solution)
if (count == 60){
write(toString(oneline), file=temp_file, append=TRUE)
oneline = ''
count = 0
}
}
write(toString(oneline), file=temp_file, append=TRUE)
return(temp_file)
}
# Get the tabs based on the number of genes selected in the UI
fetch_tabs <- function(Tabs, OId, s = NULL){
count = 0
# Add a select input or nothing at all based on user input
if(is.null(s)==FALSE){
selection <- select(s)
x <- selectInput(inputId = 'sel', label = "Choose an Annotation:", choices = selection$keys())
}
else
x <- ''
for(gene in input$gene){
if(count==0){myTabs = character()}
count = count + 1
genie <<- gene
fetch_files()
file_tab <- lapply(sprintf('File for %s', gene), tabPanel
fluidRow(
titlePanel(sprintf("File for %s:", gene)),
column(5,
pre(textOutput(outputId = "file")),offset = 0))
)
addTabs <- c(file_tab, lapply(sprintf('%s for %s',paste('Specific', Tabs), gene), tabPanel,
fluidRow(
x,
titlePanel(sprintf("Attribute for %s:", gene)),
column(5,
pre(textOutput(outputId = OId), offset = 0)))
))
# Append additional tabs every iteration
myTabs <- c(myTabs, addTabs)
}
return(myTabs)
}
# Select the proper file and return a dictionary for selectInput
select <- function(ext, fil=FALSE){
f <- paste(toString(genie), toString(input$organism), sep = '_')
f <- paste(f, ext, sep = '')
f <- p('_DATA', f)
if(fil==FALSE){
return(dict_fetch(f))
}
else if(fil==TRUE){
return(toString(f))
}
}
# Output gene info table
output$table <- DT::renderDataTable(
geneinfo,
filter = 'top',
escape = FALSE,
options = list(autoWidth = TRUE,
options = list(pageLength = 10),
columnDefs = list(list(width = '600px', targets = c(6))))
)
observe({
x <- geneinfo[input$table_rows_all, 2]
if (is.null(x))
x <- genes
updateSelectizeInput(session, 'gene', choices = x)
})
# Output for the File tab
output$file <- renderText({
extension <<- '.gbk'
f <- select(extension, f=TRUE)
includeText(f)
})
# Output for attributes with ony one property
output$attributes <- renderText({
extension <<- '.kv'
f <- fetch_temp()
includeText(f)
})
# Output for attributes with multiple properties (features, annotations)
output$sub <- renderText({
f <- fetch_temp()
includeText(f)
})
# Input that creates tabs and selectors for more input
output$change_tabs <- renderUI({
# Fetch all the appropriate files for output
Tabs = input$attribute
if(input$attribute == 'Annotations'){
extension <<- '.anno'
OId = 'sub'
s <- extension
}
else if(input$attribute == 'Features'){
extension <<- '.feat'
OId = 'sub'
s <- extension
}
else{
OId = 'attributes'
s <- NULL
}
myTabs <- fetch_tabs(Tabs, OId, s = s)
do.call(tabsetPanel, myTabs)
})
}
)
Explanation: Now I'm aware that there's a lot to look at here.. But my problem exists within output$change_tabs (it's the last function), which calls fetch_tabs(). Fetch tabs uses the input$gene (a list of genes via selectizeInput(multiple=TRUE)) to dynamically create a set of 2 tabs per gene selected by the user.
What's Happening: So if the user selects 2 genes then 4 tabs are created. With 5 genes 10 tabs are created... And so on and so forth... Each tab is EXACTLY THE SAME, except for the data.
Roadblocks: BUT... for each tab I'm trying to use the same output Id (since they are EXACTLY THE SAME) for the data that I want to display (textOutput(outputId = "file")). As explained above in the second link, this simply does not work because HTML.
Questions: I've tried researching several solutions, but I would rather not have to implement this solution. I don't want to have to rewrite so much code. Is there any way I can add a reactive or observer function that can wrap or fix my output$file function? Or is there a way for me to add information to my tabs after the do.call(tabsetPanel, myTabs)? Am I thinking about this the right way?
I'm aware that my code isn't commented very well so I apologize in advance. Please feel free to critique my coding style in the comments, even if you don't have a solution. Please and thank you!
I've come up with a very VERY crude answer that will work for now...
Here is the answer from #BigDataScientist
My Issue with BigDataScientist's Answer:
I can't dynamically pass data to the outputs. The output functions are not interpreted until they are needed... So if I wanted to pass the for loop iterator that you created (iter) into the dynamically created outputs, then I wouldn't be able to do that. It can only take static data
My Solution:
I end up taking advantage of sys.calls() solution I found here in order to get the name of the function as a string. The name of the function has the info I need (in this case a number).
library(shiny)
library(shinythemes)
myTabs <<- list()
conv <- function(v1) {
deparse(substitute(v1))
}
ui <- navbarPage(inverse = TRUE, "GENE PROJECT",
theme = shinytheme("cerulean"),
tabPanel("Gene Info",
sidebarLayout(
sidebarPanel(
sliderInput("bins",
"Number of bins:",
min = 1,
max = 5,
value = 3)
),
# Show a plot of the generated distribution
mainPanel(
uiOutput('changeTab')
)
)
)
)
server <- function(input, output) {
observe({
b <<- input$bins
myTabs <<- list()
# Dynamically Create output functions
# Dynamically Create formatted tabs
# Dynamically Render the tabs with renderUI
for(iter in 1:b){
x <<- iter
output[[sprintf("tab%s", iter)]] <- renderText({
temp <- deparse(sys.calls()[[sys.nframe()-3]])
x <- gsub('\\D','',temp)
x <- as.numeric(x)
f <- sprintf('file%s.txt', x)
includeText(f)
})
addTabs <<- lapply(sprintf('Tab %s', iter), tabPanel,
fluidRow(
titlePanel(sprintf("Tabble %s:", iter)),
column(5,
pre(textOutput(outputId = sprintf('%s%s','tab', iter))))))
myTabs <<- c(myTabs, addTabs)
}
myTabs <<- c(myTabs, selected = sprintf('Tab %s', x))
output$changeTab <- renderUI({
do.call(tabsetPanel, myTabs)
})
})
}
# Run the application
shinyApp(ui = ui, server = server)
I think your being a victim of this behavior. Try:
for (el in whatever) {
local({
thisEl <- el
...
})
}
like Joe suggests in the first reply to the Github issue I linked to. This is only necessary if you're using a for loop. lapply already takes el as an argument, so you get this "dynamic evaluation" benefit (for lack of a better name) for free.
For readability, I'm going to quote most of Joe's answer here:
You're the second person at useR that I talked to that was bitten by this behavior in R. It's because all the iterations of the for loop share the same reference to el. So when any of the created reactive expressions execute, they're using whatever the final value of el was.
You can fix this either by 1) using lapply instead of a for loop; since each iteration executes as its own function call, it gets its own reference to el; or 2) using a for loop but introducing a local({...}) inside of there, and creating a local variable in there whose value is assigned to el outside of the reactive.

Resources