forked from jokergoo/circlize_book
-
Notifications
You must be signed in to change notification settings - Fork 0
/
02-circlize-layout.Rmd
780 lines (657 loc) · 35.9 KB
/
02-circlize-layout.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
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
# Circular layout {#circular-layout}
## Coordinate transformation {#coordinate-transformation}
To map graphics onto the circle, there exist transformations among several
coordinate systems. First, there are **data coordinate systems** in which
ranges for x-axes and y-axes are the ranges of original data. Second, there is
a **polar coordinate system** in which these coordinates are mapped onto a
circle. Finally, there is a **canvas coordinate system** in which graphics are
really drawn on the graphic device (Figure \@ref(fig:coordinate-transformation)).
Each cell has its own data coordinate and they are
independent. **circlize** first transforms coordinates from data coordinate
system to polar coordinate system and finally transforms into canvas
coordinate system. For users, they only need to imagine that each cell is a
normal rectangular plotting region (data coordinate) in which x-lim and y-lim
are ranges of data in that cell. **circlize** knows which cell you are in and
does all the transformations automatically.
```{r coordinate-transformation, echo = FALSE, fig.width = 8, fig.height = 4, fig.cap = "Transformation between different coordinates"}
source("src/intro-03-transformation.R")
```
The final canvas coordinate is in fact an ordinary coordinate in the base R
graphic system with x-range in `(-1, 1)` and y-range in `(-1, 1)` by default.
It should be noted that **the circular plot is always drawn inside the circle which has
radius of 1 (which means it is always a unit circle), and from outside to
inside**.
## Rules for making the circular plot {#rules-for-making-the-circular-plot}
The rule for making the circular plot is rather simple. It follows the sequence of
`initialize layout -> create track -> add graphics -> create track -> add graphics - ... -> clear`.
Graphics can be added at any time as long as the tracks are created.
Details are shown in Figure \@ref(fig:circlize-order) and as follows:
```{r circlize-order, echo = FALSE, fig.width = 8, fig.height = 7, fig.cap = "Order of drawing circular layout."}
source("src/intro-02-order.R")
```
1. Initialize the layout using `circos.initialize()`. Since circular layout in
fact visualizes data which is in categories, there must be at least a
categorical variable. Ranges of x values on each category can be specified
as a vector or the range itself. See Section \@ref(sectors-and-tracks).
2. Create plotting regions for the new track and add graphics. The new track
is created just inside the previously created one. Only after the creation
of the track can you add other graphics on it. There are three ways to add
graphics in cells.
- After the creation of the track, use low-level graphic function like
`circos.points()`, `circos.lines()`, ... to add graphics cell by
cell. It always involves a `for` loop and you need to subset the data
by the categorical variable manually.
- Use `circos.trackPoints()`, `circos.trackLines()`, ... to add
simple graphics through all cells simultaneously.
- Use `panel.fun` argument in `circos.track()` to add graphics
immediately after the creation of a certain cell. `panel.fun` needs
two arguments `x` and `y` which are x values and y values that are in
the current cell. This subset operation is applied automatically.
This is the most recommended way. Section \@ref(panel-fun) gives
detailed explanation of using `panel.fun` argument.
3. Repeat step 2 to add more tracks on the circle unless it reaches the center
of the circle.
4. Call `circos.clear()` to clean up.
As mentioned above, there are three ways to add graphics on a track.
1. Create plotting regions for the whole track first and then add graphics
by specifying `sector.index`. In the following pseudo code, `x1`, `y1`
are data points in a given cell, which means you need to do data
subsetting manually.
In following code, `circos.points()` and `circos.lines()` are used
separatedly from `circos.track()`, thus, the index for the sector needs
to be explicitly specified by `sector.index` argument. There is also a
`track.index` argument for both functions, however, the default value is
the "current" track index and as the two functions are used just after
`circos.track()`, the "current" track index is what the two functions
expect and it can be ommited when calling the two functions.
```{r, eval = FALSE}
circos.initialize(sectors, xlim)
circos.track(ylim)
for(sector.index in all.sector.index) {
circos.points(x1, y1, sector.index)
circos.lines(x2, y2, sector.index)
}
```
2. Add graphics in a batch mode. In following code, `circos.trackPoints()`
and `circos.trackLines()` need a categorical variable, a vector of x
values and a vector of y values. X and y values will be split by the
categorical variable and sent to corresponding cell to add the graphics.
Internally, this is done by using `circos.points()` or `circos.lines()`
in a `for` loop. This way to add graphics would be convenient if users
only want to add a specific type of simple graphics (e.g. only points) to
the track, but it is not recommended for making complex graphics.
`circos.trackPoints()` and `circos.trackLines()` need a `track.index` to
specify which track to add the graphics. Similarly, since these two are
called just after `circos.track()`, the graphics are added in the newly
created track right away.
```{r, eval = FALSE}
circos.initialize(sectors, xlim)
circos.track(ylim)
circos.trackPoints(sectors, x, y)
circos.trackLines(sectors, x, y)
```
3. Use a panel function to add self-defined graphics as soon as the cell has
been created. This is the way recommended and you can find most of the
code in this book uses `panel.fun`. `circos.track()` creates cells one by
one and after the creation of a cell, and `panel.fun` is executed on this
cell immediately. In this case, the "current" sector and "current" track
are marked to this cell that you can directly use low-level functions
without specifying sector index and track index.
If you look at following code, you will find the code inside `panel.fun`
is as natural as using `points()` or `lines()` in the normal R graphic
system. This is a way to help you think a cell is an "imaginary
rectangular plotting region".
```{r, eval = FALSE}
circos.initialize(sectors, xlim)
circos.track(sectors, all_x, all_y, ylim,
panel.fun = function(x, y) {
circos.points(x, y)
circos.lines(x, y)
})
```
There are several internal variables keeping tracing of the current sector and
track when applying `circos.track()` and `circos.update()`. Thus, although
functions like `circos.points()`, `circos.lines()` need to specify the index
of sector and track, they will take the current one by default. As a result,
if you draw points, lines, text _et al_ just after the creation of the track
or cell, you do not need to set the sector index and the track index
explicitly and it will be added in the most recently created or updated cell.
## Sectors and tracks {#sectors-and-tracks}
A circular layout is composed of sectors and tracks. As illustrated in Figure
\@ref(fig:circlize-coordinate), the red circle is one track and the blue represents
one sector. The intersection of a sector and a track is called a cell which
can be thought as an imaginary plotting region for data points. In this
section, we introduce how to set data ranges on x and y directions in cells.
```{r circlize-coordinate, echo = FALSE, fig.width = 4, fig.height = 4, fig.cap = "Sectors and tracks in circular layout."}
source("src/intro-04-coordinate.R")
```
Sectors are first allocated on the circle by `circos.initialize()`. There must
be a categorical variable (say `sectors`) that on the circle, each sector
corresponds to one category. The width of sectors (measured by degree) are proportional to the data
range in sectors on x direction (or the circular direction). The data range can be specified as a numeric
vector `x` which has same length as `sectors`, then `x` is split by `sectors`
and data ranges are calculated for each sector internally.
Data ranges can also be specified directly by `xlim` argument. The valid value
for `xilm` is a two-column matrix with same number of rows as number of
sectors that each row in `xlim` corresponds to one sector. If `xlim` has row
names which already cover sector names, row order of `xlim` is automatically
adjusted. If `xlim` is a vector of length two, all sectors have the same x range.
```{r, eval = FALSE}
circos.initialize(sectors, x = x)
circos.initialize(sectors, xlim = xlim)
```
If `sectors` is not specified, the row names of `xlim` are taken as the value for `sectors`.
```{r, eval = FALSE}
circos.initialize(xlim = xlim)
```
After the initialization of the layout, you may not see anything drawn or only an
empty graphical device is opened. That is because no track has been created
yet, however, the layout has already been recorded internally.
In the initialization step, not only the width of each sector is assigned, but
also the order of sectors on the circle is determined. **Order of the sectors
are determined by the order of levels of the input factor**. If the value for
`sectors` is not a factor, the order of sectors is `unique(sectors)`. Thus, if
you want to change the order of sectors, you can just change of the level of
`sectors` variable. The following code generates plots with different
sector orders (Figure \@ref(fig:circlize-factor)).
```{r, eval = FALSE}
sectors = c("d", "f", "e", "c", "g", "b", "a")
s1 = factor(sectors)
circos.initialize(s1, xlim = c(0, 1))
s2 = factor(sectors, levels = sectors)
circos.initialize(s2, xlim = c(0, 1))
```
```{r circlize-factor, echo = FALSE, fig.width = 8, fig.height = 4, fig.cap = "Different sector orders."}
source("src/intro-05-factor.R")
```
**In different tracks, cells in the same sector share the same data range on
x-axes.** Then, for each track, we only need to specify the data range on y
direction (or the radical direction) for cells. Similar as `circos.initialize()`, `circos.track()` also
receives either `y` or `ylim` argument to specify the range of y-values. Since
all cells in a same track share a same y range, `ylim` is just a vector of
length two if it is specified.
`x` can also be specified in `circos.track()`, but it is only used to send to
`panel.fun`. In Section \@ref(panel-fun), we will introduce how `x` and `y`
are sent to each cell and how the graphics are added.
```{r, eval = FALSE}
circos.track(sectors, y = y)
circos.track(sectors, ylim = c(0, 1))
circos.track(sectors, x = x, y = y)
```
In the track creation step, since all sectors have already been allocated in
the circle, if `sectors` argument is not set, `circos.track()` would create
plotting regions for all available sectors. Also, levels of `sectors` do
not need to be specified explicitly because the order of sectors has already
be determined in the initialization step. If users only create cells for a subset
of sectors in the track (not all sectors), in fact, cells in remaining
unspecified sectors are created as well, but with no borders (pretending they
are not created).
```{r, eval = FALSE}
# assume `sectors` only covers a subset of sectors
# You will only see cells that are covered in `sectors` have borders
circos.track(sectors, y = y)
# You will see all cells have borders
circos.track(ylim = c(0, 1))
circos.track(ylim = ranges(y))
```
Cells are basic units in the circular plot and are independent from each
other. After the creation of cells, they have self-contained meta values of
x-lim and y-lim (data range measured in data coordinate). So if you are adding
graphics in one cell, you do not need to consider things outside the cell and
also you do not need to consider you are in the circle. Just pretending it is
normal rectangle region with its own coordinate.
```{r circlize-direction, echo = FALSE, fig.width = 8, fig.height = 4, fig.cap = "Sector directions."}
source("src/intro-07-direction.R")
```
```{r circlize-axis-direction, echo = FALSE, fig.width = 8, fig.height = 4, fig.cap = "Axes directions."}
source("src/intro-07-axis-direction.R")
```
## Graphic parameters {#graphic-parameters}
Some basic parameters for the circular layout can be set by `circos.par()`.
These parameters are listed as follows. Note some parameters can only be
modified before the initialization of the circular layout.
- `start.degree`: The starting degree where the first sector is put. Note this
degree is measured in the standard polar coordinate system which means it is always
reverse clockwise. E.g. if it is set to 90, sectors start from the top
center of the circle. See Figure \@ref(fig:circlize-direction).
- `gap.degree`: Gap between two neighbour sectors. It can be a single value
which means all gaps share same degree, or a vector which has same number as
sectors. **Note the first gap is after the first sector.** See Figure
\@ref(fig:circlize-direction) and Figure \@ref(fig:circlize-region).
- `gap.after`: Same as `gap.degree`, but more understandable. Modifying values
of `gap.after` will also modify `gap.degree` and vice versa.
- `track.margin`: [Like `margin` in Cascading Style Sheets (CSS)](https://www.w3schools.com/css/css_margin.asp), it is the
blank area out of the plotting region, also outside of the borders. Since
left and right margin are controlled by `gap.after`, only bottom and top
margin need to be set. The value for `track.margin` is the percentage to the
radius of the unit circle. The value can also be set by `mm_h()`/`cm_h()`/`inches_h()` functions with absolute units. See Figure
\@ref(fig:circlize-region).
- `cell.padding`: Padding of the cell. [Like `padding` in Cascading Style
Sheets (CSS)](https://www.w3schools.com/css/css_padding.asp), it is the blank area around the plotting regions, but within
the borders. The parameter has four values, which control the bottom, left,
top and right padding respectively. The first and the third padding values
are the percentages to the radius of the unit circle, and the second and
fourth values are the degrees. The first and the third value can be set by
`mm_h()`/`cm_h()`/`inches_h()` with absolute units. See Figure \@ref(fig:circlize-region).
- `unit.circle.segments`: Since curves are simulated by a series of straight
lines, this parameter controls the amount of segments to represent a curve.
The minimal length of the line segment is the length of the unit circle
($2\pi$) divided by `unit.circle.segments`. More segments means better
approximation for the curves, while generate larger file size if figures are
in PDF format. See explanantion in Section \@ref(lines).
- `track.height`: The default height of tracks. It is the percentage to the
radius of the unit circle. The height includes the top and bottom cell
paddings but not the margins. The value can be set by
`mm_h()`/`cm_h()`/`inches_h()` with absolute units.
- `points.overflow.warning`: Since each cell is in fact not a real plotting
region but only an ordinary rectangle (or more precisely, a circular
rectangle), it does not remove points that are plotted outside of the
region. So if some points (or lines, text) are out of the plotting region,
by default, the package would continue drawing the points but with warning
messages. However, in some circumstances, drawing something out of the
plotting region is useful, such as adding some text annotations (like the
first track in Figure \@ref(fig:circlize-glance-track-1)). Set this value to
`FALSE` to turn off the warnings.
- `canvas.xlim`: The ranges in the canvas coordinate in x direction. **circlize** is
forced to put everything inside the unit circle, so `canvas.xlim` and `canvas.ylim`
is `c(-1, 1)` by default. However, you can set it to a more
broad interval if you want to leave more spaces out of the circle. By
choosing proper `canvas.xlim` and `canvas.ylim`, actually you can customize
the circle. E.g. setting `canvas.xlim` to `c(0, 1)` and `canvas.ylim` to
`c(0, 1)` would only draw 1/4 of the circle.
- `canvas.ylim`: The ranges in the canvas coordinate in y direction.
- `circle.margin`: Margin in the horizontal and vertical direction. The value
should be a positive numeric vector and the length of it should be either 1,
2, or 4. When it has length of 1, it controls the margin on the four sides
of the circle. When it has length of 2, the first value controls the margin
on the left and right, and the second value controls the margin on the
bottom and top side. When it has length of 4, the four values controls the
margins on the left, right, bottom and top sides of the circle. So A value
of `c(x1, x2, y1, y2)` means `circos.par(canvas.xlim = c(-(1+x1), 1+x2),
canvas.ylim = c(-(1+y1), 1+y2))`.
- `clock.wise`: The order for drawing sectors. Default is `TRUE` which means
clockwise (Figure \@ref(fig:circlize-direction).
- `xaxis.clock.wise`: The direction of x-axis in each cell. By default it is clockwise.
Note this new parameter was only available from version 0.4.11 (Figure \@ref(fig:circlize-axis-direction)).
Also see more details [on this blog post](https://jokergoo.github.io/2020/08/17/reverse-x-axes-in-the-circular-plot/).
```{r circlize-region, echo = FALSE, fig.width = 4, fig.height = 4, fig.cap = "Regions in a cell."}
source("src/intro-06-region.R")
```
Default values for graphic parameters are listed in following table.
-------------------------- ----------------------------
`start.degree` `0`
`gap.degree`/`gap.after` `1`
`track.margin` `c(0.01, 0.01)`
`cell.padding` `c(0.02, 1.00, 0.02, 1.00)`
`unit.circle.segments` `500`
`track.height` `0.2`
`points.overflow.warning` `TRUE`
`canvas.xlim` `c(-1, 1)`
`canvas.ylim` `c(-1, 1)`
`circle.margin` `c(0, 0, 0, 0)`
`clock.wise` `TRUE`
`xaxis.clock.wise` `TRUE`
-------------------------- ---------------------------
Parameters related to the allocation of sectors cannot be changed after the
initialization of the circular layout. Thus, `start.degree`,
`gap.degree`/`gap.after`, `canvas.xlim`, `canvas.ylim`, `circle.margin`, `clock.wise` and `xaxis.clock.wise` can
only be modified before `circos.initialize()`. The second and the fourth
values of `cell.padding` (left and right paddings) can not be modified neither
(or will be ignored).
Similar reason, since some of the parameters are defined before the initialization
of the circular layout, after making each plot, you need to call `circos.clear()`
to manually reset all the parameters.
`circos.par()` can be used in the same way as `options()`:
```{r, eval = FALSE}
circos.par("start.degree" = 30)
```
Or use the `$` symbol:
```{r, eval = FALSE}
circos.par$start.degree = 30
```
Use `RESET` to reset all the options to their default values:
```{r, eval = FALSE}
circos.par(RESET = TRUE)
```
Simply enter `circos.par` prints the current values of all parameters:
```{r}
circos.par
```
`circos.par()` is aimed to be designed to be independent to the number or the
order of sectors, however, there is an exception. The `gap.degree`/`gap.after` parameter
controls the spaces between two neighbouring sectors. When it is set as a
scalar, the gap is the same to every two neighbouring sectors and it works
fine. It can also be a vector which has the same length as the number of
sectors and it can cause problems:
1. When you change the order of the sectors, you also need to manually change
the order of `gap.degree`/`gap.after`.
2. In `chordDiagram()` function which will be introduced in Section \@ref(the-chorddiagram-function), by
default the tiny sectors are removed to improve the visualization, which
means, the gap.degree should be adjusted.
3. Also in `chordDiagram()`,
sometimes it is not very straightforward to find out the order of sectors,
thus, it is difficult to set a proper `gap.degree`/`gap.after`.
Now, from version 0.4.10, the value of `gap.degree`/`gap.after` can be set as a
named vector where the names are the names of the sectors. In this case, the
`gap.degree`/`gap.after` vector can be automatically reordered or subsetted according to the
sector ordering in the plot.
## Create plotting regions {#create-plotting-regions}
As described above, only after creating the plotting region can you add low-level
graphics in it. The minimal set of arguments for `circos.track()` is to set
either `y` or `ylim` which assigns range of y values for this track.
`circos.track()` creates tracks for all sectors although in some case only
parts of them are visible.
If `sectors` is not specified, all cells in the track will be created with the
same settings. If `sectors`, `x` and `y` are set, they need to be vectors with
the same length. Proper values of `x` and `y` that correspond to current cell
will be passed to `panel.fun` by subsetting `sectors` internally. Section
\@ref(panel-fun) explains the usage of `panel.fun`.
Graphic arguments such as `bg.border` and `bg.col` can either be a scalar or a
vector. If it is a vector, the length must be equal to the number of sectors and the order
corresponds to the order of sectors.
Thus, you can create plot regions with different styles of borders and
background colors.
If you are confused with the `sectors` orders, you can also customize the
borders and background colors inside `panel.fun`.
`get.cell.meta.data("cell.xlim")`/`CELL_META$cell.xlim` and
`get.cell.meta.data("cell.ylim")`/`CELL_META$cell.ylim` give you dimensions of
the plotting region and you can customize plot regions directly by e.g.
```{r, eval = FALSE}
circos.rect(CELL_META$cell.xlim[1], CELL_META$cell.ylim[1],
CELL_META$cell.xlim[2], CELL_META$cell.ylim[2],
col = "#FF000040", border = 1)
```
`circos.track()` provides `track.margin` and `cell.padding` arguments that
they only control track margins and cell paddings for the current track. Of course
the second and fourth value (the left and the right padding) in `cell.padding` are ignored.
## Update plotting regions {#update-plotting-regions}
`circos.track()` creates new tracks, however, if `track.index` argument is set
to a track which already exists, `circos.track()` actually **re-creates** this
track. In this case, coordinates on y directions can be re-defined, but
settings related to the positions of the track such as the height of the track
can not be modified.
```{r, eval = FALSE}
circos.track(sectors, ylim = c(0, 1), track.index = 1, ...)
```
For a single cell, `circos.update()` can be used to erase all graphics that
have been already added in the cell. However, the data coordinate in the cell
keeps unchanged.
```{r, eval = FALSE}
circos.update(sector.index, track.index)
circos.points(x, y, sector.index, track.index)
```
## `panel.fun` argument {#panel-fun}
The `panel.fun` argument in `circos.track()` is extremely useful to apply plotting
as soon as the cell has been created. This self-defined function needs two
arguments `x` and `y` which are data points that belong to this cell. The
value for `x` and `y` are automatically extracted from `x` and `y` in
`circos.track()` according to the category defined in `sectors`. In the
following example, inside `panel.fun`, in sector `a`, the value of `x` is
`1:3` and in sector `b`, value of `x` is `4:5`. If `x` or `y` in
`circos.track()` is `NULL`, then `x` or `y` inside `panel.fun` is also `NULL`.
```{r, eval = FALSE}
sectors = c("a", "a", "a", "b", "b")
x = 1:5
y = 5:1
circos.track(sectors, x = x, y = y,
panel.fun = function(x, y) {
circos.points(x, y)
})
```
Since you can obtain the current sector index in `panel.fun`, you can ignore the values
from the `x` and `y` arguments, while do subsetting by your own:
```{r, eval = FALSE}
sectors = c("a", "a", "a", "b", "b")
x2 = 1:5
y2 = 5:1
circos.track(ylim = range(y),
panel.fun = function(x, y) { # here x and y are useless
l = sectors == CELL_META$sector.index
circos.points(x2[l], y2[l])
})
```
In `panel.fun`, one thing important is that if you use any low-level graphic
functions, you don't need to specify `sector.index` and `track.index`
explicitly. Remember that when applying `circos.track()`, cells in the track
are created one after one. When a cell is created, **circlize** would set the
sector index and track index of the cell as the 'current' index. When the cell
is created, `panel.fun` is executed immediately. Without specifying
`sector.index` and `track.index`, the 'current' ones are used and that's
exactly what you need.
The advantage of `panel.fun` is that it makes you feel you are using graphic
functions in the base graphic engine (You can see it is almost the same of
using `circos.points(x, y)` and `points(x, y)`). It will be much easier for
users to understand and customize new graphics.
Inside `panel.fun`, information of the 'current' cell can be obtained through
`get.cell.meta.data()`. Also this function takes the 'current' sector and
'current' track by default.
```{r, eval = FALSE}
get.cell.meta.data(name)
get.cell.meta.data(name, sector.index, track.index)
```
Information that can be extracted by `get.cell.meta.data()` are:
- `sector.index`: The name for the sector.
- `sector.numeric.index`: Numeric index for the sector.
- `track.index`: Numeric index for the track.
- `xlim`: Minimal and maximal values on the x-axis.
- `ylim`: Minimal and maximal values on the y-axis.
- `xcenter`: mean of `xlim`.
- `ycenter`: mean of `ylim`.
- `xrange`: defined as `xlim[2] - xlim[1]`.
- `yrange`: defined as `ylim[2] - ylim[1]`.
- `cell.xlim`: Minimal and maximal values on the x-axis extended by cell
paddings.
- `cell.ylim`: Minimal and maximal values on the y-axis extended by cell
paddings.
- `xplot`: Degree of right and left borders in the plotting region. The values ignore the direction of the circular layout (i.e. whether it is clock wise or not). `xplot[1]` is always upstream of `xplot[2]` in the clock-wise direction.
- `yplot`: Radius of bottom and top radius in the plotting region.
- `cell.width`: Width of the cell, measured in degree. It is calculated as `(xplot[1] - xplot[2]) %% 360`.
- `cell.height`: Height of the cell. Simply `yplot[2] - yplot[1]`.
- `cell.start.degree`: Same as `xplot[1]`.
- `cell.end.degree`: Same as `xplot[2]`.
- `cell.bottom.radius`: Same as `yplot[1]`.
- `cell.top.radius`: Same as `yplot[2]`.
- `track.margin`: Margins of the cell.
- `cell.padding`: Paddings of the cell.
Following example code uses `get.cell.meta.data()` to add sector index in the
center of each cell.
```{r, eval = FALSE}
circos.track(ylim = ylim, panel.fun = function(x, y) {
sector.index = get.cell.meta.data("sector.index")
xcenter = get.cell.meta.data("xcenter")
ycenter = get.cell.meta.data("ycenter")
circos.text(xcenter, ycenter, sector.index)
})
```
`get.cell.meta.data()` can also be used outside `panel.fun`, but you need to
explictly specify `sector.index` and `track.index` arguments unless the current
index is what you want.
There is a companion variable `CELL_META` which is identical to
`get.cell.meta.data()` to get cell meta information, but easier and shorter to
write. Actually, the value of `CELL_META` itself is meaningless, but e.g.
`CELL_META$sector.index` is automatically redirected to
`get.cell.meta.data("sector.index")`. Following code rewrites previous example
code with `CELL_META`.
```{r, eval = FALSE}
circos.track(ylim = ylim, panel.fun = function(x, y) {
circos.text(CELL_META$xcenter, CELL_META$ycenter,
CELL_META$sector.index)
})
```
Please note `CELL_META` only extracts information for the "current" cell, thus,
it is recommended to use only in `panel.fun`.
Nevertheless, if you have several lines of code which need to be executed out of `panel.fun`,
you can flag the specified cell as the "current" cell by `set.current.cell()`, which can save you from typing
too many `sector.index = ..., track.index = ...`. E.g. following code
```{r, eval = FALSE}
circos.text(get.cell.meta.data("xcenter", sector.index, track.index),
get.cell.meta.data("ycenter", sector.index, track.index),
get.cell.meta.data("sector.index", sector.index, track.index),
sector.index, track.index)
```
can be simplified to:
```{r, eval = FALSE}
set.current.cell(sector.index, track.index)
circos.text(get.cell.meta.data("xcenter"),
get.cell.meta.data("ycenter"),
get.cell.meta.data("sector.index"))
# or even simpler
circos.text(CELL_META$xcenter, CELL_META$ycenter, CELL_META$sector.index)
```
## Other utilities {#other-utilities}
### `circlize()` and `reverse.circlize()` {#circlize_and_reverse_circlize}
**circlize** transforms data points in several coordinate systems and it is
basically done by the core function `circlize()`. The function transforms from data
coordinate (coordinate in the cell) to the polar coordinate and its companion
`reverse.circlize()` transforms from polar coordinate to a specified data coordinate. The
default transformation is applied in the `current` cell.
```{r, echo = 3:10}
library(circlize)
pdf(NULL)
sectors = c("a", "b")
circos.initialize(sectors, xlim = c(0, 1))
circos.track(ylim = c(0, 1))
# x = 0.5, y = 0.5 in sector a and track 1
circlize(0.5, 0.5, sector.index = "a", track.index = 1)
# theta = 90, rou = 0.9 in the polar coordinate
reverse.circlize(90, 0.9, sector.index = "a", track.index = 1)
reverse.circlize(90, 0.9, sector.index = "b", track.index = 1)
circos.clear()
invisible(dev.off())
```
You can see the results are different for two `reverse.circlize()`
although it is the same points in the polar coordinate, because they are
mapped to different cells.
`circlize()` and `reverse.circlize()` can be used to connect two circular
plots if they are drawn on a same page. This provides a way to build more
complex plots. Basically, the two circular plots share a same polar
coordiante, then, the manipulation of `circlize->reverse.circlize->circlize`
can transform coordinate for data points from the first circular plot to the
second. In Chapter \@ref(nested-zooming), we use this technique to combine two
circular plots where one zooms subset of regions in the other one.
The transformation between polar coordinate and canvas coordinate is simple.
**circlize** has a `circlize:::polar2Cartesian()` function but this function
is not exported.
Following example (Figure \@ref(fig:circular-pokemon)) adds raster image to the circular plot. The raster image is added
by `rasterImage()` which is applied in the canvas coordinate. Note how we change
coordinate from data coordinate to canvas coordinate by using `circlize()`
and `circlize:::polar2Cartesian()`.
```{r circular-pokemon, warning = FALSE, fig.width = 6, fig.height = 6, dev = "png", fig.cap = "Add raster image to the circular plot."}
library(yaml)
data = yaml.load_file("https://raw.githubusercontent.com/Templarian/slack-emoji-pokemon/master/pokemon.yaml")
set.seed(123)
pokemon_list = data$emojis[sample(length(data$emojis), 40)]
pokemon_name = sapply(pokemon_list, function(x) x$name)
pokemon_src = sapply(pokemon_list, function(x) x$src)
library(EBImage)
circos.par("points.overflow.warning" = FALSE)
circos.initialize(pokemon_name, xlim = c(0, 1))
circos.track(ylim = c(0, 1), panel.fun = function(x, y) {
pos = circlize:::polar2Cartesian(circlize(CELL_META$xcenter, CELL_META$ycenter))
image = EBImage::readImage(pokemon_src[CELL_META$sector.numeric.index])
circos.text(CELL_META$xcenter, CELL_META$cell.ylim[1] - mm_y(2),
CELL_META$sector.index, facing = "clockwise", niceFacing = TRUE,
adj = c(1, 0.5), cex = 0.6)
rasterImage(image,
xleft = pos[1, 1] - 0.05, ybottom = pos[1, 2] - 0.05,
xright = pos[1, 1] + 0.05, ytop = pos[1, 2]+ 0.05)
}, bg.border = 1, track.height = 0.15)
circos.clear()
```
In **circlize** package, there is a `circos.raster()` function which directly
adds raster images. It is introduced in Section \@ref(raster-image).
### Absolute units {#convert-functions}
For the functions in **circlize** package, they needs arguments which are
lengths measured either in the canvas coordinate or in the data coordinate.
E.g. `track.height` argument in `circos.track()` corresponds to percent of
radius of the unit circle. **circlize** package is built in the R base graphic
system which is not straightforward to define a length with absolute units
(e.g. a line of length 2 cm). To solve this problem, **circlize** provides
several functions which convert absolute units to the canvas coordinate or the
data coordinate accordingly.
`mm_h()`, `cm_h()`, `inches_h()`/`inch_h()` functions convert absolute units
to the canvas coordinate in the radical direction, which normally define the
heights of e.g. tracks. If users want to convert a string height or width to
the canvas coordinate, directly use `strheight()` or `strwidth()` functions.
```{r, eval = FALSE}
mm_h(2) # 2mm
cm_h(1) # 1cm
```
`mm_x()`, `cm_x()`, `inches_x()`/`inch_x()`, `mm_y()`, `cm_y()`,
`inches_y()`/`inch_y()` convert absolute units to the data coordinate in the x
or y direction. By default, the conversion is applied in the "current" cell,
but it can still be used in other cells by specifying `sector.index` and
`track.index` arguments.
```{r, eval = FALSE}
mm_x(2)
mm_x(1, sector.index, track.index)
mm_y(2)
mm_y(1, sector.index, track.index)
```
Since the width of the cell is not identical from the top to the bottom in the
cell, for `*_x()` function, the position on y direction where the convert is
applied can be specified by the `h` argument (measured in data coordinate). By
default it is converted at the middle point on y-axis.
```{r, eval = FALSE}
mm_x(2, h)
```
Normally the difference of e.g. `mm_x(2)` at different y positions in a track
is very small (unless the track has very big `track.height`), so in most
cases, you can ignore the setting of `h` argument.
Following plot (Figure \@ref(fig:unit-convert)) is an example of setting absolute units.
```{r unit-convert, fig.width = 6, fig.height = 6, fig.cap = "Setting absolute units"}
sectors = letters[1:10]
circos.par(cell.padding = c(0, 0, 0, 0), track.margin = c(0, 0))
circos.initialize(sectors, xlim = cbind(rep(0, 10), runif(10, 0.5, 1.5)))
circos.track(ylim = c(0, 1), track.height = mm_h(5),
panel.fun = function(x, y) {
circos.lines(c(0, 0 + mm_x(5)), c(0.5, 0.5), col = "blue")
})
circos.track(ylim = c(0, 1), track.height = cm_h(1),
track.margin = c(0, mm_h(2)),
panel.fun = function(x, y) {
xcenter = get.cell.meta.data("xcenter")
circos.lines(c(xcenter, xcenter), c(0, cm_y(1)), col = "red")
})
circos.track(ylim = c(0, 1), track.height = inch_h(1),
track.margin = c(0, mm_h(5)),
panel.fun = function(x, y) {
line_length_on_x = cm_x(1*sqrt(2)/2)
line_length_on_y = cm_y(1*sqrt(2)/2)
circos.lines(c(0, line_length_on_x), c(0, line_length_on_y), col = "orange")
})
circos.clear()
```
### `circos.info()` and `circos.clear()` {#circos-info-and-circos-clear}
You can get basic information of your current circular plot by
`circos.info()`. The function can be called at any time.
```{r, echo = 2:7}
pdf(NULL)
sectors = letters[1:3]
circos.initialize(sectors, xlim = c(1, 2))
circos.info()
circos.track(ylim = c(0, 1))
circos.info(sector.index = "a", track.index = 1)
circos.clear()
invisible(dev.off())
```
It can also add labels to all cells by `circos.info(plot = TRUE)`.
You should always call `circos.clear()` at the end of every circular plot.
There are several parameters for circular plot which can only be set before
`circos.initialize()`, thus, before you draw the next circular plot, you need
to reset all these parameters.
## Set gaps between tracks
In the older versions, you need to set `track.height` parameter either in
`circos.par()` or in `circos.track()` to control the space between tracks. Now
there is a new `set_track_gap()` function which simplifies the setting of gaps
between two tracks. With the `mm_h()`/`cm_h()`/`inch_h()` functions, it is very easy
to set the gaps with physical units (Figure \@ref(fig:set-track-gap)).
```{r set-track-gap, fig.width = 6, fig.height = 6, fig.cap = "Setting gaps between tracks."}
circos.initialize(letters[1:10], xlim = c(0, 1))
circos.track(ylim = c(0, 1))
set_track_gap(mm_h(2)) # 2mm
circos.track(ylim = c(0, 1))
set_track_gap(cm_h(0.5)) # 0.5cm
circos.track(ylim = c(0, 1))
circos.clear()
```