0
votes

This question is related to another one I somewhat solved a few days ago.

My intention:

  1. To upload a csv with several columns.
  2. Plot each column in a line and points plot.
  3. Allow the user to select two different points from the plot, called first/last. The program always get the last two points clicked, order them to find first/last (first<=last).

Since the columns may differ from one dataset to another I have to create dynamically the structure of the app, and the problem is that I nest a observeEvent for the click in each plot inside a observeEvent (when the user changes the input dataset). The problem is that the observeEvent for the click depends on the dataset loaded (different columns).

What I do in the app is to create a pool with all the clicks in all the plots and extract the lastest two ones from each plot when needed, and I use this information to modify the plot with colors green and red.

To create two sample datasets:

inputdata<-data.frame(weekno=1:20, weekna=letters[1:20])
inputdata$normal<-dnorm(inputdata$weekno,10)
inputdata$beta<-dbeta(inputdata$weekno, 1, 1)
inputdata$gamma<-dgamma(inputdata$weekno, 1, 1)
inputdata$logistic<-dlogis(inputdata$weekno,10)
inputdata$poisson<-dpois(inputdata$weekno, 2)
test1<-inputdata[c("normal","gamma")]
row.names(test1)<-inputdata$weekna
test2<-inputdata[c("normal","logistic")]
row.names(test2)<-inputdata$weekna
write.csv(test1, file="test1.csv")
write.csv(test2, file="test2.csv")

The app:

library(ggplot2)
library(shiny)
library(shinydashboard)

tail.order<-function(i.data, i.n, i.order){
  res<-tail(i.data, n=i.n)
  res<-res[order(res[i.order]),]
  res$id.tail<-1:NROW(res)
  res
}

extract.two<-function(i.data, i.order, i.column){
  #data<-unique(i.data, fromLast=T)
  data<-i.data
  results <- do.call("rbind", by(data, data[i.column], tail.order, i.n=2, i.order=i.order))
  return(results)
}

ui <- fluidPage(
  fluidRow(
    column(4,fileInput('file', "Load file")),
    column(8,uiOutput("maintab"))
  )
)

server <- function(input, output) {

  values <- reactiveValues(origdata = NULL, plotdata = NULL, clickdata=NULL, dummy = TRUE)

  read_data <- reactive({
    infile <- input$file
    inpath <- infile$datapath
    inname <- infile$name
    if(is.null(infile)) readdata<-NULL else readdata<-read.csv(inpath, row.names=1)
    readdata
  })

  observeEvent(input$file, {
    datfile <- read_data()
    seasons<-names(datfile)
    plotdata<-cbind(data.frame(weekno=1:NROW(datfile),weekna=rownames(datfile), stringsAsFactors = F), datfile)
    origdata<-plotdata
    for (s in seasons) eval(parse(text=paste0("plotdata$'",as.character(s),"_color'<-'1'")))
    values$origdata <- origdata
    values$plotdata <- plotdata
    values$clickdata <- data.frame()
    rm("origdata", "plotdata")
    lapply(seasons, function(s){output[[paste0("plot_",as.character(s))]] <- renderPlot({
      ggplot(values$plotdata, aes_(x=as.name("weekno"), y=as.name(s))) +
        geom_point(aes_(as.name("weekno"), as.name(s), colour=as.name(paste0(s,"_color")), size=as.name(paste0(s,"_color")))) +
        scale_color_manual(values = c("1" = "grey", "2" = "red", "3" = "green")) +
        scale_size_manual(values = c("1" = 4, "2" = 6, "3" = 6)) +
        geom_line(aes_(x=as.name("weekno"), y=as.name(s)), color="#000000") +
        ggthemes::theme_few() +
        guides(color=FALSE, size=FALSE)
    })})
    lapply(seasons,function(s){
      observeEvent(input[[paste0("plot_",as.character(s),"_click")]], {
        np <- nearPoints(values$origdata, input[[paste0("plot_",as.character(s),"_click")]], maxpoints=1 , threshold = 10000)
        values$clickdata<-rbind(values$clickdata,cbind(data.frame(variable=as.character(s), stringsAsFactors = F), np))
        if (NROW(values$clickdata)>0){
          p0<-extract.two(values$clickdata,"weekno","variable")
          p1<-subset(p0, variable==as.character(s) & id.tail==1)
          p2<-subset(p0, variable==as.character(s) & id.tail==2)
          if (NROW(p1)>0) {
            values$plotdata[values$plotdata[,paste0(as.character(s),"_color")]=="2", paste0(as.character(s),"_color")]<-"1"
            values$plotdata[values$origdata$weekno==p1$weekno,paste0(as.character(s),"_color")]<-"2"
          }
          if (NROW(p2)>0){
            values$plotdata[values$plotdata[,paste0(as.character(s),"_color")]=="3",paste0(as.character(s),"_color")]<-"1"
            values$plotdata[values$origdata$weekno==p2$weekno,paste0(as.character(s),"_color")]<-"3"
          }
        }
      })
    })
  })

  output$maintab <- renderUI({
    datfile <- read_data()
    seasons<-names(datfile)
    do.call(tabsetPanel,
            c(
              lapply(seasons,function(s){
                call("tabPanel",s,call("plotOutput", outputId=paste0("plot_",as.character(s)),
                                       click = paste0("plot_",as.character(s),"_click")))
              }),
              list(
                tabPanel("First & last",tableOutput("results")),
                tabPanel("Clicks",tableOutput("resultsfull"))
              )
            )
    )
  })

  output$results<-renderTable({
    if (NROW(values$clickdata)>0) extract.two(values$clickdata,"weekno","variable")
  })

  output$resultsfull<-renderTable({
    values$clickdata
  })

}

