Chapter 5 Legends
The heatmaps and simple annotations automatically generate legends which are
put one the right side of the heatmap. By default there is no legend for
complex annotations, but they can be constructed and added manually (Section
5.5). All legends are internally constructed by
Legend()
constructor. In later sections, we first introduce the settings for
continuous legends and discrete legends, then we will discuss how to configure
the legends associated with the heatmaps and annotations, and how to add new
legends to the plot.
All the legends (no matter a single legend or a pack of legends) all belong to
the Legends
class. The class only has one slot grob
which is the real
grid::grob
object or the grid::gTree
object that records how to draw the
graphics. The wrapping of the Legends
class and the methods designed for the
class make legends as single objects and can be drawn like points with
specifying the positions on the viewport.
The legends for heatmaps and annotations can be controlled by
heatmap_legend_param
argument in Heatmap()
, or annotation_legend_param
argument in HeatmapAnnotation()
. Most of the parameters in Legend()
function can be directly set in the two arguments with the same parameter
name. The details of setting heatmap legends and annotation legends
parameters are introduced in Section 5.4.
5.1 Continuous legends
Since most of heatmaps contain continuous values, we first introduce the settings for the continuous legend.
Continuous legend needs a color mapping function which should be generated by
circlize::colorRamp2()
. In the heatmap legends and annotation legends that
are automatically generated, the color mapping functions are passed by the
col
argument from Heatmap()
or HeatmapAnnotation()
function, while if
you construct a self-defined legend, you need to provide the color mapping
function.
The break values provided in the color mapping function (e.g. c(0, 0.5, 1)
in following example) will not exactly be the same as the break values in the
legends). The finally break values presented in the legend are internally
adjusted to make the numbers of labels close to 5 or 6.
First we show the default style of a vertical continuous legend:
library(circlize)
col_fun = colorRamp2(c(0, 0.5, 1), c("blue", "white", "red"))
lgd = Legend(col_fun = col_fun, title = "foo")
lgd
is a Legends
class object. The size of the legend can be obtained by
ComplexHeatmap:::width()
and ComplexHeatmap:::height()
function.
## [1] 9.90361111111111mm
## [1] 28.0329444444444mm
The legend is actually a packed graphic object composed of rectangles, lines
and texts. It can be added to the plot by draw()
function. In
ComplexHeatmap pacakge, you don’t need to use draw()
directly on legend
objects, but it might be useful if you use the legend objects in other places.
pushViewport(viewport(width = 0.9, height = 0.9))
grid.rect() # border
draw(lgd, x = unit(1, "cm"), y = unit(1, "cm"), just = c("left", "bottom"))
draw(lgd, x = unit(0.5, "npc"), y = unit(0.5, "npc"))
draw(lgd, x = unit(1, "npc"), y = unit(1, "npc"), just = c("right", "top"))
popViewport()
If you only want to configure the legends generated by heatmaps or
annotations, you don’t need to construct the Legends
object by your own.
The parameters introduced later can be directly used to customize the
legends by heatmap_legend_param
argument in Heatmap()
and
annotation_legend_param
argument in HeatmapAnnotation()
(introduced in
Section
5.4). It is still nice to see how these parameters change the
styles of the legend in following examples. Following is a simple example
showing how to configure legends in the heatmap and heatmap annotation.
Heatmap(matrix(rnorm(100), 10),
heatmap_legend_param = list(
title = "rnorm", at = c(-2, 0, 2),
labels = c("neg_two", "zero", "pos_two")
),
top_annotation = HeatmapAnnotation(
foo = 1:10,
annotation_legend_param = list(foo = list(title = "foo_top_anno"))
))
In following examples, we only show how to construct the legend object, while
not show the code which draws the legends. Only remember you can use draw()
function on the Legends
object to draw the single legend on the plot.
For continuous legend, you can manually adjust the break values in the legend
by setting at
. Note the height is automatically adjusted.
The labels corresponding to the break values are set by labels
.
lgd = Legend(col_fun = col_fun, title = "foo", at = c(0, 0.5, 1),
labels = c("low", "median", "high"))
The height of the vertical continous legend is set by legend_height
.
legend_height
can only be set for the veritcal continous legend and the
value is the height of the legend body (excluding the legend title).
If it is a vertical legend, grid_width
controls the widths of the legend
body. grid_width
is originally designed for the discrete legends where the
each level in the legend is a grid, but here we use the same name for the
parameter that controls the width of the legend.
The graphic parameters for the labels are controlled by labels_gp
.
The border of the legend as well as the ticks for the break values are
controlled by border
. The value of border
can be logical or a string of
color.
title_position
controls the position of titles. For vertical legends, the
value should be one of topleft
, topcenter
, lefttop-rot
and
leftcenter-rot
. Following two plots show the effect of lefttop-rot
title
and leftcenter-rot
title.
lgd = Legend(col_fun = col_fun, title = "foooooooo", title_position = "lefttop-rot",
legend_height = unit(4, "cm"))
lgd = Legend(col_fun = col_fun, title = "foooooooo", title_position = "leftcenter-rot",
legend_height = unit(4, "cm"))
Legend titles and labels can be set as mathematical formulas.
lgd = Legend(col_fun = col_fun, title = expression(hat(beta) == (X^t * X)^{-1} * X^t * y),
at = c(0, 0.25, 0.5, 0.75, 1), labels = expression(alpha, beta, gamma, delta, epsilon))
Settings for horizontal continuous legends are almost the same as vertical
legends, except that now legend_width
controls the width of the legend, and
the title position can only be one of topcenter
, topleft
, lefttop
and
leftcenter
.
The default style for horizontal legend:
Manually set at
:
lgd = Legend(col_fun = col_fun, title = "foo", at = c(0, 0.25, 0.5, 0.75, 1),
direction = "horizontal")
Manually set labels
:
lgd = Legend(col_fun = col_fun, title = "foo", at = c(0, 0.5, 1),
labels = c("low", "median", "high"), direction = "horizontal")
Set legend_width
:
lgd = Legend(col_fun = col_fun, title = "foo", legend_width = unit(6, "cm"),
direction = "horizontal")
Set graphic parameters for labels:
lgd = Legend(col_fun = col_fun, title = "foo", labels_gp = gpar(col = "red", font = 3),
direction = "horizontal")
Set rotations of labels.
Title can be set as topleft
, topcenter
or lefttop
and leftcenter
.
lgd = Legend(col_fun = col_fun, title = "foooooooo", direction = "horizontal",
title_position = "topcenter")
lgd = Legend(col_fun = col_fun, title = "foooooooo", direction = "horizontal",
title_position = "lefttop")
In examples we showed above, the intervals between every two break values are
equal. Actually at
can also be set as break values with uneuqal intervals.
In this scenario, the ticks on the legend are still at the original places
while the corresponding texts are shifted to get rid of overlapping. Then,
there are lines connecting the ticks and the labels.
If the labels do not need to be adjusted, they are still at the original places.
It is similar for the horizontal legends:
lgd = Legend(col_fun = col_fun, title = "foo", at = c(0, 0.1, 0.15, 0.5, 0.9, 0.95, 1),
direction = "horizontal")
Set rotations of labels to 90 degree.
lgd = Legend(col_fun = col_fun, title = "foo", at = c(0, 0.1, 0.15, 0.5, 0.9, 0.95, 1),
direction = "horizontal", title_position = "lefttop", labels_rot = 90)
When the position of title is set to lefttop
, the area below the title will
also be taken into account when calculating the adjusted positions of labels.
lgd = Legend(col_fun = col_fun, title = "foo", at = c(0, 0.1, 0.5, 0.75, 1),
labels = c("mininal", "q10", "median", "q75", "maximal"),
direction = "horizontal", title_position = "lefttop")
5.2 Discrete legends
Discrete legends are used for discrete color mappings. The continuous color mapping can also be degenerated as discrete color mapping by only providing the colors and the break values.
You can either specify at
or labels
, but most probably you specify
labels
. The colors should be specified by legend_gp
.
The discrete legend for continuous color mapping:
The position of title:
lgd = Legend(labels = month.name[1:6], title = "foo", legend_gp = gpar(fill = 1:6),
title_position = "lefttop")
lgd = Legend(labels = month.name[1:6], title = "foo", legend_gp = gpar(fill = 1:6),
title_position = "leftcenter-rot")
The size of grids are controlled by grid_width
and grid_height
.
lgd = Legend(at = 1:6, legend_gp = gpar(fill = 1:6), title = "foo",
grid_height = unit(1, "cm"), grid_width = unit(5, "mm"))
The graphic parameters of labels are controlled by labels_gp
.
lgd = Legend(labels = month.name[1:6], legend_gp = gpar(fill = 1:6), title = "foo",
labels_gp = gpar(col = "red", fontsize = 14))
The graphic parameters of the title are controlled by title_gp
.
lgd = Legend(labels = month.name[1:6], legend_gp = gpar(fill = 1:6), title = "foo",
title_gp = gpar(col = "red", fontsize = 14))
Borders of grids are controlled by border
.
One important thing for the discrete legend is you can arrange the grids into
multiple rows or/and columns. If ncol
is set to a number, the grids are
arranged into ncol
columns.
Still the title position is calculated based on the multiplt-column legend.
lgd = Legend(labels = month.name[1:10], legend_gp = gpar(fill = 1:10), title = "foo",
ncol = 3, title_position = "topcenter")
You can choose to list the legend levels by rows by setting by_row = TRUE
.
lgd = Legend(labels = month.name[1:10], legend_gp = gpar(fill = 1:10), title = "foo",
ncol = 3, by_row = TRUE)
The gap between two columns are controlled by gap
.
lgd = Legend(labels = month.name[1:10], legend_gp = gpar(fill = 1:10), title = "foo",
ncol = 3, gap = unit(1, "cm"))
Instead of ncol
, you can also specify the layout by nrow
. Note you cannot
use ncol
and nrow
at a same time.
One extreme case is when all levels are put in one row and the title are rotated by 90 degree. The height of the legend will be the height of the rotated title.
lgd = Legend(labels = month.name[1:6], legend_gp = gpar(fill = 1:6), title = "foooooo",
nrow = 1, title_position = "lefttop-rot")
Following style a lot of people might like:
lgd = Legend(labels = month.name[1:6], legend_gp = gpar(fill = 1:6), title = "foooooo",
nrow = 1, title_position = "leftcenter")
Legend()
also supports to use simple graphics (e.g. points, lines, boxplots) as
legends. type
argument can be specified as points
or p
that you can use number
for pch
or single-letter for pch
.
lgd = Legend(labels = month.name[1:6], title = "foo", type = "points",
pch = 1:6, legend_gp = gpar(col = 1:6), background = "#FF8080")
lgd = Legend(labels = month.name[1:6], title = "foo", type = "points",
pch = letters[1:6], legend_gp = gpar(col = 1:6), background = "white")
Or set type = "lines"
/type = "l"
to use lines as legend:
lgd = Legend(labels = month.name[1:6], title = "foo", type = "lines",
legend_gp = gpar(col = 1:6, lty = 1:6), grid_width = unit(1, "cm"))
Or set type = "boxplot"
/type = "box"
to use boxes as legends:
lgd = Legend(labels = month.name[1:6], title = "foo", type = "boxplot",
legend_gp = gpar(fill = 1:6))
When pch
is an integer number, the numbers in 26:28
correspond to following symbols:
If you want more legend styles to be supported, contact me.
5.3 A list of legends
A list of legends can be constructed or packed as a Legends
object where the
individual legends are arranged within a certain layout. The legend list can
be sent to packLegend()
separatedly or as a list. The legend can be arranged
either vertically or horizontally. ComplexHeatmap uses packLegend()
internally to arrange multiple legends. Normally you don’t need to manually
control the arrangement of multiple legends, but the following section would
be useful if you want to manually construct a list of legends and apply to
other plots.
lgd1 = Legend(at = 1:6, legend_gp = gpar(fill = 1:6), title = "legend1")
lgd2 = Legend(col_fun = col_fun, title = "legend2", at = c(0, 0.25, 0.5, 0.75, 1))
lgd3 = Legend(labels = month.name[1:3], legend_gp = gpar(fill = 7:9), title = "legend3")
pd = packLegend(lgd1, lgd2, lgd3)
# which is same as
pd = packLegend(list = list(lgd1, lgd2, lgd3))
Simillar as single legend, you can draw the packed legends by draw()
function. Also you can get the size of pd
by ComplexHeatmap:::width()
and
ComplexHeatmap:::height()
.
## [1] 19.1675555555556mm
## [1] 72.0988333333333mm
Horizontally arranging the legends simply by setting direction = "horizontal"
.
One feature of packLegend()
is, e.g. if the packing is vertically and the
sum of the packed legends exceeds the height specified by max_height
, it
will be rearragned as mutliple column layout. In following example, the
maximum height is 10cm
.
When all the legends are put into multiple columns, column_gap
controls the
space between two columns.
pd = packLegend(lgd1, lgd3, lgd2, lgd3, lgd2, lgd1, max_height = unit(10, "cm"),
column_gap = unit(1, "cm"))
Similar for horizontal packing:
lgd1 = Legend(at = 1:6, legend_gp = gpar(fill = 1:6), title = "legend1",
nr = 1)
lgd2 = Legend(col_fun = col_fun, title = "legend2", at = c(0, 0.25, 0.5, 0.75, 1),
direction = "horizontal")
pd = packLegend(lgd1, lgd2, lgd3, lgd1, lgd2, lgd3, max_width = unit(10, "cm"),
direction = "horizontal", column_gap = unit(5, "mm"), row_gap = unit(1, "cm"))
The packed legends pd
is also a Legends
object, which means you can use
draw()
to draw it by specifying the positions.
pd = packLegend(lgd1, lgd2, lgd3, direction = "horizontal")
pushViewport(viewport(width = 0.8, height = 0.8))
grid.rect()
draw(pd, x = unit(1, "cm"), y = unit(1, "cm"), just = c("left", "bottom"))
popViewport()
To be mentioned again, packLegend()
is used internally to manage the list
of heatmap and annotation legends.
5.4 Heatmap and annotation legends
Settings for heatmap legend are controlled by heatmap_legend_param
argument
in Heatmap()
. The value for heatmap_legend_param
is a list of parameters
which are supported in Legend()
.
m = matrix(rnorm(100), 10)
Heatmap(m, name = "mat", heatmap_legend_param = list(
at = c(-2, 0, 2),
labels = c("low", "zero", "high"),
title = "Some values",
legend_height = unit(4, "cm"),
title_position = "lefttop-rot"
))
annotation_legend_param
controls legends for annotations. Since a
HeatmapAnnotation
may contain multiple annotations, the value of
annotation_legend_param
is a list of configurations of each annotation.
ha = HeatmapAnnotation(foo = runif(10), bar = sample(c("f", "m"), 10, replace = TRUE),
annotation_legend_param = list(
foo = list(
title = "Fooooooh",
at = c(0, 0.5, 1),
labels = c("zero", "median", "one")
),
bar = list(
title = "Baaaaaaar",
at = c("f", "m"),
labels = c("Female", "Male")
)
))
Heatmap(m, name = "mat", top_annotation = ha)
If the heatmaps are concatenated horizontally, all heatmap and row annotation legends are grouped and all column annotation legends ae grouped. The reason we assume the horizontal direction passes the main message of the plot, while the vertical direction provides secondary information.
ha1 = HeatmapAnnotation(foo1 = runif(10), bar1 = sample(c("f", "m"), 10, replace = TRUE))
ha2 = HeatmapAnnotation(foo2 = runif(10), bar2 = sample(c("f", "m"), 10, replace = TRUE))
Heatmap(m, name = "mat1", top_annotation = ha1) +
rowAnnotation(sth = runif(10)) +
Heatmap(m, name = "mat2", top_annotation = ha2)
Similarlly, if the heatmaps are concatenated vertically, all heatmaps/column annotations are grouped and legends for all row annotations are grouped.
ha1 = HeatmapAnnotation(foo1 = runif(10), bar1 = sample(c("f", "m"), 10, replace = TRUE),
annotation_name_side = "left")
ha2 = HeatmapAnnotation(foo2 = runif(10), bar2 = sample(c("f", "m"), 10, replace = TRUE))
Heatmap(m, name = "mat1", top_annotation = ha1) %v%
Heatmap(m, name = "mat2", top_annotation = ha2,
right_annotation = rowAnnotation(sth = 1:10))
show_legend
in HeatmapAnnotation()
and show_heatmap_legend
in
Heatmap()
controls whether show the legends. Note show_legend
can be a
single logical value, a logical vector, or a named vector which controls
subset of annotations.
ha = HeatmapAnnotation(foo = runif(10),
bar = sample(c("f", "m"), 10, replace = TRUE),
show_legend = c(TRUE, FALSE), # it can also be show_legend = c(bar = FALSE)
annotation_name_side = "left")
Heatmap(m, name = "mat1", top_annotation = ha) +
Heatmap(m, name = "mat2", show_heatmap_legend = FALSE)
merge_legend
in draw()
function controlls whether to merge all the legends
into a single group. Normally, when there are many annotations and heatmaps,
the number of legends is always large. In this case, the legends are
automatically arranged into multiple columns (or multiple rows if they are put
at the bottom of the heatmaps) to get rid of being out of the figure page. If
a heatmap has heatmap annotations, the order of putting legends are: legends
for the left annotations, legends for the top annotations, legend of the
heatmap, legends for the bottom annotations and legends for the right
annotations.
ha1 = HeatmapAnnotation(foo1 = runif(10),
bar1 = sample(c("f", "m"), 10, replace = TRUE))
ha2 = rowAnnotation(foo2 = runif(10),
bar2 = sample(letters[1:3], 10, replace = TRUE))
ha3 = rowAnnotation(foo3 = runif(10),
bar3 = sample(month.name[1:3], 10, replace = TRUE))
ht_list = Heatmap(m, name = "mat1", top_annotation = ha1) +
Heatmap(m, name = "mat2", left_annotation = ha2) +
ha3
draw(ht_list, merge_legend = TRUE)
A continuous color mapping can have a
discrete legend by setting color_bar = "discrete")
, both work for heatmap
legends and annotation legends.
Heatmap(m, name = "mat", heatmap_legend_param = list(color_bar = "discrete"),
top_annotation = HeatmapAnnotation(foo = 1:10,
annotation_legend_param = list(
foo = list(color_bar = "discrete"))))
If the value
is a character vector, no matter it is an annotation or the one-row/one-column
matrix for the heatmap, the default order of legend labels is sort(unique(value))
and if value
is a factor, the order of legend labels is levels(value)
. Always remember the order can be
fine-tuned by setting at
and labels
parameters in heatmap_legend_param
/annotation_legend_param
in Heatmap()
/HeamtapAnnotation()
functions respectively.
## [1] "a" "b" "a" "c" "c" "c" "b" "c" "b" "b"
fa1 = factor(chr)
fa2 = factor(chr, levels = c("c", "a", "b"))
Heatmap(m, top_annotation = HeatmapAnnotation(chr = chr, fa1 = fa1, fa2 = fa2, fa3 = fa2,
annotation_legend_param = list(fa3 = list(at = c("b", "c", "a")))))
5.5 Add customized legends
The self-defined legends (constructed by Legend()
) can be added to the
heatmap legend list by heatmap_legend_list
argument in draw()
and the
legends for annotations can be added to the annotation legend list by
annotation_legend_list
argument.
There is a nice example of adding self-defined legends in Section 10.2, but here we show a simple example.
As mentioned before, only the heatmap and simple annotations can generate legends on the plot. ComplexHeatmap provides many annotation functions, but none of them supports generating legends. In following code, we add a point annotation, a line annotation and a summary annotation to the heatmaps.
ha1 = HeatmapAnnotation(pt = anno_points(1:10, gp = gpar(col = rep(2:3, each = 5)),
height = unit(2, "cm")), show_annotation_name = FALSE)
ha2 = HeatmapAnnotation(ln = anno_lines(cbind(1:10, 10:1), gp = gpar(col = 4:5, lty = 1:2),
height = unit(2, "cm")), show_annotation_name = FALSE)
m = matrix(rnorm(100), 10)
ht_list = Heatmap(m, name = "mat1", top_annotation = ha1) +
Heatmap(m, name = "mat2", top_annotation = ha2) +
Heatmap(m[, 1], name = "mat3",
top_annotation = HeatmapAnnotation(
summary = anno_summary(gp = gpar(fill = 2:3))
), width = unit(1, "cm"))
draw(ht_list, ht_gap = unit(7, "mm"), row_km = 2)
Next we construct legends for the points, the lines and the boxplots.
lgd_list = list(
Legend(labels = c("red", "green"), title = "pt", type = "points", pch = 16,
legend_gp = gpar(col = 2:3)),
Legend(labels = c("darkblue", "lightblue"), title = "ln", type = "lines",
legend_gp = gpar(col = 4:5, lty = 1:2)),
Legend(labels = c("group1", "group2"), title = "km", type = "boxplot",
legend_gp = gpar(fill = 2:3))
)
draw(ht_list, ht_gap = unit(7, "mm"), row_km = 2, annotation_legend_list = lgd_list)
5.6 The side of legends
By default, the heatmap legends and annotation legends are put on the right of
the plot. The side relative to the heatmaps of the two types of legends can be
controlled by heatmap_legend_side
and annotation_legend_side
arguments in
draw()
function. The values that can be set for the two arguments are
left
, right
, bottom
and top
.
m = matrix(rnorm(100), 10)
ha1 = HeatmapAnnotation(foo1 = runif(10), bar1 = sample(c("f", "m"), 10, replace = TRUE))
ha2 = HeatmapAnnotation(foo2 = runif(10), bar2 = sample(c("f", "m"), 10, replace = TRUE))
ht_list = Heatmap(m, name = "mat1", top_annotation = ha1) +
rowAnnotation(sth = runif(10)) +
Heatmap(m, name = "mat2", top_annotation = ha2)
draw(ht_list, heatmap_legend_side = "left", annotation_legend_side = "bottom")
When the legends are put at the bottom or on the top, the legends are arranged
horizontally. We might also want to set every single legend as horizontal
legend, this needs to be set via the heatmap_legend_param
and
annotation_legend_param
arguments in Heatmap()
and HeatmapAnnotation()
functions:
ha1 = HeatmapAnnotation(foo1 = runif(10), bar1 = sample(c("f", "m"), 10, replace = TRUE),
annotation_legend_param = list(
foo1 = list(direction = "horizontal"),
bar1 = list(nrow = 1)))
ha2 = HeatmapAnnotation(foo2 = runif(10), bar2 = sample(c("f", "m"), 10, replace = TRUE),
annotation_legend_param = list(
foo2 = list(direction = "horizontal"),
bar2 = list(nrow = 1)))
ht_list = Heatmap(m, name = "mat1", top_annotation = ha1,
heatmap_legend_param = list(direction = "horizontal")) +
rowAnnotation(sth = runif(10),
annotation_legend_param = list(sth = list(direction = "horizontal"))) +
Heatmap(m, name = "mat2", top_annotation = ha2,
heatmap_legend_param = list(direction = "horizontal"))
draw(ht_list, merge_legend = TRUE, heatmap_legend_side = "bottom",
annotation_legend_side = "bottom")