12

I'd like to create a bar plot on an Archimedean spiral, like discussed here.

With an end goal of something like this, but less overwhelming.

Here's a sample dataframe:

    test <- structure(list(month = c(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
                                     1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12), 
           year = c(2015, 2015, 2015, 2015, 2015, 2015, 2015, 
                    2015, 2015, 2015, 2015, 2015, 2016, 2016, 
                    2016, 2016, 2016, 2016, 2016, 2016, 2016, 
                    2016, 2016, 2016), 
           value = c(49, 34, 35, 34, 50, 35, 48, 50, 44, 38, 42, 
                   43, 33,30, 42, 43, 58, 55, 47, 36, 35, 53, 
                   61, 59)), 
          .Names = c("month", "year", "value"), 
          class = "data.frame", row.names = c(NA, -24L))

I can make a bar graph, using the following code:

    ggplot(monthly, aes(x = ym, y = value)) +
      geom_bar(stat = "identity") 

And I can make the spiral, using the following code:

    a <- 0   #Any number here & it still looks the same to me...
    b <- 10  #Any number here & it still looks the same to me...
    theta <- seq(0,10*pi, 0.01)
    r <- a + b*theta
    df <- data.frame(x = r*cos(theta), y = r*sin(theta))
    ggplot(df, aes(x,y)) + 
      geom_point(col = 'red')

But how (if at all) can I plot the bars on the spiral?

This is about as close as I've gotten: creating a spiral with my data rather than the above formula. But my data isn't actually displayed...

    d <- ggplot(monthly, aes(x = month, y = month, color = year)) + 
      geom_path(size = 2) + 
      coord_polar() +
      theme_minimal() + 
      theme(legend.position = "none")
    d
Community
  • 1
  • 1
jesstme
  • 604
  • 2
  • 10
  • 25

1 Answers1

18

A fun question. To do this properly with bars, we need to use polygons to correctly account for the 'warp' of each corner. This plot will look better the more bars per cycle you have, but you can change the y limits to avoid strong distortion in the center. You'll have to come up with some way to nicely label the y-axis, but coord_polar isn't very good at that to begin with.

library(tidyverse)

First, either create sample df from scratch:

monthly <- 
  expand.grid(month = 1:12, year = factor(unique(monthly$year))) %>% 
  mutate(value = runif(n(), 10, 20),
         y = as.numeric(year) - 1 + (month - 1) / 12) 

Or, working from an existing df:

    monthly <- monthly %>%
      mutate(y = as.numeric(year) - 1 + (month - 1) / 12)

Continue with the following:

bars <- monthly %>% 
  mutate(value_norm = value / (max(value) * 1.1),
         xmin = month - 0.5,
         xmax = month + 0.5,
         ymin = y,
         ymax = y + value_norm)
# we could plot `bars` here, but things will not line up nicely, since
# the bar will be nice and flat, but it needs to curve with the spiral.

poly <- bars %>% 
  rowwise() %>% 
  do(with(., data_frame(year = year,
                        month = month,
                        x = c(xmin, xmax, xmax, xmin),
                        y = c(ymin - 1/24, 
                              ymin + 1/24, 
                              ymax + 1/24, 
                              ymax - 1/24))))

ggplot(poly, aes(x, y, fill = interaction(month, year))) + 
  geom_polygon(col = 1) +
  coord_polar() +
  ylim(-3, 5) +
  viridis::scale_fill_viridis(discrete = TRUE, option = 'C') +
  scale_x_continuous(breaks = 1:12, labels = month.name) +
  theme_minimal() + 
  theme(legend.position = "none", axis.text.y = element_blank(),
        axis.title = element_blank())

enter image description here

An alternative is to just make a spiralling heatmap:

bars2 <- monthly %>% 
  mutate(xmin = month - 0.5,
         xmax = month + 0.5,
         ymin = y,
         ymax = y + 1)

poly2 <- bars2 %>% 
  rowwise() %>% 
  do(with(., data_frame(value = value,
                        year = year,
                        month = month,
                        x = c(xmin, xmax, xmax, xmin),
                        y = c(ymin - 1/24, ymin + 1/24, ymax + 1/24, ymax - 1/24))))

enter image description here

Axeman
  • 32,068
  • 8
  • 81
  • 94
  • 1
    This is great! Answer accepted. I fixed the sample df provided & made a few changes in your code. There might be a better way to keep the code flexible in case there are more than 24 rows, but nrow() seemed to work for me. 3 questions: How would I color the bars according to their value? Changing to fill = value creates something that looks like a ninja star. Suggestions? Also, could you add the code to plot the heat map? Lastly, I'm still working out how to adjust this to plot multiple bars per month, for example one bar per daily value. Ideas welcomed! – jesstme Jan 13 '17 at 18:42
  • 1
    Figured out how to manipulate the color. Still hoping for guidance plotting daily values within each month & year. `ggplot(poly, aes(x, y, group = interaction(month, year))) + geom_polygon(aes(fill = value, colour = year), size = 1) + coord_polar()` – jesstme Jan 13 '17 at 21:35
  • 1
    I know I didn't provide a fully flexible function, but it should be relatively easy to adapt? `y = as.numeric(year) - 1 + (month - 1) / 12` will have to change, for days it will be something like `y = as.numeric(year) - 1 + (day - 1) / 365`. And the polygons would need adjustment by `1/730` instead of `1/24`. – Axeman Jan 13 '17 at 23:41
  • And yes, separating the grouping from the fill is how you actually color by value. – Axeman Jan 13 '17 at 23:43