forked from jokergoo/circlize_book
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy path04-legends.Rmd
185 lines (153 loc) · 7.54 KB
/
04-legends.Rmd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
```{r, echo = FALSE}
library(grid)
knitr::opts_chunk$set(
dev = "png"
)
```
# Legends {#legends}
```{r, echo = FALSE, warning = FALSE, results = "hide", message = FALSE, include = FALSE}
suppressPackageStartupMessages(library(ComplexHeatmap))
suppressPackageStartupMessages(library(gridBase))
suppressPackageStartupMessages(library(circlize))
```
**circlize** provides complete freedom for users to design their own graphics
by implementing the self-defined function `panel.fun`. However one drawback
arises that **circlize** is completely blind to users' data so that one
important thing is missing for the visualization which is the legend.
Although legends cannot be automatically generated by **circlize**, by using
functionality from other R packages, it is just a few more lines to really
implement it. Here I will demonstrate how to customize legends and arrange to
the circular plot.
As an example, a circular plot which contains two tracks and links inside the
circle is generated. The first track will have a legend that contains points,
the second track will have a legend that contains lines, and the links
correspond to a continuous color mapping. The code is wrapped into a function
so that it can be used repeatedly.
```{r}
library(circlize)
col_fun = colorRamp2(c(-2, 0, 2), c("green", "yellow", "red"))
circlize_plot = function() {
set.seed(12345)
sectors = letters[1:10]
circos.initialize(sectors, xlim = c(0, 1))
circos.track(ylim = c(0, 1), panel.fun = function(x, y) {
circos.points(runif(20), runif(20), cex = 0.5, pch = 16, col = 2)
circos.points(runif(20), runif(20), cex = 0.5, pch = 16, col = 3)
})
circos.track(ylim = c(0, 1), panel.fun = function(x, y) {
circos.lines(sort(runif(20)), runif(20), col = 4)
circos.lines(sort(runif(20)), runif(20), col = 5)
})
for(i in 1:10) {
circos.link(sample(sectors, 1), sort(runif(10))[1:2],
sample(sectors, 1), sort(runif(10))[1:2],
col = add_transparency(col_fun(rnorm(1))))
}
circos.clear()
}
```
In **ComplexHeatmap** package with version higher than 1.99.0, there is a
`Legend()` function which customizes legends with various styles. In following
code, legends for the two tracks and links are constructed. In the end the
three legends are packed vertically by `packLegend()`. For more detailed usage
of `Legend()` and `packLegend()`, please refer to their help pages and [the
ComplexHeatmap
book](https://jokergoo.github.io/ComplexHeatmap-reference/book/legends.html).
```{r legend_list}
library(ComplexHeatmap)
# discrete
lgd_points = Legend(at = c("label1", "label2"), type = "points",
legend_gp = gpar(col = 2:3), title_position = "topleft",
title = "Track1")
# discrete
lgd_lines = Legend(at = c("label3", "label4"), type = "lines",
legend_gp = gpar(col = 4:5, lwd = 2), title_position = "topleft",
title = "Track2")
# continuous
lgd_links = Legend(at = c(-2, -1, 0, 1, 2), col_fun = col_fun,
title_position = "topleft", title = "Links")
lgd_list_vertical = packLegend(lgd_points, lgd_lines, lgd_links)
lgd_list_vertical
```
`lgd_points`, `lgd_lines`, `lgd_links` and `lgd_list_vertical` can be thought
as boxes which contain all graphical elements for legends and they can be
added to the plot by `draw()`.
**circlize** is implemented in the base graphic system while
**ComplexHeatmap** is implemented by **grid** graphic system. However, these
two systems can be mixed somehow. We can directly add grid graphics to the base
graphics. (Actually they are two independent layers but drawn on a same
graphic device.)
```{r directly-add, dev = "png", echo = 1:9, fig.width = 4, fig.height = 4, fig.cap = "Directly add grid graphics."}
circlize_plot()
# next the grid graphics are added directly to the plot
# where circlize has created.
draw(lgd_list_vertical, x = unit(4, "mm"), y = unit(4, "mm"), just = c("left", "bottom"))
```
In Figure \@ref(fig:directly-add), the whole image region corresponds to the
circular plot and the legend layer is drawn just on top of it. Actually you
can see that one big problem is when there are many legends that the size for
the legends is too big, they may overap to the circle. One solution is to
split the legends into several parts and add each part to different corners in
the plot (Figure \@ref(fig:two-legends)).
```{r two-legends, dev = "png", fig.width = 5, fig.height = 4, fig.cap = "Split into two legends."}
lgd_list_vertical2 = packLegend(lgd_points, lgd_lines)
circlize_plot()
# next the grid graphics are added directly to the plot
# where circlize has created.
draw(lgd_list_vertical2, x = unit(4, "mm"), y = unit(4, "mm"), just = c("left", "bottom"))
draw(lgd_links, x = unit(1, "npc") - unit(2, "mm"), y = unit(4, "mm"),
just = c("right", "bottom"))
```
Still it can not solve the problem and sometimes it even makes the plot so
messed up. One better way is to split the image region into two parts where
one part only for the circular plot and the other part for legends.
To mix grid graphics and base graphics, ther are two important packages to
use: the **grid** package and **gridBase** package. **grid** is the base for
making grid graphics as well as arranging plotting regions (or, _viewports_ in
**grid** term), and **gridBase** makes it easy to integrate base graphics into
grid system.
Following code is straightforward to understand. Only one line needs to be
noticed: `par(omi = gridOMI(), new = TRUE)` that `gridOMI()` calculates the
outer margins for the base graphics so that the base graphics can be put at
the correct place and `new = TRUE` to ensure the base graphics are added to
current graphic device instead of opening a new one.
Here I use `plot.new()` to open a new graphic device. In interactive session,
it seems ok if you also use `grid.newpage()`, but `grid.newpage()` gives error
when building a **knitr** document.
```{r right-legend, dev = "png", fig.width = 4 + convertWidth(ComplexHeatmap:::width(lgd_list_vertical), "inches", valueOnly = TRUE) + 0.08, fig.height = 4}
library(gridBase)
plot.new()
circle_size = unit(1, "snpc") # snpc unit gives you a square region
pushViewport(viewport(x = 0, y = 0.5, width = circle_size, height = circle_size,
just = c("left", "center")))
par(omi = gridOMI(), new = TRUE)
circlize_plot()
upViewport()
draw(lgd_list_vertical, x = circle_size, just = "left")
```
The legends can also be put at the bottom of the circular plot and it is just
a matter how users arrange the grid viewports. In this case, all legends are
changed to horizontal style, and three legends are packed horizontally as
well.
```{r}
lgd_points = Legend(at = c("label1", "label2"), type = "points",
legend_gp = gpar(col = 2:3), title_position = "topleft",
title = "Track1", nrow = 1)
lgd_lines = Legend(at = c("label3", "label4"), type = "lines",
legend_gp = gpar(col = 4:5, lwd = 2), title_position = "topleft",
title = "Track2", nrow = 1)
lgd_links = Legend(at = c(-2, -1, 0, 1, 2), col_fun = col_fun,
title_position = "topleft", title = "Links", direction = "horizontal")
lgd_list_horizontal = packLegend(lgd_points, lgd_lines, lgd_links,
direction = "horizontal")
```
Similar code to arrange viewports.
```{r bottom-legend, dev = "png", fig.height = 4 + convertHeight(ComplexHeatmap:::height(lgd_list_horizontal), "inches", valueOnly = TRUE) + 0.08, fig.width = 4}
plot.new()
pushViewport(viewport(x = 0.5, y = 1, width = circle_size, height = circle_size,
just = c("center", "top")))
par(omi = gridOMI(), new = TRUE)
circlize_plot()
upViewport()
draw(lgd_list_horizontal, y = unit(1, "npc") - circle_size, just = "top")
```