How can I make this linetype in ggplot?

Viewed 125

enter image description here

I am currently drawing various map plots using ggplot. I checked that the following axis linetype can be drawn on the x-axis and y-axis using the oceanmap package, but I couldn't find a way to implement this with ggplot. Is there a way to apply the following linetype to the x-axis and y-axis in ggplot?

1 Answers

As already mentioned in the comments, drawing this kind of axes in ggplot2 could be achieved via rects or segments. I went for geom_rects as they allow for both a fill and a color. While doing such a legend is "trivial" in that it simply means to draw some rects in my opinion it isn't as it demands some (or a lot of?) data wrangling to set up the data for drawing the rectangles, ...

Basically I make

  1. use of geom_rect to draw the segments of the axis,
  2. use of geom_rect to draw the corners,
  3. remove the default expansion of the scale,
  4. added a separate fill scale for the rects via ggnewscale
  5. Tweaked the axis ticks to have the same size as the rect border.

I tried my best to make my approach as general as possible but there is still a lot of space for improvement, e.g. in my approach you have to manually set the scales of the axes, i.e. longitudes and latitudes.

library(ggplot2)
library(ggnewscale)
library(dplyr)

nc <- sf::st_read(system.file("shape/nc.shp", package = "sf"), quiet = TRUE)

axis_black_white <- function(xl, xr, yb, yt, width = .05, size = .1, 
                        x.breaks = ggplot2::waiver(), y.breaks = ggplot2::waiver()) {
  if (xl > xr) { h <- xr; xr <- xl; xl <- h }
  if (yb > yt) { h <- yb; yb <- yt; yt <- h }
  # Top and bottom
  xmin <- seq(xl, xr - 1, 1)
  xmax <- seq(xl + 1, xr, 1)
  df1 <- data.frame(where = "b", xmin = xmin, xmax = xmax, ymin = yb, ymax = yb - width)
  df2 <- data.frame(where = "t", xmin = xmin, xmax = xmax, ymin = yt, ymax = yt + width)
  dfx <- dplyr::bind_rows(df1, df2) %>%
    dplyr::mutate(fill = xmin %% 2 == 0)
  # Left and right
  ymin <- seq(yb, yt - 1, 1)
  ymax <- seq(yb + 1, yt, 1)
  df1 <- data.frame(where = "l", xmin = xl, xmax = xl - width, ymin = ymin, ymax = ymax)
  df2 <- data.frame(where = "r", xmin = xr, xmax = xr + width, ymin = ymin, ymax = ymax)
  dfy <- dplyr::bind_rows(df1, df2) %>%
    dplyr::mutate(fill = ymin %% 2 == 0)
  dfxy <- bind_rows(dfx, dfy)
  # Corners
  dfc <- data.frame(
    where = c("tl", "tr", "bl", "br"),
    xmin = c(xl - width, xr, xl - width, xr), 
    xmax = c(xl, xr + width, xl, xr + width),
    ymin = c(yb - width, yb - width, yt + width, yt + width),
    ymax = c(yb, yb, yt, yt),
    fill = c(TRUE, TRUE, FALSE, FALSE)
  )
  list(
    scale_x_continuous(breaks = x.breaks, expand = c(0, 0)),
    scale_y_continuous(breaks = y.breaks, expand = c(0, 0)),
    ggnewscale::new_scale_fill(),
    geom_rect(
      data = dfxy, aes(xmin = xmin, xmax = xmax, ymin = ymin, ymax = ymax, fill = fill),
      color = "black", size = size
    ),
    geom_rect(
      data = dfc, aes(xmin = xmin, xmax = xmax, ymin = ymin, ymax = ymax, fill = fill),
      color = "black", size = size
    ),
    scale_fill_manual(values = c("white", "black"), guide = "none"),
    theme(axis.ticks = element_line(size = size))
  )
}

ggplot(nc) +
  geom_sf(aes(fill = AREA)) +
  scale_fill_viridis_c(option = "plasma") +
  axis_black_white(-86, -74, 33, 37, width = .1, y.breaks = seq(33, 37, 1))

Related