shinyApp(ui, server)

To reproduce the bug:

Open test1.csv, a observeEvent for each column is created ("_click"). Open test2.csv, a observeEvent for each column is created ("_click").

Since test1.csv and test2.csv first column is called "normal" then the observeEvent$normal_click is created two times, so when I click the plot it writes two times the point clicked to the "clicks pool" (because there are two observeEvent related to that "normal_click".

When I extract the lastest two points from the "clicks pool", it retrieves the same point two times (the point I clicked and was stored two times because there was two observeEvents_click to the same plot).

I know to to circumvent the problem by uncommenting:

#data<-unique(i.data, fromLast=T)

This way it removes duplicates, but also denies the chance of telling the app to use the same point for first and last (first can be equal to last). And also this solution is not elegant since the structural problem is still there.

Any hints on how to fix this?

1
Don't nest observeEvent(). You have read_data() as a reactive value depending on input$file. Every reactive expression where you use read_data(), will be dependent on input$file as well. So the observeEvent(input$file, ...) is redundant and actually a source of a ton of possible problems. - Joris Meys
But how to rearrange the code, then? Remove the first observeEvent since I have a reactive read_data inside? But then, all the subsequent code will be run again? - Jose Lozano
I strongly suggest you take a good look at the following article and start again. You are still thinking in a procedural logic instead of a reactive logic, so there are quite a few problems actually. You have to create the correct dependencies and then everything works just fine. shiny.rstudio.com/articles/reactivity-overview.html Alas I don't have the time to decypher your code and completely rewrite it. - Joris Meys
I've read the article you suggested and I've found nothing of help. I still cant figure out how to declare observeEvent dynamically without duplicating them when switching the observer expression. - Jose Lozano

1 Answers

0
votes

I found another post talking about another problem that did lead me to the solution.

I have created a list of observeEvent that have been created not to allow duplicate the same observeEvent (called idscreated).

library(ggplot2)
library(shiny)
library(shinydashboard)

tail.order<-function(i.data, i.n, i.order){
  res<-tail(i.data, n=i.n)
  res<-res[order(res[i.order]),]
  res$id.tail<-1:NROW(res)
  res
}

extract.two<-function(i.data, i.order, i.column){
  data<-i.data
  results <- do.call("rbind", by(data, data[i.column], tail.order, i.n=2, i.order=i.order))
  return(results)
}

ui <- fluidPage(
  fluidRow(
    column(4,fileInput('file', "Load file")),
    column(8,uiOutput("maintab"))
  )
)

server <- function(input, output) {

  values <- reactiveValues(origdata = NULL, plotdata = NULL, clickdata=NULL, idscreated = character())

  read_data <- reactive({
    infile <- input$file
    inpath <- infile$datapath
    inname <- infile$name
    if(is.null(infile)) readdata<-NULL else readdata<-read.csv(inpath, row.names=1)
    readdata
  })

  observeEvent(read_data(), {
    datfile <- read_data()
    seasons<-names(datfile)
    plotdata<-cbind(data.frame(weekno=1:NROW(datfile),weekna=rownames(datfile), stringsAsFactors = F), datfile)
    origdata<-plotdata
    for (s in seasons) eval(parse(text=paste0("plotdata$'",as.character(s),"_color'<-'1'")))
    values$origdata <- origdata
    values$plotdata <- plotdata
    values$clickdata <- data.frame()
    rm("origdata", "plotdata")
    lapply(seasons, function(s){output[[paste0("plot_",as.character(s))]] <- renderPlot({
      ggplot(values$plotdata, aes_(x=as.name("weekno"), y=as.name(s))) +
        geom_point(aes_(as.name("weekno"), as.name(s), colour=as.name(paste0(s,"_color")), size=as.name(paste0(s,"_color")))) +
        scale_color_manual(values = c("1" = "grey", "2" = "red", "3" = "green", "4" = "purple")) +
        scale_size_manual(values = c("1" = 4, "2" = 6, "3" = 6, "4" = 8)) +
        geom_line(aes_(x=as.name("weekno"), y=as.name(s)), color="#000000") +
        ggthemes::theme_few() +
        guides(color=FALSE, size=FALSE)
    })})
    lapply(seasons,function(s){
      nameid<-paste0("plot_",as.character(s),"_click")
      if (!(nameid %in% values$idscreated)){
        values$idscreated<-c(values$idscreated,nameid)
      observeEvent(input[[nameid]], {
        np <- nearPoints(values$origdata, input[[nameid]], maxpoints=1 , threshold = 10000)
        values$clickdata<-rbind(values$clickdata,cbind(data.frame(variable=as.character(s), stringsAsFactors = F), np))
        if (NROW(values$clickdata)>0){
          p0<-extract.two(values$clickdata,"weekno","variable")
          p1<-subset(p0, variable==as.character(s) & id.tail==1)
          p2<-subset(p0, variable==as.character(s) & id.tail==2)
          if (NROW(p1)>0) {
            values$plotdata[values$plotdata[,paste0(as.character(s),"_color")]!="3", paste0(as.character(s),"_color")]<-"1"
            values$plotdata[values$origdata$weekno==p1$weekno,paste0(as.character(s),"_color")]<-"2"
          }
          if (NROW(p2)>0){
            values$plotdata[values$plotdata[,paste0(as.character(s),"_color")]!="2", paste0(as.character(s),"_color")]<-"1"
            values$plotdata[values$origdata$weekno==p2$weekno,paste0(as.character(s),"_color")]<-"3"
          }
          if (NROW(p1)>0 & NROW(p2)>0){
            if (p1$weekno==p2$weekno){
              values$plotdata[, paste0(as.character(s),"_color")]<-"1"
              values$plotdata[values$origdata$weekno==p1$weekno,paste0(as.character(s),"_color")]<-"4"
            }
          }

        }
      })
      }

    })
  })

  output$maintab <- renderUI({
    datfile <- read_data()
    seasons<-names(datfile)
    do.call(tabsetPanel,
            c(
              lapply(seasons,function(s){
                call("tabPanel",s,call("plotOutput", outputId=paste0("plot_",as.character(s)),
                                       click = paste0("plot_",as.character(s),"_click")))
              }),
              list(
                tabPanel("First & last",tableOutput("results")),
                tabPanel("Clicks",tableOutput("resultsfull"))
              )
            )
    )
  })

  output$results<-renderTable({
    if (NROW(values$clickdata)>0) extract.two(values$clickdata,"weekno","variable")
  })

  output$resultsfull<-renderTable({
    values$clickdata
  })

}

shinyApp(ui, server)