Plot updates without button press - incorrect isolate() usage?

677 views
Skip to first unread message

Shraddha Pai

unread,
May 14, 2014, 9:16:56 AM5/14/14
to shiny-...@googlegroups.com
Good morning, shiny-discuss group,
I'm having trouble understanding how to force updates to a UI element to wait for a button press (and am trying to use isolate()) to do this.
The shiny app does something general which we want it to do for several datasets.
The setup is as follows (I call it the "two step"), and below is the non-working toy code I have written to get the two-step load to work.
-----
1. User selects a dataset. Rest of UI is blank at this point, just the dropbox to select the dataset exists. Clicks: "Make active dataset".
Effect: The UI now appears, containing sample names for the selected dataset from step 1, among other things. There is a plot panel which is blank.

2. Now user selects which samples they want to view + other settings. Clicks: "Now plot it!"
Effect: Plot appears containing data for selected samples.
In the backend: only data for selected samples are being loaded.

3. User decides they want to change sample selection. They make changes, add some samples, remove some samples, change x/y axis.
Effect: Should be nothing because "now plot it" has not been pushed! ----> PROBLEM: Right now the data reloads with each update.

Here is my code, I have tried to use isolate() as best as I could but I must be doing something wrong. Sometimes I put isolate() and the UI needs two clicks to update.

Any guidance from the Shiny community would be great. This functionality is the core to generalizing all genomics-based Shiny tools in our lab.

Thanks,
Shraddha


-----
ui.R
suppressWarnings(shinyUI(fluidPage(
    h3("Choosing multiple datasets"),
    uiOutput("pickData"),actionButton("getData", "Make active dataset"),
    uiOutput("pickSamples"),actionButton("loadPlot","Now plot it!"),
    plotOutput("samplePlot")
)))


server.R

shinyServer(function(input,output) {

    # which button was last pressed?
    values <- reactiveValues()
    values$lastAction <- NULL
    observe({if (input$getData!=0) {values$lastAction <- "data"}})
    observe({if (input$loadPlot!=0) {values$lastAction <- "plot"}})
    observe({print(values$lastAction)})


    # tier 0 : shows up when page is loaded.
    output$pickData <- renderUI({selectInput("dataset", "Select dataset:", names(configSet))    })
    updateConfig <- reactive({
           if (input$getData == 0) return(NULL)
        print("updated config")
        isolate({return(configSet[[input$dataset]])})
   })

   
    # tier 1: shows up when "make active dataset" button is clicked.
    output$pickSamples <- renderUI({
        configParams <- updateConfig(); if (is.null(configParams)) return(NULL)
        print("updating sample box")
        print(configParams$samples)
        selectInput("samples", "Select samples:", configParams$samples, multiple=T,selected=NULL)
    })


    # tier 2: triggered when "make active dataset" or "now plot it" buttons are selected
    getData <- reactive({
        if (is.null(values$lastAction)) return(NULL)
        if (values$lastAction=="data") return(NULL) # don't update if dataset was just updated.

        #TODO: Prevent data from updating unless "now plot it" is pushed.
        #We want the plot to be update-able but not the underlying data.
        configParams <- updateConfig()
        samps <- input$samples
        if (length(samps)<1) return(NULL)
        cat("updating data")
        mat <- matrix(rnorm(10*length(samps)), byrow=F, ncol=length(samps));
        colnames(mat) <- samps
        return(list(data=mat, configParams=configParams))
   })

    # tier 2: shows up when either dataset or samples buttons are clicked.
    output$samplePlot <- renderPlot({
        out <- getData();
        if (is.null(out)) return(NULL)
        configParams <- out$configParams; dat <- out$data
        print("making plot")
        plot(0,0,type='n', xlab='x',ylab='y',main=sprintf("%s: interesting patterns", configParams$name),xlim=c(0,10),ylim=c(-2,2))
        for (k in 1:ncol(dat)) { points(1:nrow(dat), dat[,k],col=k,type='o',pch=16); }
        legend("topright", fill=1:ncol(dat),legend=colnames(dat))
    })
})



Shraddha Pai

unread,
May 15, 2014, 8:48:40 AM5/15/14
to shiny-...@googlegroups.com
Hello again,

OK this seems to be the solution to the problem. It needed four pieces in place:

