jokergoo / InteractiveComplexHeatmap

Make Interactive Complex Heatmaps
https://jokergoo.github.io/InteractiveComplexHeatmap/
Other
126 stars 23 forks source link

Not including HeatmapInfoOutput when separating UI components leads to unexpected behavior #94

Closed PhilippJunk closed 2 years ago

PhilippJunk commented 2 years ago

Dear @jokergoo

Thanks for creating such a great tool!

I have been playing around a little bit with InteractiveComplexHeatmap with the aim of building a dashboard. For my application, I am interested in only showing the main heatmap and the sub-heatmap, but not the output component. Trying to specify the UI components separately and including only originalHeatmapOutput and subHeatmapOutput in a shiny app has lead to some unexpected behavior. Here is a number of minimal examples (tested on Ubuntu 18.04 and Windows 10 with R 4.2):

Only specify originalHeatmapOutput and subHeatmapOutput: not working

library(shiny)
library(ComplexHeatmap)
library(InteractiveComplexHeatmap)

# create heatmap (taken from demo)
set.seed(123)
m = matrix(rnorm(100*100), 100)
rownames(m) = paste0("R", 1:100)
colnames(m) = paste0("C", 1:100)
ht1 = Heatmap(m,
              top_annotation = HeatmapAnnotation(foo1 = runif(100)),
              left_annotation = rowAnnotation(bar1 = anno_points(1:100)),
              show_row_names = FALSE, show_column_names = FALSE)

m = matrix(rnorm(100*100), 100)
rownames(m) = paste0("R", 1:100)
colnames(m) = paste0("C", 1:100)
ht2 = Heatmap(m,
              bottom_annotation = HeatmapAnnotation(foo2 = runif(100)),
              right_annotation = rowAnnotation(bar2 = anno_points(1:100)),
              show_row_names = FALSE, show_column_names = FALSE)

ht_main <- ht1 + ht2

# UI
ui <- fluidPage(

  # Application title
  titlePanel("Test"),

  fluidRow("Original heatmap", originalHeatmapOutput("ht", title = NULL)),

  fluidRow("Sub-heatmap", subHeatmapOutput("ht", title = NULL)),

  hr()

)

# server
server <- function(input, output, session) {
  makeInteractiveComplexHeatmap(input, output, session, ht_main, "ht")
}

# Run the application 
shinyApp(ui = ui, server = server)

Running this in a fresh R session (important, I will touch upon this later) will not produce a working shiny app, but instead leads to the following output:

Listening on http://127.0.0.1:7455
[2022-06-22 12:16:10] The heatmap list is suggested to be udpated by e.g. `ht_list = draw(ht_list)` before sending to the Shiny app.
Warning: Error in ||: invalid 'x' type in 'x || y'
  1: runApp
Warning: Detect there is already an off-screen device opened: 'png', please close it by dev.off() and reopen the
application.
Warning: Error in ||: invalid 'x' type in 'x || y'
  168: renderPlot
  166: func
  126: drawPlot
  112: <reactive:plotObj>
   96: drawReactive
   83: renderFunc
   82: output$ht_heatmap
    1: runApp
[2022-06-22 12:16:13] no area on the heatmap is selected, Do not make the sub-heatmap.

Specify all three UI components: working

The identical app, with all three UI components specified, is working as intended:

library(shiny)
library(ComplexHeatmap)
library(InteractiveComplexHeatmap)

# create heatmap (taken from demo)
set.seed(123)
m = matrix(rnorm(100*100), 100)
rownames(m) = paste0("R", 1:100)
colnames(m) = paste0("C", 1:100)
ht1 = Heatmap(m,
              top_annotation = HeatmapAnnotation(foo1 = runif(100)),
              left_annotation = rowAnnotation(bar1 = anno_points(1:100)),
              show_row_names = FALSE, show_column_names = FALSE)

m = matrix(rnorm(100*100), 100)
rownames(m) = paste0("R", 1:100)
colnames(m) = paste0("C", 1:100)
ht2 = Heatmap(m,
              bottom_annotation = HeatmapAnnotation(foo2 = runif(100)),
              right_annotation = rowAnnotation(bar2 = anno_points(1:100)),
              show_row_names = FALSE, show_column_names = FALSE)

