--- title: "Progressive circle packing" author: "Michael Bedward" date: "`r Sys.Date()`" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Progressive circle packing} %\VignetteEngine{knitr::rmarkdown} \usepackage[utf8]{inputenc} --- ```{r setup, include=FALSE} set.seed(42) library(packcircles) if (!requireNamespace("ggplot2", quietly = TRUE)) { # Cannot run graph code without ggplot2 so turn off chunk evaluation warning("Package ggplot2 is required for this vignette") knitr::opts_chunk$set(eval = FALSE) } ``` The function `circleProgressiveLayout` arranges a set of circles deterministically. The first two circles are placed to the left and right of the origin respectively. Subsequent circles are placed so that each: * is externally tangent to two previously placed circles; * does not overlap another circle; * and is as close as possible to the origin. The algorithm was described in the paper: *Visualization of large hierarchical data by circle packing* by [Weixin Wang et al. (2006)](https://doi.org/10.1145/1124772.1124851). The implementation in this package is based on a [version written in C by Peter Menzel](https://github.com/pmenzel/packCircles). The algorithm is very efficient and this, combined with the implementation in Rcpp, means arrangements for large numbers of circles can be found quickly. ## First example We begin by arranging 10 circles of various sizes. First we pass a vector of circle areas to the `circleProgressiveLayout` function. It returns a data frame of centre coordinates and radii. ```{r} areas <- c(20, 10, 40, rep(5, 7)) # Generate the layout packing <- circleProgressiveLayout(areas) head( round(packing, 2) ) ``` Next we derive a data frame of circle vertices for plotting using the `circleLayoutVertices` function, and then use ggplot to display the layout, labelling the circles to show their order of placement: ```{r fig.width=4, fig.height=4} library(ggplot2) t <- theme_bw() + theme(panel.grid = element_blank(), axis.text=element_blank(), axis.ticks=element_blank(), axis.title=element_blank()) theme_set(t) dat.gg <- circleLayoutVertices(packing, npoints=50) ggplot(data = dat.gg) + geom_polygon(aes(x, y, group = id), colour = "black", fill = "grey90", alpha = 0.7, show.legend = FALSE) + geom_text(data = packing, aes(x, y), label = 1:nrow(packing)) + coord_equal() ``` By default, `circleProgressiveLayout` takes the input sizes to be circle areas. If instead you have input radii, add `sizetype = "radius"` to the arguments when calling the function. ## Layouts are order dependent Re-ordering the input sizes will generally produce a different layout unless the sizes are uniform. Here we repeatedly shuffle the area values used above and generate a new layout each time. ```{r fig.width=7, fig.height=5} ncircles <- length(areas) nreps <- 6 packings <- lapply( 1:nreps, function(i) { x <- sample(areas, ncircles) circleProgressiveLayout(x) }) packings <- do.call(rbind, packings) npts <- 50 dat.gg <- circleLayoutVertices(packings, npoints = npts) dat.gg$rep <- rep(1:nreps, each = ncircles * (npts+1)) ggplot(data = dat.gg, aes(x, y)) + geom_polygon(aes(group = id), colour = "black", fill = "grey90") + coord_equal() + facet_wrap(~ rep, nrow = 2) ``` We can use this ordering effect to create some circle art... ```{r fig.width=7, fig.height=4} areas <- 1:1000 # area: small to big packing1 <- circleProgressiveLayout(areas) dat1 <- circleLayoutVertices(packing1) # area: big to small packing2 <- circleProgressiveLayout( rev(areas) ) dat2 <- circleLayoutVertices(packing2) dat <- rbind( cbind(dat1, set = 1), cbind(dat2, set = 2) ) ggplot(data = dat, aes(x, y)) + geom_polygon(aes(group = id, fill = -id), colour = "black", show.legend = FALSE) + scale_fill_distiller(palette = "RdGy") + coord_equal() + facet_wrap(~set, labeller = as_labeller( c('1' = "small circles first", '2' = "big circles first")) ) ``` ## More detailed layout display The package includes an example data set of the abundance of different types of bacteria measured in a study of biofilms. Columns are value (abundance), display colour and label (bacterial taxon). ```{r} data("bacteria") head(bacteria) ``` The following example shows how to display the abundance values as circles filled with the specified colours. It relies on the fact that the `id` column in the output of `circleLayoutVertices` maps to the row number of the input data. Note: the y-axis is reversed so that the layour is rendered similarly to the example [here](https://github.com/pmenzel/packCircles). ```{r fig.width=5, fig.height=5, fig.align='center'} packing <- circleProgressiveLayout(bacteria) dat.gg <- circleLayoutVertices(packing) ggplot(data = dat.gg) + geom_polygon(aes(x, y, group = id, fill = factor(id)), colour = "black", show.legend = FALSE) + scale_fill_manual(values = bacteria$colour) + scale_y_reverse() + coord_equal() ``` As a further flourish, we can make the plot interactive so that the name of the bacterial taxon is displayed when the mouse cursor hovers a circle. Note: the `ggiraph` package is required for this. ```{r fig.width=5, fig.height=5} if (requireNamespace("ggiraph")) { gg <- ggplot(data = dat.gg) + ggiraph::geom_polygon_interactive( aes(x, y, group = id, fill = factor(id), tooltip = bacteria$label[id], data_id = id), colour = "black", show.legend = FALSE) + scale_fill_manual(values = bacteria$colour) + scale_y_reverse() + labs(title = "Hover over circle to display taxon name") + coord_equal() ggiraph::girafe(ggobj = gg, width_svg = 5, height_svg = 5) } ```