--- title: "How to visualize complex heatmaps interactively" author: "Zuguang Gu ( z.gu@dkfz.de )" date: "`r Sys.Date()`" output: rmarkdown::html_vignette: width: 8 fig_width: 5 toc: true vignette: > %\VignetteIndexEntry{1. How to visualize heatmaps interactively} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, echo = FALSE} library(knitr) knitr::opts_chunk$set( error = FALSE, tidy = FALSE, message = FALSE, warning = FALSE, fig.align = "center" ) ``` ```{r, echo = FALSE} suppressPackageStartupMessages(library(InteractiveComplexHeatmap)) ``` ## Install Package **InteractiveComplexHeatmap** is available on [Bioconductor](https://bioconductor.org/packages/InteractiveComplexHeatmap/), you can install it by: ```r if (!requireNamespace("BiocManager", quietly=TRUE)) install.packages("BiocManager") BiocManager::install("InteractiveComplexHeatmap") ``` If you want the latest version, install it directly from GitHub: ```r library(devtools) install_github("jokergoo/InteractiveComplexHeatmap") ``` A printer-friendly version of the documentation is available at [bioRxiv](https://doi.org/10.1101/2021.03.08.434289). ## Usage The **InteractiveComplexHeatmap** package is very straightforward to use. For any heatmap (or list of heatmaps as a `Heatmap` or `HeatmapList` object) produced from **ComplexHeatmap** package, you just use the function `htShiny()` to export it as a Shiny app. You can copy and paste the following code: ```{r, eval = FALSE} library(ComplexHeatmap) library(InteractiveComplexHeatmap) m = matrix(rnorm(100*100), nrow = 100) ht = Heatmap(m) ht = draw(ht) # not necessary, but recommended htShiny(ht) ``` A link will be automatically opend in your web browser (or a pop-up window in RStudio IDE). To use `htShiny()`, the heatmap object is recommended to be updated with the function `draw()`. If it has not been updated, it will be applied inside `htShiny()` automatically. Updating by `draw()` speeds up loading the Shiny application because `draw()` applies clusterings which is normally the most time-consuming step in heatmap generation. After `draw()` is executed, clustering results are saved in the returned heatmap object so that repeatedly drawing heatmap can directly use the saved clustering results. If the heatmap includes randomness, such as _k_-means clustering by setting `row_km` or `column_km` argument or using random colors for annotations, it is necessary to execute `draw()` before sending the heatmap object to `htShiny()` to get rid of obtaining different heatmaps when executing `htShiny()` multiple times. Following screenshot demonstrates the Shiny app on rather complex heatmaps. The data is from [here](http://jokergoo.github.io/supplementary/ComplexHeatmap-supplementary1-4/supplS2_scRNASeq/supplS2_scRNAseq.html) and the app can be generated by running `htShinyExample(4.2)`. In this Shiny app, users can click on the orignal heatmap or select an area from it. The information of the cell or the area selected by users can be found in the text below the heatmaps. If an area is selected, a sub-heatmap is drawn on the right side of the app. Both heatmaps can be resized by dragging from the bottom right. There are several tools under both heatmaps. For the original heatmap, there are following tools: 1. *Search heatmap* It allows to search the heatmap labels to obtain a subset of rows and columns. Once rows or columns are found in the heatmaps, the sub-heatmaps will be drawn in the right panel. 2. *Configure brush* Border and backgroud of the brush can be configured here: 3. *Save image* The main heatmap can be saved into a file with one of the three formats (`png`, `pdf` and `svg`). 4. *Resize image* The size of the original heatmap can be controlled by dragging the box, however, it can be precisely controlled by entering the width and height, as shown in the following figure: For the sub-heatmap, there are following tools: 1. *Configure sub-heatmap* There are three sections of controls. 1. Basic controls such as whether to show row or column names, 2. Brushing on the original heatmap might not precisely capture rows or columns users expected. You can manually remove a certain number of rows or columns from the four dimensions of the selected sub-heatmap. 3. Selected sub-heatmap can be further converted into a second interactive heatmap application. 2. *Export table* The values in the sub-heatmaps can be viewed and exported as a text table: 3. *Save sub-heatmap as an image* Similar as in the main heatmap. 4. *Resize sub-heatmap* Similar as in the main heatmap. There are other vignettes focusing on more specific topics: - [How interactive ComplexHeatmap is implemented](implementation.html) - [Functions for Shiny app development](shiny_dev.html) - [Decorations on the heatmaps](decoration.html) - [Interactivate heatmaps indirectly generated by pheatmap(), heatmap.2() and heatmap()](interactivate_indirect.html) ## Use last generated heatmaps **ComplexHeatmap** is broadly used in many other scripts and packages where they do not directly return the `Heatmap`/`HeatmapList` object. This is of no problem to make these heatmaps interactive because the last generated heatmap object is automatically saved and calling `htShiny()` without the heatmap object will automatically use the last one (by internally using the function `ComplexHeatmap:::get_last_ht()`). I demonstrate this functionality with [the **cola** pacakge](https://bioconductor.org/packages/cola/). **cola** package heavily uses **ComplexHeatmap** to implement various customized heatmaps to visualize consensus clustering results as well as downstream analysis. As an example, the function `get_signatures()` extracts signatures that are significantly different between the predicted subgroups. `get_signatures()` makes one heatmap and returns a data frame of the signatures as well as various statistics for the test. When `get_signatures()` draws the heatmap, the heatmap object is saved internally, then directly calling `htShiny()` without the heatmap object will convert the static signature heatmap into an interactive one: ```{r, eval = FALSE} # the following code is runable library(cola) # cola is from Bioconductor data(golub_cola) get_signatures(golub_cola["ATC:skmeans"], k = 2) # this makes the heatmap htShiny() ``` Note the functionality of automatically saving the last heatmap is only turned on when **InteractiveComplexHeatmap** packags is loaded, which means, `library(InteractiveComplexHeatmap)` should be called before making the heatmap, or you need to explicitly set `ComplexHeatmap::ht_opt(save_last = TRUE)`. Examples are in `htShinyExample(1.6)` and `htShinyExample(1.7)`. Nevertheless, if possible, I still suggest to explictly put the heatmap object in `htShiny()` which makes it clear which heatmap is becoming interactive. ## Recursive interactive heatmap widgets In the right panel there is the sub-heatmap that is selected from the left panel. When the original heatmap is very huge, even a tiny selection rectangle still generates a dense sub-heatmap where single cells are very hard to identify and read. In this case, the sub-heatmap can be continually exported as another independent interactive heatmap widget which is in a new layer above current one, just by clicking the button "Interactivate sub-heatmap" under the sub-heatmap. This process can be recursively applied untill you are satisfied with the details you can see in the sub-heatmap. An example is as follows: ## Float output to mouse positions The output which contains information of the clicked cell or the sub-heatmap by default is placed below the heatmaps. Argument `output_ui_float` can be set to `TRUE` so that the output is floating to the positions of mouse. ```{r, eval = FALSE} htShiny(ht, output_ui_float = TRUE) ``` There are two examples: `htShinyExample(9.1)` and `htShinyExample(9.2)`. The demonstration is as follows: As will be explained in [the vignette "Functions for Shiny app development"](shiny_dev.html), the output can be self-defined to put customized information. The self-defined output can also be floated: ## Layout There are three main components in the interactive heatmap UI, _i.e._, the orignal heatmap, the sub-heatmap and an output that shows information of the clicked cell or the selected sub-heatmap. The layout of the three components are controlled via `layout` argument, see [the vignette "Functions for Shiny app development"](shiny_dev.html#customize-the-widgets) for detailed explanation of the `layout` argument. ## Only respond to one event The argument `response` can be set as one of `"click"`, `"hover"`, `"dblclick"`, `"brush"` and `"brush-output"` to only respond to one event on heatmap. E.g. if `response` is set to `"click"`, there will be no response for the "brush event" in the interactive heatmap, also the sub-heatmap component is removed from the app. Brushing on heatmap by default triggers two reponses: update in the sub-heatmap and update in the output. If `response` is set to `"brush-output"`, brushing will only trigger changes in the output and there will be no sub-heatmap component in the app. The following figure demonstrates only responding to `"click"` or `"brush"` where the output floats at mouse positions. Runnable examples are in `htShinyExample(1.9)` and `htShinyExample(9.3)`. ## Compact mode By default all the three UI components are includedin the app. In `htShiny()`, the argument `compact` can be set to `TRUE` so that the sub-heatmap component is removed and the output floats at mouse positions: ```{r, eval = FALSE} htShiny(ht, compact = TRUE) ``` ## Other arguments in `htShiny()` `htShiny()` is just a simple wrapper on the other two basic functions `InteractiveComplexHeatmapOutput()` and `makeInteractiveComplexHeatmap()`. Users can go to the vignette ["Functions for Shiny app development"](shiny_dev.html) for more customized controls. ## Live examples The following code demostrates two horizontally concatenated heatmaps. Please visit https://jokergoo.shinyapps.io/interactive_complexheatmap/ for a live demo. ```{r, eval = FALSE} set.seed(123) mat1 = matrix(rnorm(100), 10) rownames(mat1) = colnames(mat1) = paste0("a", 1:10) mat2 = matrix(sample(letters[1:10], 100, replace = TRUE), 10) rownames(mat2) = colnames(mat2) = paste0("b", 1:10) ht_list = Heatmap(mat1, name = "mat_a", row_km = 2, column_km = 2) + Heatmap(mat2, name = "mat_b") htShiny(ht_list) ``` The following code demostrates two vertically concatenated heatmaps. Check https://jokergoo.shinyapps.io/interactive_complexheatmap_vertical/ for a live demo. ```{r, eval = FALSE} ht_list = Heatmap(mat1, name = "mat_a", row_km = 2, column_km = 2) %v% Heatmap(mat2, name = "mat_b") htShiny(ht_list) ``` ## Integrate with other plots and packages The functionality of the interactivity is general. It can be applied to any heatmap as long as it is generated from the **ComplexHeatmap** package. Thus, it can turn many other plots into an interactive app. ### Interactive density heatmap `ComplexHeatmap::densityHeatmap()` returns a `Heatmap` object, so it can be exported as a Shiny app. Check https://jokergoo.shinyapps.io/interactive_densityheatmap/ for a live demo. The example can also be run locally by executing `htShinyExample(2.1)`. The usage of `htShinyExample()` will be introduced later and in the webpage opened by `htShinyExample()` there is also the source code for generating this Shiny app. ```{r, eval = FALSE} ht = densityHeatmap(...) htShiny(ht) ``` ### Interactive oncoPrint `ComplexHeatmap::oncoPrint()` returns a `Heatmap` object, thus, the oncoPrint can be interactive. Example code is as follows. Check https://jokergoo.shinyapps.io/interactive_oncoprint/ for a live demo. The example can be run locally by executing `htShinyExample(2.2)`. ```{r, eval = FALSE} ht = oncoPrint(...) htShiny(ht) ht = oncoPrint(...) + Heatmap(...) + rowAnnotation(...) htShiny(ht) ``` ### Interactive UpSet plot `ComplexHeatmap::UpSet()` returns a `Heatmap` object, thus, the UpSet plot generated by **ComplexHeatmap** can be interactive. It might be useful when you visualize many sets at the same time. Example code is as follows. Check https://jokergooo.shinyapps.io/interactive_upset/ for a live demo. The example can be run locally by executing `htShinyExample(2.3)`. ```{r, eval = FALSE} cm = make_comb_mat(...) ht = UpSet(cm, ...) htShiny(ht) ``` ### Interactive enriched heatmap [**EnrichedHeatmap**](https://www.bioconductor.org/packages/release/bioc/html/EnrichedHeatmap.html) inherits **ComplexHeatmap** and it outputs `Heatmap` objects, thus, an "enriched heatmap" can be exported as a Shiny app as well. Check https://jokergoo.shinyapps.io/interactive_enrichedheatmap/ for a live demo. The example can be run locally by executing `htShinyExample(3.1)`. ```{r, eval = FALSE} mat = normalizeToMatrix(...) ht = EnrichedHeatmap(mat, ...) htShiny(ht) ht = EnrichedHeatmap(mat, ...) + EnrichedHeatmap(...) + Heatmap(...) htShiny(ht) ``` ### Interactive pheatmap Since **ComplexHeatmap** can [seamlessly integrate **pheatmap**](https://jokergoo.github.io/2020/05/06/translate-from-pheatmap-to-complexheatmap/), this means your pheatmap can be interactive! Check https://jokergooo.shinyapps.io/interactive_pheatmap/. The example can be run locally by executing `htShinyExample(2.4)`. `ComplexHeatmap::pheatmap()` and `pheatmap::pheatmap()` have the same set of arguments and generate almost the same heatmaps, but be careful when you directly use `pheatmap()` without the namespace prefix (the package name), you need to make sure it is from **ComplexHeatmap** if you want to use the interactive functionality. ```{r, eval = FALSE} ht = pheatmap(...) # ComplexHeatmap::pheatmap should overwrite pheatmap::pheatmap htShiny(ht) ``` ### Interactive heatmap To facilitate the users who are still using `heatmap()` and `heatmap.2()` functions, to make the output of these two functions can be exported as interactive Shiny apps as well, from **ComplexHeatmap** version 2.7.2, two similar translation functions `ComplexHeatmap:::heatmap()` and `ComplexHeatmap:::heatmap.2()` which use the same set of arguments as the original functions and generate almost identical heatmaps. Then an interactive `heatmap()` will look like: ```{r, eval = FALSE} ht = ComplexHeatmap:::heatmap(...) htShiny(ht) ``` As you see, `heatmap()` function is not exported from **ComplexHeatmap** (so that it will not conflict with **stats** package) and it should be explicitly specifid with `:::`. An live example is at https://jokergooo.shinyapps.io/interactive_heatmap/. The example can be run locally by executing `htShinyExample(2.5)`. ### Interactive heatmap.2 Similarly, an interactive `heatmap.2()` looks like: ```{r, eval = FALSE} ht = ComplexHeatmap:::heatmap.2(...) htShiny(ht) ``` Similarlly, `heatmap.2()` is not exported from **ComplexHeatmap** (so that it will not conflict with **glots** package) and it should be specified with `:::`. An live example is at https://jokergooo.shinyapps.io/interactive_heatmap_2/. The example can be run locally by executing `htShinyExample(2.6)`. ### Interactive tidyHeatmap [The **tidyHeatmap** package](https://CRAN.R-project.org/package=tidyHeatmap) basically wraps **ComplexHeatmap** and provides "a tidy way" for generating heatmaps. Since the final heatmap is actually generated by **ComplexHeatmap**, it can be directly exported as an interactive app. You can directly apply `htShiny()` on the object generated from **tidyHeatmap**. Check https://jokergooo.shinyapps.io/interactive_tidyheatmap/. The example can be run locally by executing `htShinyExample(2.7)`. ```{r, eval = FALSE} library(tidyverse) library(tidyHeatmap) mtcars_tidy <- mtcars %>% as_tibble(rownames="Car name") %>% mutate_at(vars(-`Car name`, -hp, -vs), scale) %>% pivot_longer(cols = -c(`Car name`, hp, vs), names_to = "Property", values_to = "Value") mtcars_heatmap <- mtcars_tidy %>% heatmap(`Car name`, Property, Value ) %>% add_tile(hp) htShiny(mtcars_heatmap) ``` ### Other tips As explained before, for all these functions and packages that generate customized heatmaps, if the heatmaps are already generated in the interactive graphics device, the heatmap objects can be ommited when calling `htShiny()`. E.g.: ```{r, eval = FALSE} ComplexHeatmap::pheatmap(...) htShiny() oncoPrint(...) htShiny() ``` ## Examples shipped with the package There are many other examples shipped with the **InteractiveComplexHeatmap** package. The list of examples can be obtained by executing `htShinyExample()` with no argument. ```{r} htShinyExample() ``` You can select one specific example by sending the corresponding index to `htShinyExample()`, e.g. to run the example "1.4 A single heatmap where rows and columns are split.", simply run: ```{r, eval = FALSE} htShinyExample(1.4) ``` Then the interactive heatmaps as well as the source code for generating the app will be available in the webpage. Have fun! ## SessionInfo ```{r} sessionInfo() ```