forked from jokergoo/circlize_book
-
Notifications
You must be signed in to change notification settings - Fork 0
/
07-advanced-usage.Rmd
286 lines (233 loc) · 11.2 KB
/
07-advanced-usage.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
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
```{r, echo = FALSE}
library(circlize)
```
# Advanced layout {#advanced-layout}
## Zooming of sectors {#zooming-of-sectors}
In this section, we will introduce how to zoom sectors and put the zoomed sectors
at the same track as the original sectors.
Under the default settings, width of sectors are calculated according to the
data range in corresponding categories. Normally it is not a good idea to
manually modify the default sector width since it reflects useful information
of your data. However, sometimes manually modifying the width of sectors can
make more advanced plots, e.g. zoomings.
The basic idea for zooming is to put original sectors on part of the circle and
put the zoomed sectors on the other part, so that in the original sectors, widths
are still proportional to their data ranges, and in the zoomed sectors, the
widths are also proportional to the data ranges in the zoomed sectors.
This type of zooming is rather simple to implement. All we need to do is to
copy the data which corresponds to the zoomed sectors, assign new category names
to them and append to the original data. The good thing is since the data in
the zoomed sectors is exactly the same as the original sectors, if you treat them
as normal categories, the graphics will be exactly the same as in the original
sectors, but with x direction zoomed.
Following example shows more clearly the basic idea of this "horizontal"
zooming.
We first generate a data frame with six categories.
```{r}
set.seed(123)
df = data.frame(
sectors = sample(letters[1:6], 400, replace = TRUE),
x = rnorm(400),
y = rnorm(400),
stringsAsFactors = FALSE
)
```
We want to zoom sector a and the first 10 points in sector b. First we extract
these data and format as a new data frame.
```{r}
zoom_df_a = df[df$sectors == "a", ]
zoom_df_b = df[df$sectors == "b", ]
zoom_df_b = zoom_df_b[order(zoom_df_b[, 2])[1:10], ]
zoom_df = rbind(zoom_df_a, zoom_df_b)
```
Then we need to change the sector names in the zoomed data frame. Here we just simply
add "zoom_" prefix to the original names to show that they are "zoomed" sectors. After
that, it is attached to the original data frame.
```{r}
zoom_df$sectors = paste0("zoom_", zoom_df$sectors)
df2 = rbind(df, zoom_df)
```
In this example, we will put the original cells in the left half of the circle
and the zoomed sectors in the right. As we have already mentioned before, we
simply normalize the width of normal sectors and normalize the width of zoomed
sectors separately. Note now the sum of the sector width for the original sectors
is 1 and the sum of sector width for the zoomed sectors is 1, which means
these two types of sectors have their own half circle.
You may notice the sum of the `sector.width` is not idential to 1. This is
fine, they will be further normalized to 1 internally.
Strictly speaking, since the gaps between sectors are not taken into
consideration, the width of the original sectors are not exactly 180 degree,
but the real value is quite close to it.
```{r}
xrange = tapply(df2$x, df2$sectors, function(x) max(x) - min(x))
normal_sector_index = unique(df$sectors)
zoomed_sector_index = unique(zoom_df$sectors)
sector.width = c(xrange[normal_sector_index] / sum(xrange[normal_sector_index]),
xrange[zoomed_sector_index] / sum(xrange[zoomed_sector_index]))
sector.width
```
What to do next is just to make the circular plot in the normal way. All the
graphics in sector a and b will be automatically zoomed to sector "zoom_a"
and "zoom_b".
In following code, since the sector names are added outside the first track,
`points.overflow.warning` is set to `FALSE` to turn off the warning messages.
```{r circlize_zoom_1, eval = FALSE}
circos.par(start.degree = 90, points.overflow.warning = FALSE)
circos.initialize(df2$sectors, x = df2$x, sector.width = sector.width)
circos.track(df2$sectors, x = df2$x, y = df2$y,
panel.fun = function(x, y) {
circos.points(x, y, col = "red", pch = 16, cex = 0.5)
circos.text(CELL_META$xcenter, CELL_META$cell.ylim[2] + mm_y(2),
CELL_META$sector.index, niceFacing = TRUE)
})
```
Adding links from original sectors to zoomed sectors is a good idea to show
where the zooming occurs (Figure \@ref(fig:circlize-zoom)). Notice that
we manually adjust the position of one end of the sector b link.
```{r circlize_zoom_2, eval = FALSE}
circos.link("a", get.cell.meta.data("cell.xlim", sector.index = "a"),
"zoom_a", get.cell.meta.data("cell.xlim", sector.index = "zoom_a"),
border = NA, col = "#00000020")
circos.link("b", c(zoom_df_b[1, 2], zoom_df_b[10, 2]),
"zoom_b", get.cell.meta.data("cell.xlim", sector.index = "zoom_b"),
rou1 = get.cell.meta.data("cell.top.radius", sector.index = "b"),
border = NA, col = "#00000020")
circos.clear()
```
```{r circlize-zoom, echo = FALSE, fig.cap = "Zoom sectors."}
chunks <- knitr:::knit_code$get()
eval(parse(text = chunks[["circlize_zoom_1"]]))
eval(parse(text = chunks[["circlize_zoom_2"]]))
```
Chapter \@ref(nested-zooming) introduces another type of zooming by combining two circular plots.
## Visualize part of the circle {#part-circle}
`canvas.xlim` and `canvas.ylim` parameters in `circos.par()` are useful to
generate plots only in part of the circle. As mentioned in previews chapters,
the circular plot is always drawn in a canvas where x values range from -1 to
1 and y values range from -1 to 1. Thus, if `canvas.xlim` and `canvas.ylim`
are all set to `c(0, 1)`, which means, the canvas is restricted to the right
top part, then only sectors between 0 to 90 degree are visible
(Figure \@ref(fig:circlize-part)).
```{r circlize-part, echo = FALSE, fig.width = 8, fig.height = 4, fig.cap = "One quarter of the circle."}
source("src/intro-17-part.R")
```
To make the right plot in Figure \@ref(fig:circlize-part), we only need to set
one sector in the layout and set `gap.after` to 270. (One sector with
`gap.after` of 270 degree means the width of this sector is exactly 90
degree.)
```{r, eval = FALSE}
circos.par("canvas.xlim" = c(0, 1), "canvas.ylim" = c(0, 1),
"start.degree" = 90, "gap.after" = 270)
sectors = "a" # this is the name of your sector
circos.initialize(sectors = sectors, xlim = ...)
...
```
Similar idea can be applied to the circle where in some tracks,
only a subset of cells are needed. Gererally there are two ways.
The first way is to create the track and add graphics with subset of data that
only corresponds to the cells that are needed. And the second way
is to create an empty track first and customize the cells by `circos.update()`.
Following code illustrates the two methods (Figure \@ref(fig:circlize-part2)).
```{r circlize-part2, fig.cap = "Show subset of cells in tracks."}
sectors = letters[1:4]
circos.initialize(sectors, xlim = c(0, 1))
# directly specify the subset of data
df = data.frame(sectors = rep("a", 100),
x = runif(100),
y = runif(100))
circos.track(df$sectors, x = df$x, y = df$y,
panel.fun = function(x, y) {
circos.points(x, y, pch = 16, cex = 0.5)
})
# create empty track first then fill graphics in the cell
circos.track(ylim = range(df$y), bg.border = NA)
circos.update(sector.index = "a", bg.border = "black")
circos.points(df$x, df$y, pch = 16, cex = 0.5)
circos.track(sectors = sectors, ylim = c(0, 1))
circos.track(sectors = sectors, ylim = c(0, 1))
circos.clear()
```
## Combine multiple circular plots {#combine-circular-plots}
**circlize** finally makes the circular plot in the base R graphic system.
Seperated circular plots actually can be put in a same page by some tricks
from the base graphic system. Here the key is `par(new = TRUE)` which allows
to draw a new figure as a new layer directly on the previous canvas region. By
setting different `canvas.xlim` and `canvas.ylim`, it allows to make more
complex plots which include more than one circular plots.
Folowing code shows how the two independent circualr plots are added and
nested. Figure \@ref(fig:circlize-nested) illustrates the invisible canvas
coordinate and how the two circular plots overlap.
```{r, eval = FALSE}
sectors = letters[1:4]
circos.initialize(sectors, xlim = c(0, 1))
circos.track(ylim = c(0, 1), panel.fun = function(x, y) {
circos.text(0.5, 0.5, "outer circos", niceFacing = TRUE)
})
circos.clear()
par(new = TRUE) # <- magic
circos.par("canvas.xlim" = c(-2, 2), "canvas.ylim" = c(-2, 2))
sectors = letters[1:3]
circos.initialize(sectors, xlim = c(0, 1))
circos.track(ylim = c(0, 1), panel.fun = function(x, y) {
circos.text(0.5, 0.5, "inner circos", niceFacing = TRUE)
})
circos.clear()
```
```{r circlize-nested, echo = FALSE, fig.width = 8, fig.height = 8/3, fig.cap = "Nested circular plots."}
source("src/intro-19-nested.R")
```
The second example (Figure \@ref(fig:circlize-separated)) makes a plot where
two circular plots separate from each other. You can use technique introduced
in Section \@ref(part-circle) to only show part of the circle, select proper
`canvas.xlim` and `canvas.ylim`, and finally arrange the two plots into one
page. The source code for generating Figure \@ref(fig:circlize-separated) is
at https://github.com/jokergoo/circlize_book/blob/master/src/intro-20-separated.R.
```{r circlize-separated, echo = FALSE, fig.width = 8, fig.height = 8/3, fig.cap = "Two separated circular plots"}
source("src/intro-20-separated.R")
```
The third example is to draw cells with different radius (Figure \@ref(fig:circlize-diff-radius)).
In fact, it makes four circular plots where only one
sector for each plot is plotted.
```{r circlize-diff-radius, fig.cap = "Cells with differnet radius."}
sectors = letters[1:4]
lim = c(1, 1.1, 1.2, 1.3)
for(i in 1:4) {
circos.par("canvas.xlim" = c(-lim[i], lim[i]),
"canvas.ylim" = c(-lim[i], lim[i]),
"track.height" = 0.4)
circos.initialize(sectors, xlim = c(0, 1))
circos.track(ylim = c(0, 1), bg.border = NA)
circos.update(sector.index = sectors[i], bg.border = "black")
circos.points(runif(10), runif(10), pch = 16)
circos.clear()
par(new = TRUE)
}
par(new = FALSE)
```
Note above plot is different from the example in Figure \@ref(fig:circlize-part2).
In Figure \@ref(fig:circlize-part2), cells both visible and invisible all belong to
a same track and they are in a same circular plot, thus they should have same
radius. But for the example here, cells have different radius and they belong
to different circular plot.
In chapter \@ref(nested-zooming), we use this technique to implement zoomings by combining two circular plots.
## Arrange multiple plots {#arrange-multiple-plots}
**circlize** is implemented in the base R graphic system, thus, you can use
`layout()` or `par("mfrow")`/`par("mfcol")` to arrange multiple circular plots in one page
(Figure \@ref(fig:circlize-multiple-layout)).
```{r circlize-multiple-layout, fig.width = 8, fig.height = 8, fig.cap = "Arrange multiple circular plots."}
layout(matrix(1:9, 3, 3))
for(i in 1:9) {
sectors = 1:8
par(mar = c(0.5, 0.5, 0.5, 0.5))
circos.par(cell.padding = c(0, 0, 0, 0))
circos.initialize(sectors, xlim = c(0, 1))
circos.track(ylim = c(0, 1), track.height = 0.05,
bg.col = rand_color(8), bg.border = NA)
for(i in 1:20) {
se = sample(1:8, 2)
circos.link(se[1], runif(2), se[2], runif(2),
col = rand_color(1, transparency = 0.4), border = NA)
}
circos.clear()
}
```