---
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()
```