I came across a problem while using rCharts in my Shiny application. I generate dynamically 1-3 tabPanels (depending on the user selection) and in each of them one plot is rendered. Plot may be of two types: simple (with graphics libary) or rCharts (with morris library). The whole tabsetPanel is rendered within the uiOutput component, each time an user changes his input. The type of the ui output (plotOutput for simple plot or showOutput for rCharts) is defined when a tabPanel is rendered, so the plot does have a proper environment.
And the problem: simple plots (with graphics, ggplot2 etc.) do work OK - they are displayed correctly in a tabPanel. However, when I work with application and have 2 or 3 rCharts to be displayed, it happens that one chart is not displaying - almost at all (see the images below). Of course, such a problem does not appear with simple plots.
I tried to have the output size fixed, size flexible, but the problem still exists. I have varsions of R and the libraries as follows:
> R.Version()$version.string
[1] "R version 3.0.1 (2013-05-16)"
> packageVersion("Shiny")
[1] ‘0.7.0’
> packageVersion("rCharts")
[1] ‘0.3.51’
Thank you a lot for any suggestions.
rCharts working OK:
rCharts FAIL to be dispkayed ok:
EDIT: my code below:
UI
library(shiny)
library(Epi)
shinyUI(pageWithSidebar(
headerPanel("Header"),
sidebarPanel(
checkboxInput(inputId = "checkboxInputx", label = "function: x", value = TRUE),
checkboxInput(inputId = "checkboxInputxpower2", label = "function: x^2", value = FALSE),
checkboxInput(inputId = "checkboxInput2x", label = "function: 2x", value = FALSE),
actionButton("gobutton","GO!")
),
mainPanel(
radioButtons("plottypechoice", "Choose plot type", c("simple", "rCharts")),
uiOutput("plotpanelcontent")
)
))
SERVER
library(shiny)
library(rCharts)
library(Epi)
library(reshape2)
# build data frame
x <- 1:100
df <- data.frame(x, x^2, 2*x)
names(df) <- c("x", "xpower2", "2productx")
shinyServer(function(input, output) {
# generate tabsetPanel with tabPlots with plot of selected type
output$plotpanelcontent <- renderUI({
if(input$gobutton != 0){
# collect tab names
tab.names <- vector()
if(input$checkboxInputx) tab.names <- c(tab.names, "x")
if(input$checkboxInputxpower2) tab.names <- c(tab.names, "xpower2")
if(input$checkboxInput2x) tab.names <- c(tab.names, "2productx")
print(tab.names)
# render tabs
tabs <- lapply(tab.names, function(tab.name){
# define tabPanel content depending on plot type selection
if(input$plottypechoice == "simple")
tab <- tabPanel(tab.name, plotOutput(paste0("simpleplot", tab.name)))
else
tab <- tabPanel(tab.name, showOutput(paste0("rchartplot", tab.name), "morris"))
return(tab)
})
return(do.call(tabsetPanel, tabs))
}
})
# Render simple plots
output$simpleplotx <- renderPlot({
print(plot(df[,1], df[,1]))
plot(df[,1], df[,1])
})
output$simpleplotxpower2 <- renderPlot({
print(plot(df[,1], df[,2]))
plot(df[,1], df[,2])
})
output$simpleplot2productx <- renderPlot({
print(plot(df[,1], df[,3]))
plot(df[,1], df[,3])
})
# Render rCharts
output$rchartplotx <- renderChart({
plot <- mPlot(x="x", y="x", type = "Line", data = df)
plot$set(dom = "rchartplotx")
return(plot)
})
output$rchartplotxpower2 <- renderChart({
plot <- mPlot(x="x", y="xpower2", type = "Line", data = df)
plot$set(dom = "rchartplotxpower2")
return(plot)
})
output$rchartplot2productx <- renderChart({
plot <- mPlot(x="x", y="2productx", type = "Line", data = df)
plot$set(dom = "rchartplot2productx")
return(plot)
})
})
UPDATE:
I have asked Olly Smith, an author of morris.js library, to suggest a solution and I received a following response:
Morris can't correctly draw charts when they're not displayed on screen. When using Morris in tabbed applications, you need to redraw the active chart whenever the tab selection changes. There's a redraw() method on the chart objects that each of the Morris.Line/Bar/Donut constructors return that you can use to do this. Unfortunately, it's currently undocumented, but it's a stable part of the API now so it's safe to use.
UPDATE 2. :
I followed the Ramnath suggestions and update both Shiny and rCharts libraries version:
> packageVersion("Shiny")
[1] ‘0.8.0’
> packageVersion("rCharts")
[1] ‘0.3.53’
and run the code just after restarting the R session. Unfortunately, plots seem to behave in even stranger way now. After doing actions in the following order:
set: "function: x", "rCharts", GO [OK],
add: "function: x^2" [see "not_ok_1" image attached below in the post],
add: "function: 2x" [see "not_ok_2" image attached below in the post].
I received the plot visualizations as follows:
OK
"not_ok_1" image
"not_ok_2" image
Related
The example below is using ggtree in which I can brush the tips in the phylogeny and add an annotation label ("clade"). Steps to get the app going -
load the tree - called vert.tree
brush over (highlight) tips (test with human and lemur) and press the 'annotate tree' button to add the label in red.
What I want to do is add another annotation onto the tree while maintaining the first annotation (human and lemur). For example, a second label for the pig and cow tips. Essentially, I want to be able to add a line onto a phylogenetic tree based on user input and then repeat that based on second input from the user while maintaining the first line on the image. Currently, the label gets reset every time I brush a different pair so only one annotation is displayed at a time.
# This is a Shiny web application. You can run the application by clicking
# the 'Run App' button above.
library(shiny)
library(treeio)
library(ggtree)
library(phytools)
library(ape)
#make phylogenetic tree
text.string <-"(((((((cow, pig),whale),(bat,(lemur,human))),(robin,iguana)),coelacanth),gold_fish),shark);"
#read in the tree
vert.tree<-ape::read.tree(text=text.string)
# Define UI for application that draws a histogram
ui <- fluidPage(
# Application title
titlePanel("Test"),
actionButton("add_annotation","Add clade annotation"),
# Show a plot of the generated distribution
mainPanel(plotOutput("treeDisplay", brush ="plot_brush")
)
)
# Define server logic required to draw a histogram
server <- function(input, output) {
#reactive that holds base tree - this is how I am building the base tree
make_tree <- reactive({
ggtree::ggtree(vert.tree)+
ggtree::geom_tiplab()+
ggplot2::xlim(NA, 10)})
#render base tree
output$treeDisplay <- renderPlot({
make_tree()
})
#reactive that holds the brushed points on a plot
dataWithSelection <- reactive({
brushedPoints(make_tree()$data, input$plot_brush)
})
#add to label to vector if isTip == True
dataWithSelection2 <- reactive({
tipVector <- c()
for (i in 1:length(dataWithSelection()$label)){ if(dataWithSelection()$isTip[i] == TRUE) tipVector <- c(tipVector,dataWithSelection()$label[i])}
return(tipVector)
})
# incorporate the tipVector information for adding layer
layer <- reactive({
ggtree::geom_cladelabel(node=phytools::findMRCA(ape::as.phylo(make_tree()), dataWithSelection2()), label = "Clade", color = "red")
})
#display that layer onto the tree
observeEvent(input$add_annotation, {
output$treeDisplay <- renderPlot({make_tree() + layer()})
})
}
# Run the application
shinyApp(ui = ui, server = server)
Suggestions greatly appreciated!
updated to include a base tree (vert.tree)
Hope you found the solution already, but if not, here is an approach.
First it helps to do the problem in a non-shiny setting. What we need is a list that accumulates vectors of tips. Then we cycle over this list to generate annotations:
tree_plot <-
ggtree::ggtree(vert.tree) +
ggtree::geom_tiplab() +
ggplot2::xlim(NA, 10)
tip_vector <- list(c("human", "lemur"), c("pig", "cow"))
make_layer <- function(tree, tips, label, color) {
ggtree::geom_cladelabel(
node = phytools::findMRCA(ape::as.phylo(tree), tips),
label = label,
color = color
)
}
x + lapply(1:2, function(i)
make_layer(
tree_plot,
tips = tip_vector[[i]],
label = paste("Clade", i),
color = "red"
))
The key bit is in the lapply call, where generate the annotation layer for each member of the tip_vector list.
Now that this is working, we go to shiny. In your app, every time you click add annotation the brushed points data frame is refreshed and your tip vector is just a vector of the newly brushed tips. Any previously selected clades are forgotten.
To remember these, we can introduce two reactive values. One n_annotations is a numeric reactiveVal counting how many times we click add annotation. The other annotations is a reactiveValues list which stores all the brushed clades under the names paste0("ann", n_annotations()).
Then, the actual adding of the layer of annotations proceeds as in the non-reactive example with lapply cycling over the reactiveValues.
App code:
# This is a Shiny web application. You can run the application by clicking
# the 'Run App' button above.
library(shiny)
library(treeio)
library(ggtree)
library(phytools)
library(ape)
#make phylogenetic tree
text.string <-"(((((((cow, pig),whale),(bat,(lemur,human))),(robin,iguana)),coelacanth),gold_fish),shark);"
#read in the tree
vert.tree<-ape::read.tree(text=text.string)
# Define UI for application that draws a histogram
ui <- fluidPage(
# Application title
titlePanel("Test"),
actionButton("add_annotation","Add clade annotation"),
# Show a plot of the generated distribution
mainPanel(plotOutput("treeDisplay", brush ="plot_brush"),
plotOutput("treeDisplay2")
)
)
# Define server logic required to draw a histogram
server <- function(input, output) {
#reactive that holds base tree - this is how I am building the base tree
make_tree <- reactive({
ggtree::ggtree(vert.tree) +
ggtree::geom_tiplab() +
ggplot2::xlim(NA, 10)
})
#render base tree
output$treeDisplay <- renderPlot({
make_tree()
})
# Initialize a reactive value and set to zero
n_annotations <- reactiveVal(0)
annotations <- reactiveValues()
#reactive that holds the brushed points on a plot
dataWithSelection <- reactive({
brushedPoints(make_tree()$data, input$plot_brush)
})
#add to label to vector if isTip == True
dataWithSelection2 <- eventReactive(input$plot_brush, {
tipVector <- c()
for (i in 1:length(dataWithSelection()$label)) {
if (dataWithSelection()$isTip[i] == TRUE)
tipVector <- c(tipVector, dataWithSelection()$label[i])
}
tipVector
})
make_layer <- function(tree, tips, label, color) {
ggtree::geom_cladelabel(
node = phytools::findMRCA(ape::as.phylo(tree), tips),
label = label,
color = color
)
}
#display that layer onto the tree
anno_plot <- eventReactive(input$add_annotation, {
# update the reactive value
new <- n_annotations() + 1
n_annotations(new)
annotations[[paste0("ann", n_annotations())]] <- dataWithSelection2()
plt <-
make_tree() +
lapply(1:n_annotations(), function(i)
make_layer(
make_tree(),
tips = annotations[[paste0("ann", i)]],
label = paste("Clade", i),
color = "red"
))
return(plt)
})
output$treeDisplay2 <- renderPlot({
anno_plot()
})
}
# Run the application
shinyApp(ui = ui, server = server)
Edit: how the reactive values work without the phylo stuff
I tried to comment this thoroughly.
ui <- basicPage(
actionButton("add_anno", "Add annotation"),
helpText("n_annotation is counting clicks"),
textOutput("n_anno"),
helpText("clades is accumulating clades"),
verbatimTextOutput("clades")
)
server <- function(input, output) {
# this initializes a reactive value
# and sets the initial state to 0
n_anno <- reactiveVal(0)
# makes an empty reactive list
# this can be populated and index
# like a normal list
# e.g., clades[["first"]] <- c("bird", "lizard")
clades <- reactiveValues()
observeEvent(input$add_anno, {
# increment the number of clicks
new_count <- n_anno() + 1
# update the reactiveValue
# works the same way we initialized it
# except instead of zero we set the incremented value
n_anno(new_count)
# making a name for an element in the clades list
# we use the n_anno number of clicks to increment the clades
# message just prints it on console
message( paste0("clade", n_anno() ))
# populate the list of clades for annotations
clades[[ paste0("clade", n_anno() ) ]] <- sample(LETTERS, 3)
})
output$n_anno <- renderText(n_anno())
output$clades <- renderPrint(
str(reactiveValuesToList(clades))
)
}
shinyApp(ui, server)
hmmm - okay when I tested your suggestion
dataWithSelection2 <- reactive({
tipVector <- c()
for (i in 1:length(dataWithSelection()$label)){
if(!is.null(dataWithSelection()$isTip[i])) {
tipVector <- c(tipVector,dataWithSelection()$label[i])
}
}
return(tipVector)
})
I get the error: missing value where TRUE/FALSE needed....
I have a chart in Shiny generated with the HighCharter package. I would like to surpress the "No data to display" message which is shown when the series to be plotted is empty. In my case, the actual content of the plot is shown as a plotLines (variable age below). However, in order for HighCharter to display the plotLines, it needs data. This is the reason, why I add the line (remove that line to see what I mean):
%>% hc_series(list(data=c(), visible=FALSE, id="dummy"))
Can this be done?
Here is a sample using reprex(venue="r"): I would like that the background gradient and the plotLines are showed but the message "No data to display" should be hidden.
library(shiny)
library(highcharter)
#> Highcharts (www.highcharts.com) is a Highsoft software product which is
#> not free for commercial and Governmental use
# layout
ui <- fluidPage(highchartOutput("highchart_slider", height = "200px"))
server <- function(input, output) {
# This value comes from the backend and is variable. It is in [-100, 100]
age <- 20
output$highchart_slider <- renderHighchart({
hcSlider <- highchart() %>%
hc_chart(renderTo= "container",
defaultSeriesType = 'bar',
plotBackgroundColor=list(
linearGradient = list(x1=0, x2=1, y1=0, y2=0),
stops=list(list(0, '#bf0000'),list(0.45, '#e1e218'),
list(0.55, '#e1e218'),list(1, 'darkgreen')))) %>%
hc_yAxis(tickInterval=100, min=-100, max=100,
plotLines=list(list(
label = list(text = "title", align = 'center', verticalAlign = 'top'),
color = "black", width = 4, value = age, y = -2))
) %>%
hc_series(list(data=c(), visible=FALSE, id="dummy"))
# display plot
hcSlider
})
}
# start the app
shinyApp(ui = ui, server = server)
#' <!--html_preserve-->
#' Shiny applications not supported in static R Markdown documents
#' <!--/html_preserve-->
Your question has no reproducible example, but as I understand, you want to prevent a plot to be plotted, whenever no data is available?
I would advice looking into req()
https://shiny.rstudio.com/articles/req.html
If you want a plot to be only plotted when let's say data is available,
do:
output$plot <- renderPlot({
req(data)
...
})
req will not only stop the plot when the data is NULL or FALSE, but also when the user has no data selected
this is my R code:
output$heatmap_viewed_ads <- renderPlotly({
plot_ly( x = c(1,2,3,4,5), y = casted_viewed_ads$FEED, z = as.matrix(casted_viewed_ads[2:15]), type = "heatmap",colors = "Greens" )%>%
layout(title = "#Views", xaxis=list(title="Position of Display"))})
I am plotting right here this data. I do now want to just show the matrix that is generated by
as.matrix(casted_viewed_ads[2:15])
Can someone help me please? I'm new to R. I'm using Shiny and Plotly.
Thanks!
You can use renderTable to display your matrix in the Shiny app.
Server
output$matrix <- renderTable({
yourmatrix <- as.matrix(casted_viewed_ads[2:15])
yourmatrix
})
UI
mainPanel(
plotOutput("heatmap_viewed_ads"), #you can eliminate this line if you just want to show the table
tableOutput("matrix")
)
The aim of this exercise is to allow users to compare two different models based on their inputs. To do this, I have created an action button that asks users to specify their base model, and a reset button that takes the dataset back to before the baseline was added. The "base" logical determines whether the user wishes to include the base or not.
Once the add baseline actionbutton is clicked, the current state of the data.frame is saved and grouping variable is renamed with "baseline" added before it (using paste). Users can select a different model which renders in comparison to this static base.
For some reason, I cannot get the observe event to change the dataset. The observe event creates the baseline dataset fine (tested with print() ), however, the if() function does not alter "data" and therefore stops the base added to the ggplot. The code is written like this for two reasons. 1) by including the if() function after the observe event, any further changes to data only changes "data", it then gets added to the unchanged baseline data. 2) Also allows for the creation of the reset button which simply resets the data.frame to before the rbinding took place.
This small issue has infuriated me and I cannot see where I am going wrong. Cheers in advance for any help people can provide. There are simplier ways to do this (open to suggestions), however, the iris data is only an example of the function, and the actual version is more complex.
library("ggplot2")
if (interactive()) {
ui <- fluidPage(
selectInput("rows", label = h3("Choose your species"),
choices = list("setosa", "versicolor", "virginica")
),
actionButton("base", "Create baseline"),
actionButton("reset", "Reset baseline"),
plotOutput(outputId = "plot")
) # close fluid page
server <- function(input, output) {
output$plot <- renderPlot({ # create plot
base <- "no" # create baseline indicator which we can change once the observeevent below is changed
data <- iris
data <- iris[which(data$Species == input$rows),] # Get datasubset based on user input
observeEvent(input$base, { # If base is Pressed, run code below:
baseline <- data # Make Baseline Data by duplicating the users' specification
baseline$Species <- paste("Baseline",
data$Species, sep = "_") # Rename the grouping variable to add Baseline B4 it
base <- "yes" # Change our indicator of whether a baseline had been made to yes
}) # Close observe Event
observeEvent(input$reset, {
base <- "no" # This is placed before the rbind so that if we want to reset it will stop the merging of the two dataframes before it happens.
})
if (base == "yes") {
data <- rbind(data, baseline) # Run once the observe event has changed baseline to yes.This is kept seperatel that way any subsequent changes to data will not effect
# the final data. This command will simple add the base onto the changed "data" before plotting
}
observeEvent(input$reset, {
base <- "no"
})
ggplot(data, aes(x=Petal.Width, y = as.numeric(Sepal.Width), colour = Species)) + # variable = each dataset selected, value = respective values for that model
labs(x="Hypothetical X", y="Hypothetical X") +
geom_line()
}) # Close Render Plot
} # Close Serve Function
shinyApp(ui, server)
}
EXAMPLE TWO WITH REACTIVE OBJECT
library(shiny)
library(ggplot2)
library("tidyr")
library("dplyr")
library("data.table")
# Lets make a fake dataset called "Data". Has 4 variable options and
the Ages each data point relates to.
Ages <- 1:750
Variable1 <- rnorm(n=750, sd = 2, mean = 0)
Variable2 <- rnorm(n=750, sd = 1, mean = 2)
Variable3 <- rnorm(n=750, sd = 8, mean = 6)
Variable4 <- rnorm(n=750, sd = 3, mean = 3)
Data <- as.data.frame(cbind(Ages, Variable1, Variable2, Variable3,
Variable4) )
### UI
ui <- fluidPage(
checkboxGroupInput(inputId = "columns",
label = h4("Which Variables would you like in your
model?"), # Input Checkbox
choices = c("Variable1", "Variable2", "Variable3",
"Variable4")),
plotOutput(outputId = "plot"),
# Lets have our plot
actionButton("base", "Create baseline"),
# Baseline action
actionButton("reset", "Reset baseline") # Reset Action
) # Close UI
server <- function(input, output) {
output$plot <- renderPlot({
validate(need(!is.null(input$columns), 'Please tick a box to show a
plot.')) # Place a please choose columns for null input
data <- gather(select(Data, "Ages", input$columns), variable, value, -
Ages) ## Just doing a little data manipulation to change from wide to
long form. This allows for calculations down the track and easier
plotting
# Now we can modify the data in some way, for example adding 1. Will
eventually add lots of model modifications here.
data$value <- data$value + 1
rVals <- reactiveValues() # Now we create the reactive
values object
rVals[['data']] <- data # Making a reactive values
function. Place Data as "data".
observeEvent(input$base,{
baseline <- data
baseline$variable <- paste("Baseline",
baseline$variable, sep = "_")
# Rename Variables to Baseline preamble
rVals[['baseline']] <- baseline
# Put the new data into the reactive object under "baseline"
})
observeEvent(input$reset,{ # Reset button will wipe the
data
rVals[['baseline']] <- NULL
})
if(!is.null(rVals[['baseline']])) # if a baseline has been .
created, then
{rVals[['final']] <- bind_rows(rVals[['data']], rVals[['baseline']])
# Here we can simply bind the two datasets together if Baseline exists
} else {rVals[['final']] <- rVals[['data']]}
# Otherwise we can use keep it as it is
## Make our Plot !
ggplot(rVals[['final']], aes(x=Ages, y = as.numeric(value), colour =
variable)) + # variable = each dataset selected, value = respective
values for that model
labs(x="Age", y="value") +
geom_line()
}) ## Close the render plot
} ## Close the server
shinyApp(ui, server)
You have observer inside reactive expression, i have seen this causing problems on number of occasions when i was correcting shiny code. Create reactive expression (your plot function) and observers only to specify which is the baseline value of species (character string) then feed this to filtering data inside the plot function:
library(shiny)
library(ggplot2)
ui <- fluidPage(
selectInput("rows", label = h3("Choose your species"),
choices = list("setosa", "versicolor", "virginica")
),
actionButton("base", "Create baseline"),
actionButton("reset", "Reset baseline"),
plotOutput(outputId = "plot")
) # close fluid page
server <- function(input, output) {
rVals = reactiveValues()
rVals[['data']] = iris
rVals[['baseline']] = NULL
output$plot <- renderPlot({
# here we duplicate table to manipulate it before rendering
# the reason for duplicate is that you dont want to affect your
# base data as it may be used elsewhere
# note that due to R's copy-on-write this may be expensive operation and
# have impact on app performance
# in all cases using data.table package is recommended to mitigate
# some of the CoW implications
render.data = rVals[['data']][rVals[['data']][['Species']] %in% c(rVals[['baseline']],input$rows),]
# here manipulate render.data
# and then continue with plot
ggplot(data=render.data,
aes(x=Petal.Width, y = as.numeric(Sepal.Width), colour = Species,group=Species)
) +
labs(x="Hypothetical X", y="Hypothetical X") +
geom_line()
})
observeEvent(input$base,{
rVals[['baseline']]=input$rows
})
observeEvent(input$reset,{
rVals[['baseline']]=NULL
})
}
shinyApp(ui, server)
I have a graph that I only want to display if it wouldn't take too long to render, otherwise I want to have a button the user can press to render it.
My problem is that the graph renders before being hidden in some circumstances.
Open this minimally reproduced example up and try the following steps
UI.r
shinyUI(fluidPage(
titlePanel("when to render"),
fluidRow(
column(2,radioButtons("TheChoice", label= "choices",
choices = list("only one" = 1,"just two" = 2,"three" = 3,"all four" = 4),selected = 1))
),
fluidRow(
mainPanel(tabsetPanel(tabPanel("Blank page",NULL),
tabPanel("plot output",
verbatimTextOutput("validRows"),
conditionalPanel(
condition = "output.validRows < 3",
plotOutput("thePlot")
))
)
)
)
))
Server.r
library(shiny)
library(ggplot)
library(reshape2)
`[` <- function(...) base::`[`(...,drop=FALSE)
myTable <- matrix(1:20,ncol=4)
colnames(myTable) <- c("Index","catone","cattwo","catthree")
rownames(myTable) <- c("first","second","third","fourth","fifth")
shinyServer(function(input, output,session) {
TableIndex <- reactiveValues(X = NULL)
observe({
print("updating TableIndex")
if(input$TheChoice == 1) TableIndex$X <- 1 else TableIndex$X <- 1:input$TheChoice
})
graphedTableData <- reactive({
return(myTable[TableIndex$X,2])
})
dataSubset <-
reactive({
print(paste0("calculating subset with ", length(TableIndex$X)," rows"))
renderedData <- graphedTableData()
renderedData2 <- melt(renderedData,id=rownames(renderedData))
colnames(renderedData2)<-c("catone","cattwo","catthree")
return(renderedData2)
})
output$thePlot <- renderPlot({
print(paste0("Rendering Plot with ", length(TableIndex$X)," rows"))
theSubset <- dataSubset()
ggplot(data=theSubset,aes(x=catone,y=cattwo,colour=catthree)) +
geom_line()
})
output$validRows <- reactive({print("updating validRows");length(TableIndex$X)})
})
click on the plot output tab: a plot renders and is then output (with one data row used)
select choice 2: a plot renders and is then output (with two data rows used)
select choice 3: a plot renders and is THEN hidden :(
select choice 4: nothing is rendered/output (good)
now going between 3 and 4, nothing happens (good)
By looking at the console output, you can see that "TableIndex" is updated first, so it seems like this should never render with choice 3, since the plot would immediately disappear before it was updated. Presumably, that updating already triggered the reactive() and renderPlot() though.
How do I prevent these functions from executing in this order? I know I could just short circuit the reactive() and renderPlot() by checking TableIndex$X in the first line, but that seems hacky and I'm just learning shiny so I'm hoping for a cleaner solution
Bonus points if you implement the appearance of a render button instead of displaying nothing for choices 3 and 4. I've yet to attempt that but I believe it would be something with renderUI() ?
(TableIndex is calculated separately and then accessed in this way because in real life, it is finding the relevant indices from a large table and then applying those same indices to other tables. Assume that the index finding is fast)