Search code examples
rhoverflexdashboard

R reactive elements: use "i" (for i in 1:8) in generic id


I have a flexdashboard with several graphs and I want to add a hover function with a tooltip showing data on 8 of them.

To avoid copying the whole hoveroptions + tooltip code 8 times, I use a loop to create the hoveroptions 8x (it's 8 times the same options) with different names (1-8) and turned the tooltip (a renderUI) into a function with arguments "data" and "i".

I need the same hoveroptions with different names because if I use only one for all, graph 1 will show a tooltip when I hover on graph 2, which I don't want obviously. All my graphs use parts of the same dataset, but show different variables on the same position. E.g graph 1 show variable Wtot, while graph 2 show variable Mtot which is on the same plant/row coordinate.

I have a problem in my tooltipcode: how to write a generic version of the hoverOpts id? The id's are plot_hover1, plot_hover2 and so on. Using paste("input$plot_hover",i,sep="") doesn't work. It creates a character string, looking exactly like the id, but it doesn't have the same meaning apparently.

Code (1 plot example)

---
title: "Test"
output: 
  flexdashboard::flex_dashboard:
    orientation: columns
    vertical_layout: fill
runtime: shiny
---

```{r setup, include=FALSE}
library(flexdashboard)
library(shiny)
library(ggplot2)

# data
Wtot <- c(10,65,139,87) 
plant <- c(15,15,30,30) 
row <- c(10,20,10,20) 
df <- data.frame(Wtot,plant,row)

# hover options x8
HO <- list(NULL)
for (i in 1:8){
  HO[[i]] <- hoverOpts(id = paste("plot_hover",i,sep=""), delay = 300,
                       delayType = c("debounce", "throttle"), clip = TRUE, nullOutside = TRUE)
}

# plot function
Wplot <- function(dat,i){
renderPlot({
  ggplot(data=dat, aes(row, plant, colour=Wtot)) +
    xlim(0,40) +
    ylim(0,50) +
    coord_equal() +
    geom_point(aes(fill=Wtot, size=Wtot), colour="black", pch=22) +
    scale_fill_gradient2(low="green", mid="yellow", high="red", na.value="grey",
                         limits=c(0,300), midpoint=150, breaks=c(0,75,150,225,300)) +
    scale_size_continuous(range=c(3,6), limits=c(0,300), breaks=c(0,75,150,225,300)) +
    guides(fill=guide_legend(), size = guide_legend())
}, outputArgs = list(hover = HO[[i]])) # <-- i works
}

# tooltip function
tooltip <- function(dat,i){
renderUI({
  hover <- input$plot_hover1 # <-- problem with id, "1" should be "i"
  point <- nearPoints(dat, hover)
  if (nrow(point) == 0) return(NULL)

  left_pct <- (hover$x - hover$domain$left) / (hover$domain$right - hover$domain$left)
  top_pct <- (hover$domain$top - hover$y) / (hover$domain$top - hover$domain$bottom)
  left_px <- hover$range$left + left_pct * (hover$range$right - hover$range$left)
  top_px <- hover$range$top + top_pct * (hover$range$bottom - hover$range$top)

  style <- paste0("position:absolute; z-index:100; background-color: rgba(245, 245, 245, 0.85);",
                    "left:", left_px + 30, "px; top:", top_px + 2, "px;")
  wellPanel(
    style = style,
    p(HTML(paste0("<b> nr: </b>", rownames(point), "<br/>",
                  "<b> Wtot: </b>", point$Wtot, "<br/>"))))
})
}
```

```{r}
# plot 1 with tooltip
Wplot(df,1)
tooltip(df,1)
```

Solution

  • I think you can use hover <- eval(parse(text = paste0("input$plot_hover", i))).

    Meaning you parse the character and then evaluate it as if you were running the command.

    Try this again. Your example is odd to test since all the plots are the same, and originally you only plotted the first plot but you can't test other plots with that. I plotted the first two plots side-by-side, but didn't bother to make both plots different - I'm sure you could do that.

    ---
    title: "Test"
    output: 
      flexdashboard::flex_dashboard:
      orientation: columns
    vertical_layout: fill
    runtime: shiny
    ---
    
    ```{r setup, include=FALSE}
    library(flexdashboard)
    library(shiny)
    library(ggplot2)
    
    # data
    Wtot <- c(10,65,139,87)
    plant <- c(15,15,30,30)
    row <- c(10,20,10,20) 
    df <- data.frame(Wtot,plant,row)
    
    # hover options x8
    HO <- list(NULL)
    for (i in 1:8){
      HO[[i]] <- hoverOpts(id = paste("plot_hover",i,sep=""), delay = 300,
                           delayType = c("debounce", "throttle"), clip = TRUE, nullOutside = TRUE)
    }
    
    # plot function
    Wplot <- function(dat,i){
      renderPlot({
        ggplot(data=dat, aes(row, plant, colour=Wtot)) +
          xlim(0,40) +
          ylim(0,50) +
          coord_equal() +
          geom_point(aes(fill=Wtot, size=Wtot), colour="black", pch=22) +
          scale_fill_gradient2(low="green", mid="yellow", high="red", na.value="grey",
                               limits=c(0,300), midpoint=150, breaks=c(0,75,150,225,300)) +
          scale_size_continuous(range=c(3,6), limits=c(0,300), breaks=c(0,75,150,225,300)) +
          guides(fill=guide_legend(), size = guide_legend())
      }, outputArgs = list(hover = HO[[i]])) # <-- i works
    }
    
    # tooltip function
    tooltip <- function(dat,i){
      renderUI({
        hover <- eval(parse(text = paste0("input$plot_hover", i))) # <-- problem with id, "1" should be "i"
        # hover <- input$plot_hover1
        point <- nearPoints(dat, hover)
        if (nrow(point) == 0) return(NULL)
    
        left_pct <- (hover$x - hover$domain$left) / (hover$domain$right - hover$domain$left)
        top_pct <- (hover$domain$top - hover$y) / (hover$domain$top - hover$domain$bottom)
        left_px <- hover$range$left + left_pct * (hover$range$right - hover$range$left)
        top_px <- hover$range$top + top_pct * (hover$range$bottom - hover$range$top)
    
        style <- paste0("position:absolute; z-index:100; background-color: rgba(245, 245, 245, 0.85);",
                        "left:", left_px + 30, "px; top:", top_px + 2, "px;")
        wellPanel(
          style = style,
          p(HTML(paste0("<b> nr: </b>", rownames(point), "<br/>",
                        "<b> Wtot: </b>", point$Wtot, "<br/>"))))
      })
    }
    ```
    
    ##
    
    ### Plot 1
    
    ```{r}
    # plot 1 with tooltip
    Wplot(df,1)
    tooltip(df,1)
    ```
    
    ##
    
    ### Plot 2
    
    ```{r}
    # plot 1 with tooltip
    Wplot(df,2)
    tooltip(df,2)
    ```