You have clicked on heatmap @{df$heatmap}, row @{df$row_index}, column @{df$column_index}
")) } }) } suppressPackageStartupMessages(library(kableExtra)) brush_action = function(df, output) { row_index = unique(unlist(df$row_index)) column_index = unique(unlist(df$column_index)) output[["info"]] = renderUI({ if(!is.null(df)) { HTML(kable_styling(kbl(m[row_index, column_index, drop = FALSE], digits = 2, format = "html"), full_width = FALSE, position = "left")) } }) } server = function(input, output, session) { makeInteractiveComplexHeatmap(input, output, session, ht, click_action = click_action, brush_action = brush_action) } shinyApp(ui, server) ``` The second example gives another scenario where the output needs to be self-defined. In this example, an gene expression matrix is visualized and clicking on the heatmap will print the corresponding gene and some other annotations related to this gene (e.g. the corresponding gene symbol, RefSeq IDs and UniProt IDs). Run `htShinyExample(5.3)` to see how this is implemented. `htShinyExample(5.4)` gives an example where the heatmap visualizes correlations of a list of Gene Ontology terms (The plot is generated by [the **simplifyEnrichment** package](https://bioconductor.org/packages/simplifyEnrichment/)). In this example, the click and brush actions are self-defined so that the selected GO IDs as well as their detailed descriptions are printed. `htShinyExample(5.5)` visualizes an correlation heatmap where clicking on the cell generates a scatter plot of the two corresponding variables. In this example, I set `response = "click"` in `InteractiveComplexHeatmapOutput()`, so that the sub-heatmap is removed from the app and the scatterplot (the output) is directly placed on the right of the original correlation heatmap. `htShinyExample(5.6)` visualizes an a heatmap of pairwise Jaccard coefficients for multiple lists of genomic regions. Clicking on the heatmap cell draws a Hilbert curve (draw by [the **HilbertCurve** package](https://bioconductor.org/packages/HilbertCurve/)) which shows how the two corresponding sets of genomic regions overlap. Instead of occupying static space, the output component can be floated to the mouse positions by setting `output_ui_float = TRUE` in `InteractiveComplexHeatmapOutput()` so that clicking, hovering or brushing from the heatmap opens a frame that contains the output. There are two examples: `htShinyExample(9.1)` and `htShinyExample(9.2)`. The demonstration is as follows: The self-defined output can also be floated if the self-defined UI replaces the default UI by setting `InteractiveComplexHeatmapOutput(..., output_ui = new_output_ui)`: ## Compact mode In `InteractiveComplexHeatmapOutput()`, argument `compact` can be set to `TRUE`, so there is only the original heatmap and the output is floating at the mouse positions if hovering/clicking on heatmap. The calling ```{r, eval = FALSE} InteractiveComplexHeatmapOutput(..., compact = TRUE) ``` is actually identical to ```{r, eval = FALSE} InteractiveComplexHeatmap(..., response = c(action, "brush-output"), output_ui_float = TRUE) ``` Self-defined output can still be used here, e.g. ```{r, eval = FALSE} new_output_ui = ... InteractiveComplexHeatmap(..., compact = TRUE, output_ui = new_output_ui) ``` See examples with `htShinyExample(1.11)`. ## Dynamically generate interactive heatmap widget In previous examples, the heatmaps are already generated before making the interactive app. There are also scenarios where the heatmaps are generated on the fly, e.g. when the matrix is dynamically generated in the middle of an analysis. There might be following scenarios: - The heatmap is based on a subset of matrix which is filtered by users, _e.g._, the expression matrix for differentially expressed genes filtered by different cutoffs. - The annotations are dynamically provided by users. - The heatmap parameters are changed by users, _e.g._, the clustering method or the splitting variable. - If there are multiple heatmaps, which heatmaps are going to be drawn is dynamically selected. In **InteractiveComplexHeatmap**, there are three ways to dynamically generate the interactive heatmap widgets which I will explain one by one. ### Directly use `makeInteractiveComplexHeatmap()` I first demonstrate use of `makeInteractiveComplexHeatmap()`. In the following example, the matrix is reordered by a user-selected column: ```{r, eval = FALSE} ui = fluidPage( sliderInput("column", label = "Which column to order?", value = 1, min = 1, max = 10), InteractiveComplexHeatmapOutput() ) server = function(input, output, session) { m = matrix(rnorm(100), 10) rownames(m) = 1:10 colnames(m) = 1:10 observeEvent(input$column, { order = order(m[, input$column]) ht = Heatmap(m[order, , drop = FALSE], cluster_rows = FALSE, cluster_columns = FALSE) makeInteractiveComplexHeatmap(input, output, session, ht) }) } shiny::shinyApp(ui, server) ``` A similar but slightly complex example is as follows. It can be run by `htShinyExample(6.2)`. The use is very natural. `makeInteractiveComplexHeatmap()` is put inside an `observeEvent()` or an `observe()` so that every time `input$column` changes, it triggers an update of the interactive heatmap widgets. In the following code block defined in `server` function: ```{r, eval = FALSE} ... observeEvent(input$column, { order = order(m[, input$column]) ht = Heatmap(m[order, , drop = FALSE], cluster_rows = FALSE, cluster_columns = FALSE) makeInteractiveComplexHeatmap(input, output, session, ht) }) ... ``` `makeInteractiveComplexHeatmap()` internally creates a list of responses by `observeEvent()`. Every time when `input$column` triggers the update of `makeInteractiveComplexHeatmap()`, all the calls of `observeEvent()` will be re-executed. Re-executing `observeEvent()` only adds the observations to the current observation list while not overwrites them, thus, repeatedly executing `makeInteractiveComplexHeatmap()` will make a same observatin running multiple times. To solve this issue, `makeInteractiveComplexHeatmap()` saves all the observations returned by `observeEvent()` and it tries to first destroies all the avaiable observations that have been created. However, if user-defined reponses via `click_action` and `brush_action` use `observe()` or `observeEvent()`, they must manually recorded so that they can also be destroied when updating `makeInteractiveComplexHeatmap()`. See the following example: ```{r, eval = FALSE} click_action = function(df, input, output, session) { obs = observeEvent(input$foo, { ... }) record_observation(obs) } ``` ### Use `InteractiveComplexHeatmapModal()` and `InteractiveComplexHeatmapWidget()` In the first example, the interactive heatmap is already generated when the Shiny app is loaded. There is a second scenario where the complete interactive heatmap widget is dynamically generated and inserted into the HTML document. There are two other functions `InteractiveComplexHeatmapModal()` and `InteractiveComplexHeatmapWidget()` which have very similar behaviors. These two functions are normally put inside e.g. `shiny::observeEvent()` or `shiny::observe()` and they generate UI as well as render the interactive heatmaps. First I will introduce the usage of `InteractiveComplexHeatmapModal()`. In the following example, there is only an action button in the UI, and in the server function, `InteractiveComplexHeatmapModal()` is called when receiving an `input$show_heatmap` signal. This example can also be run by `htShinyExample(6.3)`. ```{r, eval = FALSE} ui = fluidPage( actionButton("show_heatmap", "Generate_heatmap"), ) server = function(input, output, session) { m = matrix(rnorm(100), 10) ht = Heatmap(m) observeEvent(input$show_heatmap, { InteractiveComplexHeatmapModal(input, output, session, ht) }) } shiny::shinyApp(ui, server) ``` As shown in the following figure, `InteractiveComplexHeatmapModal()` will open an "modal frame" which includes the interactive heatmap. In the next example which is also available in `htShinyExample(6.4)`, a different heatmap is generated according to user's selection. ```{r, eval = FALSE} ui = fluidPage( radioButtons("select", "Select", c("Numeric" = 1, "Character" = 2)), actionButton("show_heatmap", "Generate_heatmap"), ) get_heatmap_fun = function(i) { mat_list = list( matrix(rnorm(100), 10), matrix(sample(letters[1:10], 100, replace = TRUE), 10) ) Heatmap(mat_list[[i]]) } server = function(input, output, session) { observeEvent(input$show_heatmap, { i = as.numeric(input$select) InteractiveComplexHeatmapModal(input, output, session, get_heatmap = get_heatmap_fun(i)) }) } shiny::shinyApp(ui, server) ``` The usage of `InteractiveComplexHeatmapWidget()` is very similar as `InteractiveComplexHeatmapModal()`, except that now for `InteractiveComplexHeatmapWidget()`, user needs to allocate a place defined by `shiny::htmlOutput()` in UI, and later the interactive heatmap widget is put there. I modify the previous example with `InteractiveComplexHeatmapWidget()`. Now in the UI, I add one line where I specify `htmlOutput()` with ID `"heatmap_output"`, and this ID is set in `InteractiveComplexHeatmapWidget()` correspondingly. ```{r, eval = FALSE} ui = fluidPage( actionButton("show_heatmap", "Generate_heatmap"), htmlOutput("heatmap_output") ) server = function(input, output, session) { m = matrix(rnorm(100), 10) ht = Heatmap(m) observeEvent(input$show_heatmap, { InteractiveComplexHeatmapWidget(input, output, session, ht, output_id = "heatmap_output") }) } shiny::shinyApp(ui, server) ``` The app looks like follows: `InteractiveComplexHeatmapModal()` and `InteractiveComplexHeatmapWidget()` all accept an argument `js_code` where customized JavaScript code can be inserted after the interactive UI. This is sometimes useful. In previous example where the heatmap widget is triggered by clicking on the action button, every time when clicking on the button, the widget is regenerated although the heatmaps are actually the same. Actually we can change the behavior of the button that from the second click it just switches the visibility of the heatmap widget. See more examples in `htShinyExample(6.5)` and `htShinyExample(6.7)`. ```{r, eval = FALSE} ui = fluidPage( actionButton("show_heatmap", "Generate_heatmap"), htmlOutput("heatmap_output") ) server = function(input, output, session) { m = matrix(rnorm(100), 10) ht = Heatmap(m) observeEvent(input$show_heatmap1, { InteractiveComplexHeatmapWidget(input, output, session, ht, output_id = "heatmap_output", close_button = FALSE, js_code = " $('#show_heatmap').click(function() { $('#heatmap_output').toggle('slow'); }).text('Show/hide heatmap'). attr('id', 'show_heatmap_toggle'); " ) }) } shiny::shinyApp(ui, server) ``` ## Implement interactivity from scratch **InteractiveComplexHeatmap** provides rich tools for interactively working with heatmaps. However, some people might want to develop their own tools while they only need the information of which cells are selected. Next I demonstrate it with a simple example. The following example is runnable. ```{r, eval = FALSE} ui = fluidPage( actionButton("action", "Generate heatmap"), plotOutput("heatmap", width = 500, height = 500, click = "heatmap_click", brush = "heatmap_brush"), verbatimTextOutput("output") ) server = function(input, output, session) { ht_obj = reactiveVal(NULL) ht_pos_obj = reactiveVal(NULL) observeEvent(input$action, { m = matrix(rnorm(100), 10) rownames(m) = 1:10 colnames(m) = 1:10 output$heatmap = renderPlot({ ht = draw(Heatmap(m)) ht_pos = htPositionsOnDevice(ht) ht_obj(ht) ht_pos_obj(ht_pos) }) }) observeEvent(input$heatmap_click, { pos = getPositionFromClick(input$heatmap_click) selection = selectPosition(ht_obj(), pos, mark = FALSE, ht_pos = ht_pos_obj(), verbose = FALSE) output$output = renderPrint({ print(selection) }) }) observeEvent(input$heatmap_brush, { lt = getPositionFromBrush(input$heatmap_brush) selection = selectArea(ht_obj(), lt[[1]], lt[[2]], mark = FALSE, ht_pos = ht_pos_obj(), verbose = FALSE) output$output = renderPrint({ print(selection) }) }) } shinyApp(ui, server) ``` In this simple Shiny app, a click or a brush on heatmap prints the corresponding data frame that contains the information of the selected cells. There are several points that need to be noticed: 1. `draw()` and `htPositionsOnDevice()` need to be put inside the `renderPlot()`. 2. To get the position of the click on heatmap, `getPositionFromClick()` should be used. With knowing the position of the click, it can be sent to `selectPosition()` to correspond to the original matrix. 3. Similarly, to get the positions of the area that was brushed on heatmap, `getPositionFromBrush()` should be used. This method also works for complex heatmaps, e.g. with row or column splitting, or with multiple heatmaps.