forked from jokergoo/circlize_book
-
Notifications
You must be signed in to change notification settings - Fork 0
/
15-chord-diagram-advanced-usage.Rmd
389 lines (312 loc) · 14.9 KB
/
15-chord-diagram-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
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
```{r, echo = FALSE}
set.seed(999)
mat = matrix(sample(18, 18), 3, 6)
rownames(mat) = paste0("S", 1:3)
colnames(mat) = paste0("E", 1:6)
grid.col = c(S1 = "red", S2 = "green", S3 = "blue",
E1 = "grey", E2 = "grey", E3 = "grey", E4 = "grey", E5 = "grey", E6 = "grey")
library(circlize)
```
# Advanced usage of `chordDiagram()`
The default style of `chordDiagram()` is somehow enough for most visualization
tasks, still you can have more configurations on the plot.
The usage is same for both ajacency matrix and ajacency list, so we only
demonstrate with the matrix.
## Organization of tracks
By default, `chordDiagram()` creates two tracks, one track for labels and one
track for grids with axes.
```{r, echo = 2:3}
pdf(NULL)
chordDiagram(mat)
circos.info()
invisible(dev.off())
```
These two tracks can be controlled by `annotationTrack` argument. Available
values for this argument are `grid`, `name` and `axis`. The height of
annotation tracks can be set by `annotationTrackHeight` which is the
percentage to the radius of unit circle and can be set by `mm_h()` function with
an absolute unit. Axes are only added if `grid` is set in `annotationTrack`
(Figure \@ref(fig:chord-diagram-default-track)).
```{r chord-diagram-default-track, echo = -1, fig.width = 8, fig.height = 8/3, fig.cap = "Track organization in `chordDiagram()`."}
par(mfrow = c(1, 3))
chordDiagram(mat, grid.col = grid.col, annotationTrack = "grid")
chordDiagram(mat, grid.col = grid.col, annotationTrack = c("name", "grid"),
annotationTrackHeight = c(0.03, 0.01))
chordDiagram(mat, grid.col = grid.col, annotationTrack = NULL)
```
Several empty tracks can be allocated before Chord diagram is drawn. Then self-defined graphics can
be added to these empty tracks afterwards. The number of pre-allocated tracks can be set
through `preAllocateTracks`.
```{r echo = 2:3}
pdf(NULL)
chordDiagram(mat, preAllocateTracks = 2)
circos.info()
invisible(dev.off())
```
The default settings for pre-allocated tracks are:
```{r, eval = FALSE}
list(ylim = c(0, 1),
track.height = circos.par("track.height"),
bg.col = NA,
bg.border = NA,
bg.lty = par("lty"),
bg.lwd = par("lwd"))
```
The default settings for pre-allocated tracks can be overwritten by specifying `preAllocateTracks`
as a list.
```{r, eval = FALSE}
chordDiagram(mat, annotationTrack = NULL,
preAllocateTracks = list(track.height = 0.3))
```
If more than one tracks need to be pre-allocated, just specify `preAllocateTracks`
as a list which contains settings for each track:
```{r, eval = FALSE}
chordDiagram(mat, annotationTrack = NULL,
preAllocateTracks = list(list(track.height = 0.1),
list(bg.border = "black")))
```
By default `chordDiagram()` provides poor supports for customization of sector
labels and axes, but with `preAllocateTracks` it is rather easy to customize
them. Such customization will be introduced in next section.
## Customize sector labels
In `chordDiagram()`, there is no argument to control the style of sector
labels, but this can be done by first pre-allocating an empty track and
customizing the labels in it later. In the following example, one track is
firstly allocated and a Chord diagram is added without label track and axes.
Later, the first track is updated with adding labels with clockwise facings
(Figure \@ref(fig:chord-diagram-labels-show)).
```{r chord-diagram-labels-show, fig.cap = "Change label directions."}
chordDiagram(mat, grid.col = grid.col, annotationTrack = "grid",
preAllocateTracks = list(track.height = max(strwidth(unlist(dimnames(mat))))))
# we go back to the first track and customize sector labels
circos.track(track.index = 1, panel.fun = function(x, y) {
circos.text(CELL_META$xcenter, CELL_META$ylim[1], CELL_META$sector.index,
facing = "clockwise", niceFacing = TRUE, adj = c(0, 0.5))
}, bg.border = NA) # here set bg.border to NA is important
```
In the following example, the labels are put on the grids (Figure \@ref(fig:chord-diagram-labels-inside)).
Please note `circos.text()` and
`get.cell.meta.data()` can be used outside `panel.fun` if the sector index and
track index are specified explicitly.
```{r chord-diagram-labels-inside, fig.cap = "Put sector labels to the grid."}
chordDiagram(mat, grid.col = grid.col,
annotationTrack = c("grid", "axis"), annotationTrackHeight = mm_h(5))
for(si in get.all.sector.index()) {
xlim = get.cell.meta.data("xlim", sector.index = si, track.index = 1)
ylim = get.cell.meta.data("ylim", sector.index = si, track.index = 1)
circos.text(mean(xlim), mean(ylim), si, sector.index = si, track.index = 1,
facing = "bending.inside", niceFacing = TRUE, col = "white")
}
```
For the last example in this section, if the width of the sector is less than
10 degree, the labels are added in the radical direction (Figure \@ref(fig:chord-diagram-labels-multile-style)).
```{r chord-diagram-labels-multile-style, fig.cap = "Adjust label direction according to the width of sectors."}
set.seed(123)
mat2 = matrix(rnorm(100), 10)
chordDiagram(mat2, annotationTrack = "grid",
preAllocateTracks = list(track.height = max(strwidth(unlist(dimnames(mat))))))
circos.track(track.index = 1, panel.fun = function(x, y) {
xlim = get.cell.meta.data("xlim")
xplot = get.cell.meta.data("xplot")
ylim = get.cell.meta.data("ylim")
sector.name = get.cell.meta.data("sector.index")
if(abs(xplot[2] - xplot[1]) < 10) {
circos.text(mean(xlim), ylim[1], sector.name, facing = "clockwise",
niceFacing = TRUE, adj = c(0, 0.5), col = "red")
} else {
circos.text(mean(xlim), ylim[1], sector.name, facing = "inside",
niceFacing = TRUE, adj = c(0.5, 0), col= "blue")
}
}, bg.border = NA)
```
When you set direction of sector labels as radical (`clockwise` or
`reverse.clockwise`), if the labels are too long and exceed your figure
region, you can either decrease the size of the font or set `canvas.xlim` and
`canvas.ylim` parameters in `circos.par()` to wider intervals.
## Customize sector axes
Axes are helpful to correspond to the absolute values of links. By default
`chordDiagram()` adds axes on the grid track. But it is easy to customize one
with self-defined code.
In following example code, we draw another type of axes which show relative
percent on sectors. We first pre-allocate an empty track by
`preAllocateTracks` and come back to this track to add axes later.
You may see we add the first axes to the top of second track. You can also
put them to the bottom of the first track.
```{r chord_diagram_axes_two, eval = FALSE}
# similar as the previous example, but we only plot the grid track
chordDiagram(mat, grid.col = grid.col, annotationTrack = "grid",
preAllocateTracks = list(track.height = mm_h(5)))
for(si in get.all.sector.index()) {
circos.axis(h = "top", labels.cex = 0.3, sector.index = si, track.index = 2)
}
```
Now we go back to the first track to add the second type of axes and sector names.
In `panel.fun`, if the sector is less than 30 degree, the break for the axis is set to 0.5
(Figure \@ref(fig:chord-diagram-axes)).
```{r chord_diagram_axes_two2, eval = FALSE}
# the second axis as well as the sector labels are added in this track
circos.track(track.index = 1, panel.fun = function(x, y) {
xlim = get.cell.meta.data("xlim")
ylim = get.cell.meta.data("ylim")
sector.name = get.cell.meta.data("sector.index")
xplot = get.cell.meta.data("xplot")
circos.lines(xlim, c(mean(ylim), mean(ylim)), lty = 3) # dotted line
by = ifelse(abs(xplot[2] - xplot[1]) > 30, 0.2, 0.5)
for(p in seq(by, 1, by = by)) {
circos.text(p*(xlim[2] - xlim[1]) + xlim[1], mean(ylim) + 0.1,
paste0(p*100, "%"), cex = 0.3, adj = c(0.5, 0), niceFacing = TRUE)
}
circos.text(mean(xlim), 1, sector.name, niceFacing = TRUE, adj = c(0.5, 0))
}, bg.border = NA)
circos.clear()
```
```{r chord-diagram-axes, echo = FALSE, fig.width = 6, fig.height = 6, fig.cap = "Customize sector axes for Chord diagram."}
chunks <- knitr:::knit_code$get()
eval(parse(text = chunks[["chord_diagram_axes_two"]]))
eval(parse(text = chunks[["chord_diagram_axes_two2"]]))
```
## Put horizontally or vertically symmetric
In Chord diagram, when there are two groups (which correspond to rows and columns
if the input is an adjacency matrix), it is always visually beautiful to rotate the diagram
to be symmetric on horizontal direction or vertical direction. See following example:
```{r chord-diagram-sym, fig.width = 8, fig.height = 4, fig.cap = "Rotate Chord diagram."}
par(mfrow = c(1, 2))
circos.par(start.degree = 0)
chordDiagram(mat, grid.col = grid.col, big.gap = 20)
abline(h = 0, lty = 2, col = "#00000080")
circos.clear()
circos.par(start.degree = 90)
chordDiagram(mat, grid.col = grid.col, big.gap = 20)
abline(v = 0, lty = 2, col = "#00000080")
circos.clear()
```
## Compare two Chord diagrams
Normally, in Chord diagram, values in `mat` are normalized to the summation of
the absolute values in the matrix, which means the width for links only
represents relative values. Then, when comparing two Chord diagrams, it is
necessary that unit width of links in the two plots should be represented in a
same scale. This problem can be solved by adding larger gaps to the Chord
diagram which has smaller matrix.
First we make the "big" Chord diagram.
```{r chord_diagram_compare_1, eval = FALSE}
mat1 = matrix(sample(20, 25, replace = TRUE), 5)
chordDiagram(mat1, directional = 1, grid.col = rep(1:5, 2), transparency = 0.5,
big.gap = 10, small.gap = 1) # 10 and 1 are default values for the two arguments
```
The second matrix only has half the values in `mat1`.
```{r chord_diagram_compare_2, eval = FALSE}
mat2 = mat1 / 2
```
`calc_gap()` can be used to calculate the gap for the second Chord diagram
to make the scale of the two Chord diagram the same.
```{r chord_diagram_compare_3, eval = FALSE}
gap = calc_gap(mat1, mat2, big.gap = 10, small.gap = 1)
chordDiagram(mat2, directional = 1, grid.col = rep(1:5, 2), transparency = 0.5,
big.gap = gap, small.gap = 1)
```
Now the scale of the two Chord diagrams (Figure \@ref(fig:chord-diagram-compare)) are the
same if you compare the scale of axes in the two diagrams.
```{r chord-diagram-compare, echo = FALSE, fig.width = 8, fig.height = 4, fig.cap = "Compare two Chord Diagrams in a same scale."}
par(mfrow = c(1, 2))
eval(parse(text = chunks[["chord_diagram_compare_1"]]))
eval(parse(text = chunks[["chord_diagram_compare_2"]]))
eval(parse(text = chunks[["chord_diagram_compare_3"]]))
circos.clear()
```
To correctly use the functionality of `calc_gap()`, the two Chord diagram should
have same value for `small.gap` and there should be no overlap between the two
sets of the sectors.
## Multiple-group Chord diagram {#multiple-group-chord-diagram}
From verion 0.4.10 of the **circlize** package, there is a new `group`
argument in `chordDiagram()` function which is very convenient for making
multiple-group Chord diagrams.
I first generate a random matrix where there are three groups (`A`, `B`, and `C`).
Note this new functionality works the same for the input as a data frame.
```{r}
mat1 = matrix(rnorm(25), nrow = 5)
rownames(mat1) = paste0("A", 1:5)
colnames(mat1) = paste0("B", 1:5)
mat2 = matrix(rnorm(25), nrow = 5)
rownames(mat2) = paste0("A", 1:5)
colnames(mat2) = paste0("C", 1:5)
mat3 = matrix(rnorm(25), nrow = 5)
rownames(mat3) = paste0("B", 1:5)
colnames(mat3) = paste0("C", 1:5)
mat = matrix(0, nrow = 10, ncol = 10)
rownames(mat) = c(rownames(mat2), rownames(mat3))
colnames(mat) = c(colnames(mat1), colnames(mat2))
mat[rownames(mat1), colnames(mat1)] = mat1
mat[rownames(mat2), colnames(mat2)] = mat2
mat[rownames(mat3), colnames(mat3)] = mat3
mat
```
The main thing is to create "a grouping variable". The variable contains
the group labels and the sector names are used as the names in the vector.
```{r}
nm = unique(unlist(dimnames(mat)))
group = structure(gsub("\\d", "", nm), names = nm)
group
```
Assign `group` variable to the `group` argument:
```{r chord-grouped, fig.width = 8, fig.height = 4, fig.cap = "Grouped Chord diagram."}
grid.col = structure(c(rep(2, 5), rep(3, 5), rep(4, 5)),
names = c(paste0("A", 1:5), paste0("B", 1:5), paste0("C", 1:5)))
chordDiagram(mat, group = group, grid.col = grid.col)
circos.clear()
```
We can try another grouping:
```{r chord-grouped2, fig.width = 8, fig.height = 4, fig.cap = "Grouped Chord diagram. A different grouping."}
group = structure(gsub("^\\w", "", nm), names = nm)
group
chordDiagram(mat, group = group, grid.col = grid.col)
circos.clear()
```
The order of `group` controls the sector orders and if `group` is set as a factor,
the order of levels controls the order of groups.
```{r chord-grouped3, fig.width = 8, fig.height = 4, fig.cap = "Grouped Chord diagram. Control the orders of groups."}
group = structure(gsub("\\d", "", nm), names = nm)
group = factor(group[sample(length(group), length(group))], levels = c("C", "A", "B"))
group
chordDiagram(mat, group = group, grid.col = grid.col)
circos.clear()
```
The gap between groups is controlled by `big.gap` argument and the gap between
sectors is controlled by `small.gap` argument.
```{r chord-grouped4, fig.width = 8, fig.height = 4, fig.cap = "Grouped Chord diagram. Control the gaps between groups."}
group = structure(gsub("\\d", "", nm), names = nm)
chordDiagram(mat, group = group, grid.col = grid.col, big.gap = 20, small.gap = 5)
circos.clear()
```
As a normal Chord diagram, the labels and other tracks can be manually adjusted:
```{r chord-grouped5, fig.width = 8, fig.height = 4, fig.cap = "A more customized grouped Chord diagram."}
group = structure(gsub("\\d", "", nm), names = nm)
chordDiagram(mat, group = group, grid.col = grid.col,
annotationTrack = c("grid", "axis"),
preAllocateTracks = list(
track.height = mm_h(4),
track.margin = c(mm_h(4), 0)
))
circos.track(track.index = 2, panel.fun = function(x, y) {
sector.index = get.cell.meta.data("sector.index")
xlim = get.cell.meta.data("xlim")
ylim = get.cell.meta.data("ylim")
circos.text(mean(xlim), mean(ylim), sector.index, cex = 0.6, niceFacing = TRUE)
}, bg.border = NA)
highlight.sector(rownames(mat1), track.index = 1, col = "red",
text = "A", cex = 0.8, text.col = "white", niceFacing = TRUE)
highlight.sector(colnames(mat1), track.index = 1, col = "green",
text = "B", cex = 0.8, text.col = "white", niceFacing = TRUE)
highlight.sector(colnames(mat2), track.index = 1, col = "blue",
text = "C", cex = 0.8, text.col = "white", niceFacing = TRUE)
circos.clear()
```
The implementation of multi-group Chord diagram is very simple. It can also be done
by setting a proper sector order (by `order` argument) and a `gap.after` parameter.
The previous example can be implemented without the `group` argument by the following code,
but you need to manually calculate the proper value for `gap.after`. Setting `group`
argument automatically does this for you.
```{r, eval = FALSE}
circos.par(gap.after = c(rep(1, 4), 5, rep(1, 4), 5, rep(1, 4), 5))
chordDiagram(mat, order = names(sort(group)), grid.col = grid.col)
circos.clear()
```