Progressive circle packing"

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:

The algorithm was described in the paper: Visualization of large hierarchical data by circle packing by Weixin Wang et al. (2006). The implementation in this package is based on a version written in C by Peter Menzel.

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.

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:

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.

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...

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).

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.

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.

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::ggiraph(ggobj = gg, width_svg = 5, height_svg = 5)

}


Try the packcircles package in your browser

Any scripts or data that you put into this service are public.

packcircles documentation built on Sept. 8, 2023, 5:55 p.m.