8

is it possible to make rounded corners for the tiles in the geom_tile layer on ggplot2?

Example w/ standard edges:

df <- data.frame(
    x = rep(seq(2, 15, 6.5), 2),
    y = c(rep(6.5, 3), rep(2,3)),
    h = rep(4.25, 6),
    w = rep(6.25, 6), 
    info= rep("boring\ntiles", 6),
    color = factor(c(rep(1,3), rep(2,3)))
)

ggplot(df, aes(x, y, height = h, width = w, label = info)) +
    geom_tile(aes(fill = color), alpha=0.83) +
    geom_text(color = "white", fontface = "bold", size = 8,
              aes(label = info, x = x - 2.9, y = y - 1), hjust = 0)

enter image description here

Vinícius Félix
  • 8,448
  • 6
  • 16
  • 32
  • Strongly related, but I don't think it's a dupe: [round rect grob in ggplot2](https://stackoverflow.com/q/52820756/903061) – Gregor Thomas Oct 14 '20 at 15:02
  • I've a feeling it's not possible with `geom_tile()` due to how it works in the background. From the vignette: `geom_rect() and geom_tile() do the same thing, but are parameterised differently: geom_rect() uses the locations of the four corners (xmin, xmax, ymin and ymax), while geom_tile() uses the center of the tile and its size (x, y, width, height).` Additionally if it is feasible it's probably not going to be easy since rounding the corners would mean `geom_tile()` leaving out sections, although tiny, that should be included. – NColl Oct 15 '20 at 02:04

1 Answers1

4

@hrbrmstr did the hard work for us, so we can borrow GeomRrect from his statebins package and create geom_rtile with only 3 changes applied to GeomTile and geom_tile, see end of this post.

Reference: https://ggplot2-book.org/extensions.html

enter image description here

code

library(ggplot2)
# install.packages(statebins)
ggplot(df1, aes(x, y, height = h, width = w, label = info)) +
  geom_rtile(aes(fill = color), alpha=0.83, radius = unit(15, "pt")) +
  geom_text(color = "white", fontface = "bold", size = 8,
            aes(label = info, x = x - 2.9, y = y - 1), hjust = 0)

data

df1 <- data.frame(
  x = rep(seq(2, 15, 6.5), 2),
  y = c(rep(6.5, 3), rep(2,3)),
  h = rep(4.25, 6),
  w = rep(6.25, 6), 
  info= rep("rounded\ntiles!!1", 6),
  color = factor(c(rep(1,3), rep(2,3)))
)

ggproto object

`%||%` <- function(a, b) {
  if(is.null(a)) b else a
}

GeomRtile <- ggproto("GeomRtile", 
                     statebins:::GeomRrect, # 1) only change compared to ggplot2:::GeomTile
                     
  extra_params = c("na.rm"),
  setup_data = function(data, params) {
    data$width <- data$width %||% params$width %||% resolution(data$x, FALSE)
    data$height <- data$height %||% params$height %||% resolution(data$y, FALSE)

    transform(data,
      xmin = x - width / 2,  xmax = x + width / 2,  width = NULL,
      ymin = y - height / 2, ymax = y + height / 2, height = NULL
    )
  },
  default_aes = aes(
    fill = "grey20", colour = NA, size = 0.1, linetype = 1,
    alpha = NA, width = NA, height = NA
  ),
  required_aes = c("x", "y"),

  # These aes columns are created by setup_data(). They need to be listed here so
  # that GeomRect$handle_na() properly removes any bars that fall outside the defined
  # limits, not just those for which x and y are outside the limits
  non_missing_aes = c("xmin", "xmax", "ymin", "ymax"),
  draw_key = draw_key_polygon
)

geom_rtile

geom_rtile <- function(mapping = NULL, data = NULL,
                       stat = "identity", position = "identity",
                       radius = grid::unit(6, "pt"), # 2) add radius argument
                       ...,
                       linejoin = "mitre",
                       na.rm = FALSE,
                       show.legend = NA,
                       inherit.aes = TRUE) {
  layer(
    data = data,
    mapping = mapping,
    stat = stat,
    geom = GeomRtile, # 3) use ggproto object here
    position = position,
    show.legend = show.legend,
    inherit.aes = inherit.aes,
    params = rlang::list2(
      linejoin = linejoin,
      na.rm = na.rm,
      ...
    )
  )
}
markus
  • 25,843
  • 5
  • 39
  • 58