3 heatmap layout
The heatmap_layout()
function provides a powerful way to create customizable heatmaps in R using ggplot2
. This chapter will guide you through its usage.
heatmap_layout()
is a specialized version of quad_alignb()
, which itself is a specific variant of QuadLayout
(quad_layout()
) designed to align observations both horizontally and vertically. We introduce heatmap_layout()
directly, as it is more familiar to many users, especially those experienced with popular heatmap packages like pheatmap
and ComplexHeatmap
.
heatmap_layout()
simplifies the creation of heatmap plots by integrating essential elements for a standard heatmap layout, ensuring that the appropriate data mapping and visualization layers are automatically applied. ggheatmap()
is an alias for heatmap_layout()
.
Code
library(ggalign)
set.seed(123)
<- matrix(rnorm(56), nrow = 7)
small_mat rownames(small_mat) <- paste0("row", seq_len(nrow(small_mat)))
colnames(small_mat) <- paste0("column", seq_len(ncol(small_mat)))
3.1 input data
As mentioned in Section 2.1, we typically require a matrix for the Layout
which need align observations. Internally, fortify_matrix()
will be used to process the data. You can provide a numeric or character vector, a data frame, or any other data type that can be converted into a matrix using as.matrix()
.
ggheatmap(small_mat)
#> → heatmap built with `geom_tile()`
3.2 Main plot (heatmap body)
The ggheatmap()
/quad_layout()
functions arrange plots in the Quad-Side layout of the main plot. When the layout is initialized, a ggplot
object is automatically created for the main plot.
For ggheatmap()
, the matrix input will be converted into a long-format data frame when drawing. The data in the underlying ggplot
object includes the following columns:
.xpanel
and.ypanel
: the column and row panel.x
and.y
: thex
andy
coordinates.row_names
and.column_names
: A factor of the row and column names of the original matrix (only applicable when names exist)..row_index
and.column_index
: the row and column index of the original matrix.value
: the actual matrix value.
The default mapping will use aes(.data$.x, .data$.y)
, but can be customized using mapping
argument.
By default, the main plot is regarded as the active plot, meaning you can add ggplot2 elements directly to the main plot.
ggheatmap(small_mat) +
geom_point() +
scale_fill_viridis_c()
#> → heatmap built with `geom_tile()`
By default, ggheatmap()
adds a heatmap layer. If the matrix has more than 20,000 cells (nrow * ncol > 20000
), it uses geom_raster()
for performance efficiency; for smaller matrices, geom_tile()
is used. You can explicitly choose the layer by providing a single string ("raster"
or "tile"
) in the filling
argument.
ggheatmap(small_mat, filling = "raster")
ggheatmap(small_mat, filling = "tile")
Note, the filling layer will always use mapping
of aes(.data$.x, .data$.y)
, if you want to customize filling, you can set filling = NULL
, which will remove the filling layer and allow you to add custom filling geoms.
ggheatmap(small_mat, filling = NULL) +
geom_tile(aes(fill = value), color = "black", width = 0.9, height = 0.9)
A heatmap pie charts can be easily drawn:
set.seed(123)
ggheatmap(matrix(runif(360L), nrow = 20L), filling = NULL) +
geom_pie(aes(angle = value * 360, fill = value))
For more complex customizations of pie charts, you can try using ggforce::geom_arc_bar()
instead.
3.3 rasterization
When working with large heatmaps, it’s often beneficial to rasterize the heatmap body layer. You can achieve this by using the raster_magick()
function. The res
argument controls the resolution of the raster image. By default, the res
argument matches the resolution of the current device, but specifying a different value can help reduce the resolution of the rasterized heatmap body.
ggheatmap(small_mat, filling = NULL) +
raster_magick(geom_tile(aes(fill = value)), res = 50)
By leveraging raster_magick()
, you can also perform image post-processing using the magick
package. This allows for custom image resizing with filters.
ggheatmap(small_mat, filling = NULL) +
# Use `magick::filter_types()` to check available `filter` arguments
raster_magick(geom_raster(aes(fill = value)),
magick = function(image) {
::image_resize(image,
magick# we resize to the 50% of width
geometry = "50%x", filter = "Lanczos"
)
} )
Note: When using magick::image_resize()
, you should specify the geometry
argument to resize the image. If only the filter
is specified, it will only distort the image data (though subtle). For more information on image resizing, refer to ImageMagick’s resize documentation.
You can also rasterize all plots in the layout directly with raster_magick()
. This method is defined for both ggheatmap()
/quad_layout()
and stack_layout()
objects.
Additionally, You can use external packages like ggrastr or ggfx to rasterize the heatmap body.
ggheatmap(small_mat, filling = FALSE) +
::rasterise(geom_tile(aes(fill = value)), dev = "ragg") ggrastr
Likewise, you can also rasterize all plots in the layout directly with ggrastr::rasterise()
for both ggheatmap()
/quad_layout()
and stack_layout()
.
::rasterise(ggheatmap(small_mat), dev = "ragg")
ggrastr#> → heatmap built with `geom_tile()`
Furthermore, ggfx offers many image filters that can be applied to ggplot2 layers. See the package for the details.
3.4 annotations
In ggheatmap()
/quad_layout()
, annotations are handled by a stack_layout()
object and can be positioned at the top, left, bottom, or right of the main plot (heatmap body).
By default, ggheatmap()
/quad_layout()
do not activate an annotation, You can use quad_anno()
to activate an annotation, directing all subsequent additions to the specified annotation position. The quad_anno()
function has the following aliases:
anno_top
: A special case ofquad_anno()
withposition = "top"
.anno_left
: A special case ofquad_anno()
withposition = "left"
.anno_bottom
: A special case ofquad_anno()
withposition = "bottom"
.anno_right
: A special case ofquad_anno()
withposition = "right"
.
When quad_anno()
is added to a ggheatmap()
/quad_layout()
, it will try to automatically create a new stack_layout()
. For top and bottom annotations, stack_alignv()
or stack_freev()
will be used; for left and right annotations, stack_alignh()
or stack_freeh()
will be applied.
quad_anno()
will always attempt to initialize a stack_layout()
with the same alignment as the current direction. This means that if observations need to be aligned horizontally, stack_alignh()
will be used for left and right annotations, otherwise, stack_freeh()
will be initialized instead. The same logic applies for vertical alignment—stack_alignv()
or stack_freev()
will be applied for top and bottom annotations, depending on whether alignment is required. However, you can also manually add a stack_free()
for directions that require alignment, which I’ll cover in the following section.
Additionally, quad_anno()
will set the active context to the annotation. This means that subsequent additions will be directed to the annotation rather than the main plot. We use the term active context
in contrast to active plot
(as described in Section 2.2), since the annotation is a Layout
object.
ggheatmap(small_mat) +
theme(axis.text.x = element_text(angle = -60, hjust = 0)) +
# we set the active context to the left annotation
anno_left() +
align_dendro()
#> → heatmap built with `geom_tile()`
By default, the annotation stack_layout()
will try to inherit data from ggheatmap()
/quad_layout()
. If the observations require alignment vertically, this means the data from ggheatmap()
/quad_layout()
should be a matrix, the column annotations will also require a matrix and the matrix from ggheatmap()
/quad_layout()
will be transposed for use in the column annotations.
ggheatmap(small_mat) +
# we set the active context to the top annotation
anno_top() +
align_dendro()
#> → heatmap built with `geom_tile()`
You can further customize the layout design or add new plots in the annotation stack, as described in Chapter 2.
ggheatmap(small_mat) +
# in the heatmap body, we set the axis text theme
theme(axis.text.x = element_text(angle = -60, hjust = 0)) +
# we set the active context to the right annotation
anno_right() +
# in the right annotation, we add a dendrogram
align_dendro(k = 3L) +
# in the dendrogram, we add a point layer
geom_point(aes(color = factor(branch)))
#> → heatmap built with `geom_tile()`
In this example:
anno_right()
initialize the right annotation stack, and change the active context to the right of the heatmap.align_dendro(k = 3L)
adds a dendrogram to the annotation and sets itself as the active plot in the annotation stack.geom_point(aes(color = factor(branch)))
is then added to this active plot within the annotation stack, here, it means thealign_dendro()
plot.
ggheatmap()
aligns observations both horizontally and vertically, so it’s safe to always use quad_anno()
directly, as all annotations require a matrix, and the layout data is also a matrix. However, for quad_alignh()
and quad_alignv()
(which I’ll discuss in more detail in a Chapter 6), which only align observations in one direction, the data in the layout may not fit the data for the annotation (when the layout requires alignment of observations, we typically use a matrix, regardless of whether alignment is needed in one or two directions)
- `quad_alignh()`: aligning observations in horizontal direction, for column
annotations, we ll need a data frame for `stack_free()`.
- `quad_alignv()`: aligning observations in vertical direction, for row
annotations, we ll need a data frame for `stack_free()`.
In both cases, quad_anno()
won’t initialize the annotation by default, instead, you must provide the annotation stack_layout()
manually.
3.5 Adding stack layout
Similar to adding a plot in stack_layout()
(Chapter 2), when the direction requires alignment, you can add both stack_align()
and stack_free()
. However, if the direction does not require alignment, you can add only stack_free()
.
To add a stack_layout()
to the ggheatmap()
, we must prevent the automatical creation of annotation by quad_anno()
by setting initialize = FALSE
<- stack_alignh(small_mat) +
my_stack_align align_dendro(aes(color = branch), k = 3L)
ggheatmap(small_mat) +
theme(axis.text.x = element_text(angle = -60, hjust = 0)) +
anno_right(initialize = FALSE) +
+
my_stack_align layout_title("stack_align()")
#> → heatmap built with `geom_tile()`
<- stack_freeh(mpg) +
my_stack_free ggfree(mapping = aes(displ, hwy, colour = class)) +
geom_point(size = 2)
ggheatmap(small_mat) +
theme(axis.text.x = element_text(angle = -60, hjust = 0)) +
anno_right(initialize = FALSE) +
+
my_stack_free layout_title("stack_free()")
#> → heatmap built with `geom_tile()`
Note when aligning the observations, you must ensure the number of observations
is consistent in the direction. So for column annotations, you need transpose the data manually.
<- stack_alignv(t(small_mat)) +
my_stack align_dendro(aes(color = branch), k = 3L)
ggheatmap(small_mat) +
anno_top(initialize = FALSE) +
my_stack#> → heatmap built with `geom_tile()`
3.6 quad_active()
To remove the active context and redirect additions back to the heatmap body, you can use quad_active()
.
ggheatmap(small_mat) +
# we set the active context to the top annotation
anno_top() +
# we split the observations into 3 groups by hierarchical clustering
align_dendro(k = 3L) +
# remove any active annotation
quad_active() +
# set fill color scale for the heatmap body
scale_fill_viridis_c()
#> → heatmap built with `geom_tile()`
3.7 quad_switch()
/hmanno()
We also provide quad_switch()
/hmanno()
(heatmap annotation) which integrates quad_active()
and quad_anno()
into one function for ease of use. Feel free to use any of these functions to streamline your annotation process.
ggheatmap(small_mat) +
# we set the active context to the top annotation
quad_switch("t") +
# we split the observations into 3 groups by hierarchical clustering
align_dendro(k = 3L) +
# remove any active annotation
quad_switch() +
# set fill color scale for the heatmap body
scale_fill_viridis_c() +
layout_title("quad_switch()")
#> → heatmap built with `geom_tile()`
ggheatmap(small_mat) +
# we set the active context to the top annotation
hmanno("t") +
# we split the observations into 3 groups by hierarchical clustering
align_dendro(k = 3L) +
# remove any active annotation
hmanno() +
# set fill color scale for the heatmap body
scale_fill_viridis_c()+
layout_title("hmanno()")
#> → heatmap built with `geom_tile()`
3.8 Plot Size
3.8.1 Heatmap Body Size
You can specify the relative sizes of the heatmap body using the width
and height
arguments in the ggheatmap()
function.
ggheatmap(small_mat, height = 2) +
anno_top() +
align_dendro()
#> → heatmap built with `geom_tile()`
Alternatively, the quad_active()
function allows you to control the heatmap body sizes.
ggheatmap(small_mat) +
quad_active(height = 2) +
anno_top() +
align_dendro()
#> → heatmap built with `geom_tile()`
3.8.2 Annotation Stack Size
The quad_anno()
function allows you to control the total annotation stack size. The size
argument controls the relative width (for left and right annotations) or height (for top and bottom annotations) of the whole annotation stack.
ggheatmap(small_mat) +
anno_top(size = 1) +
align_dendro()
#> → heatmap built with `geom_tile()`
You can also specify it as an absolute size using unit()
:
ggheatmap(small_mat) +
anno_top(size = unit(30, "mm")) +
align_dendro()
#> → heatmap built with `geom_tile()`
Note that the size of an individual plot (Section 2.4) does not affect the total annotation stack size. You must adjust the annotation size using the method described above.
ggheatmap(small_mat) +
anno_top() +
align_dendro(size = unit(30, "mm")) +
layout_title("plot size")
#> → heatmap built with `geom_tile()`
ggheatmap(small_mat) +
anno_top(size = unit(30, "mm")) +
align_dendro() +
layout_title("annotation size")
#> → heatmap built with `geom_tile()`
In this chapter, we explored the usage of heatmap layout. These features provide a strong foundation for visualizing matrix-based data in a structured way. However, as your visualization needs grow more complex, the ability to further customize and fine-tune the layout becomes essential.
In the next chapter, we will dive into the Layout Customize functionalities, where you can gain full control over your plot’s layout.