Shiny: Dynamic height adjustment of plot - r

Problem: In belows Shiny app the user can add information presented in valueboxes depending on the select input. If the user selects all possible choices then the UI looks as in the screenshot.
Question: Is it possible that the plot (which is in the same row as the valueboxes) adjusts in height (so the bottom of the plot is aligned with the bottom of the last valuebox)?
library(shiny)
library(shinydashboard)
ui <- dashboardPage(
dashboardHeader(),
dashboardSidebar(
selectizeInput(
inputId = "select",
label = "Select country:",
choices = c("CH", "JP", "GER", "AT", "CA", "HK"),
multiple = TRUE)
),
dashboardBody(
fluidRow(column(2, uiOutput("ui1")),
column(10, plotOutput("some_plot"))))#,
# column(4, uiOutput("ui2")),
# column(4, uiOutput("ui3")))
)
server <- function(input, output) {
output$ui1 <- renderUI({
req(input$select)
lapply(seq_along(input$select), function(i) {
fluidRow(
valueBox(value = input$select[i],
subtitle = "Box 1",
width = 12)
)
})
})
output$some_plot <- renderPlot(
plot(iris)
)
}
shinyApp(ui = ui, server = server)

You can adjust the height in the renderPlot. I have set the minimum to 3 value box height. So, it starts increasing the height after you add 3 value boxes. You can modify it, as necessary. Try the code below.
library(shiny)
library(shinydashboard)
ui <- dashboardPage(
dashboardHeader(),
dashboardSidebar(
selectizeInput(
inputId = "select",
label = "Select country:",
choices = c("CH", "JP", "GER", "AT", "CA", "HK"),
multiple = TRUE)
),
dashboardBody(
fluidRow(column(2, uiOutput("ui1")),
column(10, plotOutput("some_plot"))))#,
# column(4, uiOutput("ui2")),
# column(4, uiOutput("ui3")))
)
server <- function(input, output) {
plotht <- reactiveVal(360)
observe({
req(input$select)
nvbox <- length(input$select)
if (nvbox > 3) {
plotheight <- 360 + (nvbox-3)*120
}else plotheight <- 360
plotht(plotheight)
})
output$ui1 <- renderUI({
req(input$select)
lapply(seq_along(input$select), function(i) {
fluidRow(
valueBox(value = input$select[i],
subtitle = "Box 1",
width = 12)
)
})
})
observe({
output$some_plot <- renderPlot({
plot(iris)
}, height=plotht())
})
}
shinyApp(ui = ui, server = server)

