Home | Blog | Software | Publications | GitHub
circlize package 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 package, by using functionality from other R packages, it is just a few more work to really implement it. In this post, I will demonstrate how to customize legends and arrange to the circular plot.
As an example, I generated a circular plot which contains two tracks and links inside the circle. 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.
library(circlize)
set.seed(123)
col_fun = colorRamp2(c(-1, 0, 1), c("green", "yellow", "red"))
circlize_plot = function() {
circos.initializeWithIdeogram(plotType = NULL)
bed = generateRandomBed(nr = 300)
bed = generateRandomBed(nr = 300, nc = 2)
circos.genomicTrackPlotRegion(bed,
panel.fun = function(region, value, ...) {
circos.genomicPoints(region, value, cex = 0.5, pch = 16, col = 2:3, ...)
})
bed = generateRandomBed(nr = 500, nc = 2)
circos.genomicTrackPlotRegion(bed,
panel.fun = function(region, value, ...) {
circos.genomicLines(region, value, col = 4:5, ...)
})
bed1 = generateRandomBed(nr = 100)
bed1 = bed1[sample(nrow(bed1), 20), ]
bed2 = generateRandomBed(nr = 100)
bed2 = bed2[sample(nrow(bed2), 20), ]
circos.genomicLink(bed1, bed2, col = col_fun(bed1[[4]]))
circos.clear()
}
In recently version of ComplexHeatmap package, 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 page.
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(-1, -0.5, 0, 0.5, 1), col_fun = col_fun, title_position = "topleft",
title = "Links")
lgd_list_vertical = packLegend(lgd_points, lgd_lines, lgd_links)
lgd_list_vertical
## frame[GRID.frame.65]
lgd_points
, lgd_lines
, lgd_links
and lgd_list_vertical
are all grob
objects (graphical objects),
which you can think as boxes which contain all graphical elements for legends and they can be added
to the plot by grid.draw()
.
circlize is implemented by the base graphic system while ComplexHeatmap is implemented by grid graphic system. However, these two system 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 devide.)
circlize_plot()
pushViewport(viewport(x = unit(2, "mm"), y = unit(4, "mm"), width = grobWidth(lgd_list_vertical),
height = grobHeight(lgd_list_vertical), just = c("left", "bottom")))
grid.draw(lgd_list_vertical)
upViewport()
In above plot, the whole image region corresponds to the circular plot and the legend layer is drawn just on top of it. If the legends are many and the size for the legends is too big, they may overap to the circle. In this case, it is better to split the image region into two parts where one part for the circular plot and the other part for legends.
Thanks to grid package that it is quite easy to split the image region and thanks to gridBase package that we can easily mix base graphics and grid graphics.
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.
library(gridBase)
plot.new()
circle_size = unit(1, "snpc") - unit(1, "inches")
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()
pushViewport(viewport(x = circle_size, y = 0.5, width = grobWidth(lgd_list_vertical),
height = grobHeight(lgd_list_vertical), just = c("left", "center")))
grid.draw(lgd_list_vertical)
upViewport()
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.
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(-1, -0.5, 0, 0.5, 1), col_fun = col_fun, title_position = "topleft",
title = "Links", direction = "horizontal")
lgd_list_horizontal = packLegend(lgd_points, lgd_lines, lgd_links, direction = "horizontal")
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()
pushViewport(viewport(x = 0.5, y = unit(1, "npc") - circle_size,
width = grobWidth(lgd_list_horizontal), height = grobHeight(lgd_list_horizontal),
just = c("center", "top")))
grid.draw(lgd_list_horizontal)
upViewport()
sessionInfo()
## R version 3.3.2 (2016-10-31)
## Platform: x86_64-apple-darwin13.4.0 (64-bit)
## Running under: macOS Sierra 10.12.3
##
## locale:
## [1] C/en_US.UTF-8/C/C/C/C
##
## attached base packages:
## [1] grid stats graphics grDevices utils datasets base
##
## other attached packages:
## [1] circlize_0.3.11 gridBase_0.4-7 ComplexHeatmap_1.13.2
## [4] digest_0.6.12 htmltools_0.3.5 GetoptLong_0.1.7
## [7] markdown_0.7.7 knitr_1.15.1
##
## loaded via a namespace (and not attached):
## [1] Rcpp_0.12.9 highr_0.6 DEoptimR_1.0-8
## [4] RColorBrewer_1.1-2 plyr_1.8.4 viridis_0.3.4
## [7] methods_3.3.2 class_7.3-14 tools_3.3.2
## [10] prabclus_2.2-6 dendextend_1.4.0 mclust_5.2.2
## [13] evaluate_0.10 tibble_1.2 gtable_0.2.0
## [16] lattice_0.20-34 mvtnorm_1.0-5 gridExtra_2.2.1
## [19] trimcluster_0.1-2 stringr_1.1.0 cluster_2.0.5
## [22] GlobalOptions_0.0.11 fpc_2.1-10 stats4_3.3.2
## [25] diptest_0.75-7 nnet_7.3-12 robustbase_0.92-7
## [28] flexmix_2.3-13 kernlab_0.9-25 ggplot2_2.2.1
## [31] magrittr_1.5 whisker_0.3-2 scales_0.4.1
## [34] modeltools_0.2-21 MASS_7.3-45 assertthat_0.1
## [37] shape_1.4.2 colorspace_1.3-2 stringi_1.1.2
## [40] lazyeval_0.2.0 munsell_0.4.3 rjson_0.2.15