ht_main <- ht1 + ht2

# UI
ui <- fluidPage(

  # Application title
  titlePanel("Test"),

  fluidRow("Original heatmap", originalHeatmapOutput("ht", title = NULL)),

  fluidRow("Sub-heatmap", subHeatmapOutput("ht", title = NULL)),

  fluidRow('OutputPanel', HeatmapInfoOutput('ht', title=NULL)),

  hr()

)

# server
server <- function(input, output, session) {
  makeInteractiveComplexHeatmap(input, output, session, ht_main, "ht")
}

# Run the application 
shinyApp(ui = ui, server = server)

Even more interestingly, if in the same R session, I first run app2 and then app1, app1 is working perfectly fine (which has lead to quite some confusion from my side, it is always interesting to have code which is working and then suddenly doesn't :D ).

If this behavior is intended, it would be nice to have that explicitly mentioned in the documentation, where to the best of my knowledge it is not mentioned that all three UI components have to be present at all time for the package to work. Additionally, a more descriptive error message if possible would be very helpful.

I have tried some workarounds, with mixed results, maybe this is of interest to someone who will have a similar aim:

Workaround 1: hiding HeatmapInfoOutput within a shiny::conditionalPanel: not working

This creates a working shiny app, and the main heatmap is plotted, however all click/brush events on the main heatmap appear to be broken and this renders the subheatmap useless.

library(shiny)
library(ComplexHeatmap)
library(InteractiveComplexHeatmap)

# create heatmap (taken from demo)
set.seed(123)
m = matrix(rnorm(100*100), 100)
rownames(m) = paste0("R", 1:100)
colnames(m) = paste0("C", 1:100)
ht1 = Heatmap(m,
              top_annotation = HeatmapAnnotation(foo1 = runif(100)),
              left_annotation = rowAnnotation(bar1 = anno_points(1:100)),
              show_row_names = FALSE, show_column_names = FALSE)

m = matrix(rnorm(100*100), 100)
rownames(m) = paste0("R", 1:100)
colnames(m) = paste0("C", 1:100)
ht2 = Heatmap(m,
              bottom_annotation = HeatmapAnnotation(foo2 = runif(100)),
              right_annotation = rowAnnotation(bar2 = anno_points(1:100)),
              show_row_names = FALSE, show_column_names = FALSE)

ht_main <- ht1 + ht2

# UI
ui <- fluidPage(

  # Application title
  titlePanel("Test"),

  fluidRow("Original heatmap", originalHeatmapOutput("ht", title = NULL)),

  fluidRow("Sub-heatmap", subHeatmapOutput("ht", title = NULL)),

  conditionalPanel(F, HeatmapInfoOutput('ht', title=NULL)),

  hr()

)

# server
server <- function(input, output, session) {
  makeInteractiveComplexHeatmap(input, output, session, ht_main, "ht")
}

# Run the application 
shinyApp(ui = ui, server = server)

Workaround 2: hiding HeatmapInfoOutput with JavaScript: working

This seems to be working fine, I suspect because the HTML element actually exists and can be modified, but is not displayed.

library(shiny)
library(shinyjs)
library(ComplexHeatmap)
library(InteractiveComplexHeatmap)

# create heatmap (taken from demo)
set.seed(123)
m = matrix(rnorm(100*100), 100)
rownames(m) = paste0("R", 1:100)
colnames(m) = paste0("C", 1:100)
ht1 = Heatmap(m,
              top_annotation = HeatmapAnnotation(foo1 = runif(100)),
              left_annotation = rowAnnotation(bar1 = anno_points(1:100)),
              show_row_names = FALSE, show_column_names = FALSE)

m = matrix(rnorm(100*100), 100)
rownames(m) = paste0("R", 1:100)
colnames(m) = paste0("C", 1:100)
ht2 = Heatmap(m,
              bottom_annotation = HeatmapAnnotation(foo2 = runif(100)),
              right_annotation = rowAnnotation(bar2 = anno_points(1:100)),
              show_row_names = FALSE, show_column_names = FALSE)

ht_main <- ht1 + ht2

# UI
ui <- fluidPage(
  shinyjs::useShinyjs(),

  # Application title
  titlePanel("Test"),

  fluidRow("Original heatmap", originalHeatmapOutput("ht", title = NULL)),

  fluidRow("Sub-heatmap", subHeatmapOutput("ht", title = NULL)),

  shinyjs::hidden(fluidRow('OutputPanel', HeatmapInfoOutput('ht', title=NULL))),

  hr()

)

# server
server <- function(input, output, session) {
  makeInteractiveComplexHeatmap(input, output, session, ht_main, "ht")
}

# Run the application 
shinyApp(ui = ui, server = server)

Session Info

R version 4.2.0 (2022-04-22)
Platform: x86_64-pc-linux-gnu (64-bit)
Running under: Ubuntu 18.04.6 LTS

Matrix products: default
BLAS:   /usr/lib/x86_64-linux-gnu/blas/libblas.so.3.7.1
LAPACK: /usr/lib/x86_64-linux-gnu/lapack/liblapack.so.3.7.1

locale:
 [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C               LC_TIME=en_GB.UTF-8        LC_COLLATE=en_US.UTF-8    
 [5] LC_MONETARY=en_GB.UTF-8    LC_MESSAGES=en_US.UTF-8    LC_PAPER=en_GB.UTF-8       LC_NAME=C                 
 [9] LC_ADDRESS=C               LC_TELEPHONE=C             LC_MEASUREMENT=en_GB.UTF-8 LC_IDENTIFICATION=C       

attached base packages:
[1] grid      stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
[1] shinyjs_2.1.0                   InteractiveComplexHeatmap_1.4.0 ComplexHeatmap_2.12.0          
[4] shiny_1.7.1                    

loaded via a namespace (and not attached):
 [1] circlize_0.4.15     shape_1.4.6         clisymbols_1.2.0    GetoptLong_1.0.5    xfun_0.31           colorspace_2.0-3   
 [7] viridisLite_0.4.0   htmltools_0.5.2     stats4_4.2.0        rlang_1.0.2         later_1.3.0         glue_1.6.2         
[13] BiocGenerics_0.42.0 RColorBrewer_1.1-3  matrixStats_0.62.0  foreach_1.5.2       lifecycle_1.0.1     stringr_1.4.0      
[19] munsell_0.5.0       rvest_1.0.2         fontawesome_0.2.2   GlobalOptions_0.1.2 codetools_0.2-18    kableExtra_1.3.4   
[25] evaluate_0.15       knitr_1.39          IRanges_2.30.0      fastmap_1.1.0       doParallel_1.0.17   httpuv_1.6.5       
[31] parallel_4.2.0      Rcpp_1.0.8.3        xtable_1.8-4        promises_1.2.0.1    scales_1.2.0        S4Vectors_0.34.0   
[37] webshot_0.5.3       jsonlite_1.8.0      mime_0.12           systemfonts_1.0.4   rjson_0.2.21        png_0.1-7          
[43] digest_0.6.29       stringi_1.7.6       clue_0.3-61         cli_3.3.0           tools_4.2.0         magrittr_2.0.3     
[49] cluster_2.1.3       crayon_1.5.1        ellipsis_0.3.2      xml2_1.3.3          rmarkdown_2.14      svglite_2.1.0      
[55] httr_1.4.3          rstudioapi_0.13     iterators_1.0.14    R6_2.5.1            compiler_4.2.0     
UCDNJJ commented 2 years ago

I also ran into this unexpected behavior which took awhile to track down as the original poster mentions. It would be nice to exclude the "HeatmapInfoOutput" in the UI without having to use tricks in ShinyJS.

jokergoo commented 2 years ago

@PhilippJunk , thank you very much for your analysis. Yes there is a variable which is only set in subHeatmapOutput(). If subHeatmapOutput() is not used, that variable will be NULL, later validation of this variable generates an error.

Now I have fixed this problem. Thanks again!

PhilippJunk commented 2 years ago

@jokergoo Thanks for the fix and clarification!