Search code examples
rggplot2tidyversevisualization

Sunflower Plot Using ggplot2


I posted the following question on Posit Community three months ago but have not received an answer. I am reposting it here in the hope that someone can help me.

I am interested in creating a sunflower plot using ggplot2. In a sunflower plot, overplotted points are visualized as "sunflowers," with each "leaf" representing the number of data points at that location. While I am familiar with the sunflowerplot() function in the graphics package, I am exploring ggplot2 for its seamless integration with other geom layers. Here is a base graphics example:

tb <- tibble::tibble(
  x = c(rep(0, 5), rep(1, 1), rep(0, 3), rep(1, 2)),
  y = c(rep(0, 5), rep(1, 1), rep(1, 3), rep(0, 2))
)
sunflowerplot(tb$x, tb$y)

I am specifically seeking a ggplot2 solution that mirrors the functionality of geom_count(), but uses the number of "petals" rather than point size to represent counts. While I am aware of alternatives like geom_jitter() or geom_beeswarm() for mitigating overplotting, my focus remains on crafting a sunflower plot.

Despite scouring the web, I have yet to discover a suitable ggplot2 add-on package or workaround. The term "sunflower plot" surfaced in a thread titled Plotting a Scatter Plot with Categorical Data within the Posit community. However, this discussion lacked accompanying code, and the featured plot was not a sunflower plot.

If anyone has encountered a ggplot2-based sunflower plot solution or has a clever hack to achieve this visualization, I would greatly appreciate your insights.


Solution

  • I thought this would be very tricky, but thanks to this great answer on custom shapes in ggplot2 I've been able to do it.

    The library of default shapes used by R isn't expansive enough to do this, so you need to use grid to create your own custom shapes by drawing lines between coordinates of your choosing. ggpmisc will let you add these' grobs to a ggplot sing 'geom_grob'. Here's the code:

    library(ggpmisc)
    library(grid)
    library(dplyr)
    library(ggplot2)
    
    set.seed(pi)
    
    ## Function to make shape based on count
    make_shape <- function(n){
      
      # We need our new shapes to have the same aspect ratio as the whole plot
      dev_size <- dev.size()
      dev_ratio <- dev_size[2]/dev_size[1]
      
      # If n == 1, make a point
      if(n == 1) grob <- pointsGrob(0.5, 0.5, pch = 16)
      else {
        
        ## If n > 1, define the extremeties of the 'star'
        ## (A sequence of length n that starts at 1 nth of the circle and ends at a whole circle)
        extremities <- 2 * pi * seq(1/n, 1, length.out = n)
        
        ## Create x and y vectors for the points of the shape
        x <- y <- numeric(2 * n - 1)
        
        ## Odd elemetns of x are the sin() of the angles of the extremities
        ## Use dev_ratio to get the lengths of each petal consistent
        x[seq_along(x) %% 2 == 1] <- unit(0.5 + 0.15 * dev_ratio * sin(extremities), "npc")
        ## At even elemnts of x, we go back to the centre, creating a star shape
        x[seq_along(x) %% 2 == 0] <- 0.5
        
        ## Odd elements of y are the cos() of the angles
        y[seq_along(y) %% 2 == 1] <- unit(0.5 + 0.15 * cos(extremities), "npc")
        y[seq_along(y) %% 2 == 0] <- 0.5
        
        ## Create a grob
        grob <- linesGrob(x, y , gp = gpar(lwd = 2))
      }
      grob
    }
    
    test <- tibble(
      x = 1:10,
      count = 1:10
    ) |> 
      rowwise() |> 
      mutate(shape = list(make_shape(count)))
    
    ggplot(test) + 
      geom_grob(aes(x, y = "Shapes",  label = shape)) +
      scale_x_continuous(breaks = 1:10) + 
      
      labs(y = NULL, x = "N", title = "Sunflower plot example") + 
      theme_minimal()+ 
      theme(panel.grid.minor = element_blank(),
            panel.grid.major.y = element_blank())
    

    Created on 2024-05-29 with reprex v2.1.0