Here's my attempt, based on this answer. This uses the window size listeners to dynamically adjust the size of a plot (possible by using inline = TRUE in the plotOutput call). The width of the outer container is fixed, so can be referenced directly, but the height is dynamic, so my workaround is to use the window height and subtract 50 pixels. This seems to work as long as there is a single plot element, and the sidebar hasn't been adjusted to be on top of the plot, rather than beside it.
The window resizes are debounced to only resize after there's been no change for half a second, so that the server isn't taxed too much in redraw calls. The code also doesn't plot anything if the dimensions are not yet determined, so that there's no initial plot flicker.
library(shiny)
ui <- fluidPage(
## Add a listener for the window height and plot container width
tags$head(tags$script('
var winDims = [0, 0];
var plotElt = document;
$(document).on("shiny:connected", function(e) {
plotElt = document.getElementById("plotContainer");
winDims[0] = plotElt.clientWidth;
winDims[1] = window.innerHeight;
Shiny.onInputChange("winDims", winDims);
});
$(window).resize(function(e) {
winDims[0] = plotElt.clientWidth;
winDims[1] = window.innerHeight;
Shiny.onInputChange("winDims", winDims);
});
')),
titlePanel("Old Faithful Geyser Data"),
sidebarLayout(
sidebarPanel(
sliderInput("bins",
"Number of bins:",
min = 1,
max = 50,
value = 30),
sliderInput("height", label="Height",
min=100, max=900, value = 600)
),
mainPanel(
tags$div(id="plotContainer", ## Add outer container to make JS constant
## Use an "inline" plot, so that width and height can be set server-side
plotOutput("distPlot", inline = TRUE))
)
)
)
server <- function(input, output) {
## reduce the amount of redraws on window resize
winDims_d <- reactive(input$winDims) %>% debounce(500)
## fetch the changed window dimensions
getWinX <- function(){
print(input$winDims);
if(is.null(winDims_d())) { 400 } else {
return(winDims_d()[1])
}
}
getWinY <- function(){
if(is.null(winDims_d())) { 600 } else {
return(winDims_d()[2] - 50)
}
}
output$distPlot <- renderPlot({
if(is.null(winDims_d())){
## Don't plot anything if we don't yet know the size
return(NULL);
}
x <- faithful[, 2]
bins <- seq(min(x), max(x), length.out = input$bins + 1)
hist(x, breaks = bins, col = 'darkgray', border = 'white')
}, width = getWinX, height=getWinY)
}
shinyApp(ui = ui, server = server)

Related

Shiny app - hide/show text comment under the plot after clicking checkbox

I want to build an app with the checkbox asking whether to show additional text comments under the figures.
I would like to display set of plots with or without an explanation - this shall be left to the user, whether they need more info or not.
Here are some dummy comments:
#info for box1:
"This is the red histogram"
#info for box2:
"This is the blue histogram"
Here is a dummy app:
library(shiny)
library(shinydashboard)
data <- rnorm(10000, mean=8, sd=1.3)
variable <- "This is the blue histogram"
shinyApp(
ui = dashboardPage(
skin = "black",
dashboardHeader(
title = "Example app",
titleWidth = 300
),
dashboardSidebar(
checkboxInput("show_comment",
label = "Show comment?",
value = FALSE)
),
dashboardBody(
box(title = "First histogram",
status= "warning",
plotOutput("plot1", height=300)
),
box(title = "Second histogram",
status= "warning",
plotOutput("plot2", height=300),
hidden(
div(id='text_div',
verbatimTextOutput("text")))
)
)
),
server = function(input, output) {
output$plot1 <- renderPlot({
hist(data, breaks=40, col="red", xlim=c(2,14), ylim=c(0,800))
})
output$plot2 <- renderPlot({
hist(data, breaks=20, col="blue", xlim=c(2,34), ylim=c(0,1000))
})
observeEvent(input$show_comment, {
toggle('text_div')
output$text <- renderText({ paste0(variable)})
})
}
)
The above code does not work properly - it displays comment no matter if the checkbox is clicked or not. I'd like to make it work, therefore seek for advice here.
I was trying to do it on my own using following hints, to no avail:
How to use shiny actionButton to show & hide text output?
This syntax is too complex for me as I am a beginner with shiny, so I was not able to troubleshoot my problem with hints from this thread:
Show and hide text in modularized shiny app based on actionButton() and shinyJS()
I also tried ths:
Hide/show outputs Shiny R
And here is the attempt of using above hint:
library(shiny)
library(shinydashboard)
data <- rnorm(10000, mean=8, sd=1.3)
variable <- "This is the blue histogram"
shinyApp(
ui = dashboardPage(
skin = "black",
dashboardHeader(
title = "Example app",
titleWidth = 300
),
dashboardSidebar(
checkboxInput("show_comment",
label = "Show comment?",
value = FALSE)
),
dashboardBody(
box(title = "First histogram",
status= "warning",
plotOutput("plot1", height=300)
),
box(title = "Second histogram",
status= "warning",
plotOutput("plot2", height=300),
renderText("text", span(variable))
)
)
),
server = function(input, output) {
output$plot1 <- renderPlot({
hist(data, breaks=40, col="red", xlim=c(2,14), ylim=c(0,800))
})
output$plot2 <- renderPlot({
hist(data, breaks=20, col="blue", xlim=c(2,34), ylim=c(0,1000))
})
observeEvent(input$show_comment, {
# every time the button is pressed, alternate between hiding and showing the plot
toggle("text")
})
}
)
I want to put the comments inside the same box, along with the plot - this is why I am trying to enclose it with the box command. However, if it is impossible - I would be glad of any other solution.
First time I use shinyjs so there might be a better approach. But as I understand it from the docs you first have to add useShinyjs() in your UI code
in order for all other shinyjs functions to work.
Second, there is no need to wrap the div for your comment in hidden(). Third, instead of using observeEvent I followed the example in ?toggle and use an observe where I add the state of your checkbox as the condition to trigger the toggle.
library(shiny)
library(shinydashboard)
library(shinyjs)
data <- rnorm(10000, mean = 8, sd = 1.3)
variable <- "This is the blue histogram"
shinyApp(
ui = dashboardPage(
skin = "black",
dashboardHeader(
title = "Example app",
titleWidth = 300
),
dashboardSidebar(
checkboxInput("show_comment",
label = "Show comment?",
value = FALSE
)
),
dashboardBody(
box(
title = "First histogram",
status = "warning",
plotOutput("plot1", height = 300)
),
box(
title = "Second histogram",
status = "warning",
plotOutput("plot2", height = 300),
div(id = "text_div",
verbatimTextOutput("text")
)
)
),
useShinyjs()
),
server = function(input, output) {
output$plot1 <- renderPlot({
hist(data, breaks = 40, col = "red", xlim = c(2, 14), ylim = c(0, 800))
})
output$plot2 <- renderPlot({
hist(data, breaks = 20, col = "blue", xlim = c(2, 34), ylim = c(0, 1000))
})
observe({
toggle(id = "text_div", condition = input$show_comment)
output$text <- renderText({
paste0(variable)
})
})
}
)
#>
#> Listening on http://127.0.0.1:7437

Reset FileInputi in Shiny

I have a shiny app with fileInput.
I created a reset button to clear the file from the fileInput. I know the file is cached in the shiny memory, but I just want to reset the FileInput object to look like how it originally did before I uploaded the file, when I press the reset button.
I googled a bit and found most people get around this problem using shinyjs, but shinyjs does not work in the R package I am creating so I am trying to find a work around.
I insert a executable code below!
library(shiny)
library(shinythemes)
ui <- fluidPage(
titlePanel("Old Faithful Geyser Data"),
fileInput(inputId = "ABC", label = "Input File", multiple = FALSE, accept = NULL,
width = "20%", buttonLabel = "Browse...",
placeholder = "You didn't choose any files to test, so select beside"),
sidebarLayout(
sidebarPanel(h4("Select the best the number of bins"),
br(),
br(),
sliderInput("bins",
"Number of bins:",
min = 1,
max = 50,
value = 30),
actionButton("reset", "Reset"),
),
mainPanel(
plotOutput("distPlot")
)
)
)
server <- function(input, output,session) {
output$distPlot <- renderPlot({
x <- faithful[, 2]
bins <- seq(min(x), max(x), length.out = input$bins + 1)
hist(x, breaks = bins, col = 'darkgray', border = 'white')
})
observeEvent(input$reset, {
updateSliderInput(session,"bins",value = 1)
})
}
shinyApp(ui = ui, server = server)
One way would be to use fileInput dynamically from server side, so when the reset button is clicked you can reload the input.
library(shiny)
library(shinythemes)
ui <- fluidPage(
titlePanel("Old Faithful Geyser Data"),
uiOutput('file'),
sidebarLayout(
sidebarPanel(h4("Select the best the number of bins"),
br(),
br(),
sliderInput("bins",
"Number of bins:",
min = 1,
max = 50,
value = 30),
actionButton("reset", "Reset"),
),
mainPanel(
plotOutput("distPlot")
)
)
)
server <- function(input, output,session) {
output$distPlot <- renderPlot({
x <- faithful[, 2]
bins <- seq(min(x), max(x), length.out = input$bins + 1)
hist(x, breaks = bins, col = 'darkgray', border = 'white')
})
output$file <- renderUI({
fileInput(inputId = "ABC", label = "Input File", multiple = FALSE, accept = NULL,
width = "20%", buttonLabel = "Browse...",
placeholder = "You didn't choose any files to test, so select beside")
})
observeEvent(input$reset, {
updateSliderInput(session,"bins",value = 1)
output$file <- renderUI({
fileInput(inputId = "ABC", label = "Input File", multiple = FALSE, accept = NULL,
width = "20%", buttonLabel = "Browse...",
placeholder = "You didn't choose any files to test, so select beside")
})
})
}
shinyApp(ui = ui, server = server)

Is there a way to dynamically selectInput and have its respectivelly textInput arranged like flowLayout?

In the following code the textInput does not stay inside the dashboardSidebar. I would like to have it structured like in 3 columns and the number of rows would depend on the number of items selected using the selectInput.
library(shiny)
ui <- dashboardPage(
skin = "black",
title = "Dashboard",
dashboardHeader(
title = "Dashboard",
titleWidth = 451
),
dashboardSidebar(
width = 451,
sidebarMenu(
fileInput('file1', 'Select File', accept=c('text/csv','text/comma-separated-values,text/plain','.csv'), width = 450),
textInput("avg_info", "Population Info", placeholder = "Enter values separated by a comma", width = 450),
br(),
actionButton("Btn_run", "Run"),
hr(),
uiOutput("sel_inp"),
uiOutput("text_sel"),
br(),
uiOutput("ui1"),
br()
)
),
dashboardBody()
)
server <- shinyServer(function(input, output, session) {
# Track the number of input boxes to render
observeEvent(input$Btn_run, {
output$sel_inp <- renderUI({
selectInput("COLUMN", "Selection", choices = paste0("Item",1:15), multiple = TRUE, width = 450)
})
output$text_sel <- renderUI({
trait_names <- input$COLUMN
n <- length(trait_names)
if (n > 0) {
interaction <- lapply(seq_len(n), function(i) {
textInput(inputId = trait_names[i],
label = trait_names[i],
width = 140)
})
do.call(flowLayout, interaction) # tagList
}
})
})
})
shinyApp(ui, server)
Is there a way to do that? Thank you.
This might not be the best solution, but it gave me the expected layout result.
library(shiny)
ui <- shinyUI(
pageWithSidebar(
headerPanel("Add Features"),
sidebarPanel(width=4,
fluidRow(column(12,
fileInput('file1', 'Select File', accept=c('text/csv','text/comma-separated-values,text/plain','.csv'), width = 450),
textInput("avg_info", "Population Info", placeholder = "Enter values separated by a comma", width = 450),
br(),
actionButton("Btn_run", "Run"),
hr(),
uiOutput("sel_inp"),)), # END fluidRow
fluidRow(
column(2, uiOutput("text_sel1")),
column(2, uiOutput("text_sel2")),
column(2, uiOutput("text_sel3")),
column(2, uiOutput("text_sel4"))
) # END fluidRow
), # END sidebarPanel
mainPanel()
)
)
server <- shinyServer(function(input, output, session) {
# Track the number of input boxes to render
observeEvent(input$Btn_run, {
output$sel_inp <- renderUI({
selectInput("COLUMN", "Selection", choices = paste0("Item",1:15), multiple = TRUE, width = 450)
})
output$text_sel1 <- renderUI({
trait_names <- input$COLUMN
if (length(trait_names) > 0) {
interaction1 <- lapply(seq(1,length(trait_names), by = 3), function(i) {
textInput(inputId = trait_names[i], label = trait_names[i], width = 140)
})
do.call(tagList, interaction1)
}
})
output$text_sel2 <- renderUI({
trait_names <- input$COLUMN
if (length(trait_names) > 1) {
interaction2 <- lapply(seq(2,length(trait_names), by = 3), function(i) {
textInput(inputId = trait_names[i], label = trait_names[i], width = 140)
})
do.call(tagList, interaction2)
}
})
output$text_sel3 <- renderUI({
trait_names <- input$COLUMN
if (length(trait_names) > 2) {
interaction3 <- lapply(seq(3,length(trait_names), by = 3), function(i) {
textInput(inputId = trait_names[i], label = trait_names[i], width = 140)
})
do.call(tagList, interaction3)
}
})
output$text_sel4 <- renderUI({
trait_names <- input$COLUMN
if (length(trait_names) > 3) {
interaction4 <- lapply(seq(4,length(trait_names), by = 3), function(i) {
textInput(inputId = trait_names[i], label = trait_names[i], width = 140)
})
do.call(tagList, interaction4)
}
})
})
})
shinyApp(ui, server)

Reactive R/Shiny app "forgets" variable state after other reactive variable changes

Below is a reproducible example. When you click in the histogram area, a textbox appears showing the location of that click. But when you move the slider in the sidepanel, the textbox disappears and you have to click the histogram again to make it re-appear. How can I ensure that once the plot is clicked, the textbox remains (and keeps the same content) until the next click occurs, even if the slider is moved?
ui <- fluidPage(
titlePanel("Hello Shiny!"),
sidebarLayout(
sidebarPanel(
sliderInput(inputId = "bins",
label = "Number of bins:",
min = 1,
max = 50,
value = 30)
),
mainPanel(
plotOutput(outputId = "distPlot",click = "plot_click"),
verbatimTextOutput("click_x")
)
)
)
# Define server logic required to draw a histogram ----
server <- function(input, output) {
output$click_x <- renderText({
input$plot_click$x
})
output$distPlot <- renderPlot({
x <- faithful$waiting
bins <- seq(min(x), max(x), length.out = input$bins + 1)
hist(x, breaks = bins, col = "#75AADB", border = "white",
xlab = "Waiting time to next eruption (in mins)",
main = "Histogram of waiting times")
})
}
shinyApp(ui, server)
A simple conditional output will do the trick:
output$click_x <- renderText({
if(is.null(input$plot_click$x)) "Select point in graph" else input$plot_click$x
})
if you want to store the previous value, you can apply the condition to a variable attribution. You could do it with observeEvent:
observeEvent(input$plot_click$x, {
var <- input$plot_click$x
output$click_x <- renderText({
var
})
})

Shiny reactive dataset within observeEvent

I have a very simple app which fails. The reason it fails is that the reactive dataset is available solely within the observeEvent function but not outside. I use observeEvent to get datasets from two different sources wrangled. For this example I simply used cbind. My actual code is much more complicated.
This is a logical / syntax related problem but all my searching came up short. In essence I want merged_data() to be available for all parts of the app.
Minimum repr example - this fails because merged_data() is not available outside of the ObserveEvent.
library(shiny)
library(shinyjs)
library(DT)
# Define UI for application that draws a histogram
ui <- fluidPage(
# Application title
titlePanel("testing 1 2 3"),
# Sidebar with a slider input for number of bins
sidebarLayout(
sidebarPanel(
),
# Show a plot of the generated distribution
mainPanel(
fluidRow(
column(width = 2,
offset = 0,
align = "center",
actionButton(inputId = "fetch_data_inputId",
label = "data")
) #column
,
column(width = 10,
offset = 0,
align = "center",
DT::dataTableOutput("DT1")
) #column
)#fluidrow
)
)
)
# Define server logic required to draw a histogram
server <- function(input, output,session) {
observeEvent(input$fetch_data_inputId, {
req(iris)
button_data <- colnames(iris)
merged_data <- reactive({
if( !is.null(cbind(iris[,1:4],iris3))) {
cbind(iris[,1:4],iris3)
} else {NULL}
})
}) #observeevent
output$DT1 <- renderDataTable({#
rendered_table <- merged_data()
DT::datatable(rendered_table)
})
}
# Run the application
shinyApp(ui = ui, server = server)
Minimum repr example - this works because the datatable is created within the ObserveEvent.
library(shiny)
library(shinyjs)
library(DT)
# Define UI for application that draws a histogram
ui <- fluidPage(
# Application title
titlePanel("testing 1 2 3"),
# Sidebar with a slider input for number of bins
sidebarLayout(
sidebarPanel(
),
# Show a plot of the generated distribution
mainPanel(
fluidRow(
column(width = 2,
offset = 0,
align = "center",
actionButton(inputId = "fetch_data_inputId",
label = "data")
) #column
,
column(width = 10,
offset = 0,
align = "center",
DT::dataTableOutput("DT1")
) #column
)#fluidrow
)
)
)
# Define server logic required to draw a histogram
server <- function(input, output,session) {
observeEvent(input$fetch_data_inputId, {
req(iris)
button_data <- colnames(iris)
merged_data <- reactive({
if( !is.null(cbind(iris[,1:4],iris3))) {
cbind(iris[,1:4],iris3)
} else {NULL}
})
output$DT1 <- renderDataTable({#
rendered_table <- merged_data()
DT::datatable(rendered_table)
})
}) #observeevent
}
# Run the application
shinyApp(ui = ui, server = server)
What I really need is for the reactive dataset to continue being created within observeEvent but to be accessible outside of the ObserveEvent environment so that i use it in other parts of the app, but I suspect it's the wrong approach. So anything that works would be great.
library(shiny)
library(shinyjs)
library(DT)
# Define UI for application that draws a histogram
ui <- fluidPage(
# Application title
titlePanel("testing 1 2 3"),
# Sidebar with a slider input for number of bins
sidebarLayout(
sidebarPanel(
),
# Show a plot of the generated distribution
mainPanel(
fluidRow(
column(width = 2,
offset = 0,
align = "center",
actionButton(inputId = "fetch_data_inputId",
label = "data")
) #column
,
column(width = 10,
offset = 0,
align = "center",
DT::dataTableOutput("DT1")
) #column
)#fluidrow
)
)
)
# Define server logic required to draw a histogram
server <- function(input, output,session) {
merged_data <- eventReactive(input$fetch_data_inputId, {
req(iris)
button_data <- colnames(iris)
if( !is.null(cbind(iris[,1:4],iris3))) {
cbind(iris[,1:4],iris3)
} else {NULL}
}) #eventReactive
output$DT1 <- renderDataTable({#
rendered_table <- merged_data()
DT::datatable(rendered_table)
})
}
# Run the application
shinyApp(ui = ui, server = server)

Resources