0
votes

I would like to create a polar heatmap like the heatmap from the Lancet paper "Height and body-mass index trajectories of school-aged children and adolescents from 1985 to 2019 in 200 countries and territories: a pooled analysis of 2181 population-based studies with 65 million participants": enter image description here

I appreciate the idea of annotating the age each layer of ring represents (age 5 to 19 years) by creating a fan-shaped opening of the polar heatmap (manually circled in red). I refer to 5-19 as the Y-AXIS LABELS hereafter.

Below is the code from @Cyrus Mohammadian describing how to arrange the positions of Y-AXIS LABELS of polar heatmaps. I replicate Cyrus Mohammadian's code below:

library(grid)
library(gtable)
library(reshape)
library(ggplot2)
library(plyr)

nba <- read.csv("http://datasets.flowingdata.com/ppg2008.csv")

nba$Name <- with(nba, reorder(Name, PTS))
nba.m <- melt(nba)

nba.m <- ddply(nba.m, .(variable), transform, value = scale(value))

# Convert the factor levels (variables) to numeric + quanity to determine    size   of hole.
nba.m$var2 = as.numeric(nba.m$variable) + 15

# Labels and breaks need to be added with scale_y_discrete.
y_labels = levels(nba.m$variable)
y_breaks = seq_along(y_labels) + 15


nba.labs <- subset(nba.m, variable==levels(nba.m$variable)    [nlevels(nba.m$variable)])

nba.labs <- nba.labs[order(nba.labs$Name),]
nba.labs$ang <- seq(from=(360/nrow(nba.labs))/1.5, to=(1.5* (360/nrow(nba.labs)))-360, length.out=nrow(nba.labs))+80
nba.labs$hjust <- 0
nba.labs$hjust[which(nba.labs$ang < -90)] <- 1
nba.labs$ang[which(nba.labs$ang < -90)] <- (180+nba.labs$ang)[which(nba.labs$ang < -90)]

p<-ggplot(nba.m, aes(x=Name, y=var2, fill=value)) +
  geom_tile(colour="white") +
  geom_text(data=nba.labs, aes(x=Name, y=var2+1.5,
                           label=Name, angle=ang, hjust=hjust), size=2.5) +
  scale_fill_gradient(low = "white", high = "steelblue") +
  ylim(c(0, 50)) +
  coord_polar(theta="x") +
  theme(panel.background=element_blank(),
    axis.title=element_blank(),
    panel.grid=element_blank(),
    axis.text.x=element_blank(),
    axis.ticks=element_blank(),
    axis.text.y=element_text(size=5))+ theme(axis.title.y=element_blank(),
        axis.text.y=element_blank(),
        axis.ticks.y=element_blank())
lab = textGrob((paste("G  MIN  PTS  FGM  FGA  FGP  FTM  FTA  FTP  X3PM X3PA X3PP ORB DRB  TRB  AST  STL  BLK  TO  PF")),
   x = unit(.1, "npc"), just = c("left"), 
   gp = gpar(fontsize = 7))

gp = ggplotGrob(p)
gp = gtable_add_rows(gp, unit(10, "grobheight", lab), -1)
gp = gtable_add_grob(gp, lab, t = -2, l = gp$layout[gp$layout$name == "panel",]$l)

grid.newpage()
grid.draw(gp)

This is the resultant figure: enter image description here

Y-AXIS LABELS are placed at the bottom of the heatmap instead of being positioned immediately next to each layer of ring like the Lancet paper. I therefore ask if it is possible to modify Cyrus Mohammadian's plot so that Y-AXIS LABELS are positioned next to each layer of the ring instead of being presented outside of the heatmap? In addition, it is preferrable that we can control the size of the fan-shaped opening so that we can customize according to length of the Y-AXIS LABEL texts.

A second request is to place the color legend in the center of the heatmap and make it curved. An example is illustrated in the figure below, which is from Fig 3 of the paper "Infectious diseases in children and adolescents in China: analysis of national surveillance data from 2008 to 2017":

enter image description here

Note that the color legend is centrally located and curved. How this could be done?

Thank you.

1
might need some modification for your data. stackoverflow.com/questions/62556246/… - StupidWolf
Does adding the following help? + geom_text(aes(x= -Inf, label = variable), size = 2.5, data = nba.m[!duplicated(nba.m$variable),]) + scale_x_discrete(expand = c(0.05, 0)). Might recalcibrate text angles of the outer labels though. - teunbrand
Thank you @teunbrand, your suggestion worked. Playing around with the number 0.05 allows for customization of the width of the fan-shaped blank. Besides, in light of the link from StupidWolf, I edited the question to require color legend to be placed in the center of the "donut" and make the lenged circular. - Patrick
Best I can come up with for the legend is to make a seperate plot containing the manually created legend with coord_polar() + theme_void(), grabbing the panel grob and then placing that at the center of the main plot with annotation_custom(). - teunbrand
I seem to realize something from your suggestion, but not yet clear. theme(legend.position = c(0.5, 0.5)) would position the legend centrally. Still not sure how to bend the legend. How about writing a more complete code snippet? Thank you. - Patrick

1 Answers

1
votes

Here is some example code for how you can shape something like a legend and add it to your plot. Due to some restrictions on annotation_custom() in relation to polar coordinates, I decided to use the devel version of patchwork from github to use the new inset_element() function (devtools::install_github("thomasp85/patchwork")).


library(ggplot2)
library(patchwork)

df <- reshape2::melt(volcano[1:20, 1:20])
breaks <- scales::extended_breaks()(df$value)
breaks <- scales::discard(breaks, range(df$value))

main <- ggplot(df, aes(Var1, Var2, fill = value)) +
  geom_tile() +
  scale_y_continuous(limits = c(-20, NA)) +
  guides(fill = "none") +
  coord_polar()


legend <- ggplot() +
  geom_tile(
    aes( 
    x = seq(min(df$value), max(df$value), length.out = 255),
    y = 1, fill = after_stat(x)
    )
  ) +
  annotate(
    "text", x = breaks, y = -0.1, label = breaks, size = 3
  ) +
  annotate(
    "segment", x = breaks, xend = breaks, y = 0.5, yend = 0.7, 
    colour = "white", size = 1
  ) +
  annotate(
    "segment", x = breaks, xend = breaks, y = 1.5, yend = 1.3, 
    colour = "white", size = 1
  ) +
  guides(fill = "none") +
  scale_y_continuous(limits = c(-2, 2)) +
  scale_x_continuous(expand = c(0.1, 0)) +
  coord_polar() +
  theme_void()

legend <- ggplotGrob(legend)

main + inset_element(legend, 0.3, 0.3, 0.7, 0.7) &
  theme(plot.background = element_blank())

Created on 2020-11-06 by the reprex package (v0.3.0)