1) A reactiveValues() object (values$lastAction) that tracked which button was pushed last -- values are { NULL, data, plot }.
2) the getData() function - which doesn't look at item 1, but rather depends only on the second button (loadPlot)
3) the getData() function looks up which samples were selected but using isolate().
4) the renderPlot() function which returns NULL for values$lastAction != "plot".

Here is the functional code incase someone else may find it useful.
To recap, this code implements a two-step (two button) plot-loader:
1) First button loads the configuration for the selected dataset.
2) Second button loads the data and shows the plot.
The plot is thereafter automatically reactive to all changes that don't involve changing selected samples.

Attached are the demo config files that need to be in the same directory as the shiny app.
-----
ui.R:

suppressMessages(require(shiny))


suppressWarnings(shinyUI(fluidPage(
    h3("Choosing multiple datasets"),
    uiOutput("pickData"),actionButton("getData", "Make active dataset"),
    uiOutput("pickSamples"),
    numericInput("lty","Line type:", 1:3),

    actionButton("loadPlot","Now plot it!"),
    plotOutput("samplePlot")
)))

------
server.R:

# ########################################
# Helper function
# ########################################
 
readConfig <- function# get contents of individual config file.
( f ##<<(character) path to config file
){
dat <- read.delim(f,sep="\t",comment.char="#",as.is=T,header=F)
configParams <- list()
for (k in 1:nrow(dat)) {
    configParams[[dat[k,1]]] <- dat[k,2]
    if (dat[k,1]=="samples") configParams[[dat[k,1]]] <- unlist(strsplit(dat[k,2],","))
}
return(configParams)
### (list) Key-value pairs of config information
}

listConfig <- function# list all config files available
(indir=".") {
fList <- dir(path=indir,pattern=".txt")
configSet <- list()
for (f in fList) {
    tmp <- readConfig(sprintf("%s/%s", indir,f));
    configSet[[tmp$name]] <- tmp
}

return(configSet)
### (list of lists) key: name of dataset, value: config key-value pairs
}

# ########################################
# Main function
# ########################################

configSet <- listConfig()

currentSamps <- NULL

shinyServer(function(input,output) {

    # which button was last pressed?
    # this is needed to separate data load from plot plot.

    values <- reactiveValues()
    values$lastAction <- NULL
    observe({if (input$getData!=0) {values$lastAction <- "data"}})
    observe({if (input$loadPlot!=0) {values$lastAction <- "plot"}})

    # tier 0 : shows up when page is loaded.
    output$pickData <- renderUI({selectInput("dataset", "Select dataset:", names(configSet))    })
    updateConfig <- reactive({
           if (input$getData == 0) return(NULL) # only depends on first button

        print("updated config")
        isolate({return(configSet[[input$dataset]])})
   })

    # tier 1: shows up when "make active dataset" button is clicked.
    output$pickSamples <- renderUI({
        configParams <- updateConfig(); if (is.null(configParams)) return(NULL)
        print("updating sample box")
        print(configParams$samples)
        selectInput("samples", "Select samples:", configParams$samples, multiple=T,selected=NULL)
    })

    # tier 2: triggered when "make active dataset" or "now plot it" buttons are selected
    getData <- reactive({
    if (input$loadPlot==0) return(NULL) # only depends on second button
    isolate({
        samps <- input$samples
        cat("updating data")
        if (length(samps)<1) return(NULL)

        mat <- matrix(rnorm(10*length(samps)), byrow=F, ncol=length(samps));
        colnames(mat) <- samps
        return(mat)

    })
   })

    # tier 2: shows up when either dataset or samples buttons are clicked.
    output$samplePlot <- renderPlot({
        if (is.null(values$lastAction)) return(NULL)
        if (values$lastAction=="data") return(NULL)  # don't plot if data were loaded; wait till samples are selected. Implicitly depends on plot button being pressed.

        configParams <- updateConfig()
        dat <- getData();  if (is.null(dat)) return(NULL)

       
        print("making plot")
        plot(0,0,type='n', xlab='x',ylab='y',main=sprintf("%s: interesting patterns", configParams$name),
            xlim=c(0,10),ylim=c(-2,2))
        for (k in 1:ncol(dat)) { points(1:nrow(dat), dat[,k],col=k,type='o',pch=16,lty=input$lty); }

        legend("topright", fill=1:ncol(dat),legend=colnames(dat))

    })

})

data2.txt
data1.txt
Reply all
Reply to author
Forward
0 new messages