ggplot2 annotation_ticks 在绘图区域的外面

ggplot2 annotation_ticks on the outside of the plot region

我试图找到一种优雅的方式来在使用 ggplot2 创建的绘图上插入小刻度。我找到了一个功能,它几乎完全符合我的要求:https://rdrr.io/github/hrbrmstr/ggalt/src/R/annotation_ticks.r

只有一个缺点:与 annotation_logticks 一样,刻度线绘制在绘图区域内。我需要他们在外面。

一种解决方案是对刻度长度使用负值。当我这样做时,蜱虫消失了。我假设,这是由于 ggplot2 的默认裁剪操作造成的,它抑制了绘图区域外的绘图(?)(另请参阅 log ticks on the outer side of axes (annotation_logticks),其中裁剪被关闭 - 不幸的是 - 导致ticks 超出 plot-range).

那么:是否有修改 annotation_ticks - 函数的选项,以便在绘图区域 外部 产生刻度,仅覆盖绘图范围?理想情况下,此功能应合并到 annotate_ticks - 函数中(我不想保存然后重新安排情节;我宁愿一步构建我的最终情节)。

我找到了一种令人满意的调整 annotation_ticks 函数的解决方案。如果我们只是简单地从您发布的 link 中复制粘贴代码,我们可以在 GeomTicks ggproto 对象的末尾附近进行以下小调整:

GeomTicks <- ggproto(
  "GeomTicks", Geom,
  # ...
  # all the rest of the code
  # ...
    gTree(children = do.call("gList", ticks), cl = "ticktrimmer") # Change this line
  },
  default_aes = aes(colour = "black", size = 0.5, linetype = 1, alpha = 1)
)

然后我们可以编写一个小函数,通过劫持 grid 包中的 S3 泛型 makeContent 来简单地剪辑在绘制之前触发的范围之外的刻度:

library(grid)

makeContent.ticktrimmer <- function(x) {
  # Loop over segment grobs
  x$children <- lapply(x$children, function(m) {
    # convert positions to values
    x0 <- convertX(m$x0, "npc", valueOnly = T)
    x1 <- convertX(m$x1, "npc", valueOnly = T)
    y0 <- convertY(m$y0, "npc", valueOnly = T)
    y1 <- convertY(m$y1, "npc", valueOnly = T)

    # check if values are outside 0-1
    if (length(unique(x0)) == 1) {
      keep <- y0 >= 0 & y0 <= 1 & y1 >= 0 & y1 <= 1
    } else if (length(unique(y0)) == 1) {
      keep <- x0 >= 0 & x0 <= 1 & x1 >= 0 & x1 <= 1
    } else {
      keep <- TRUE
    }

    # Trim the segments
    m$x0 <- m$x0[keep]
    m$y0 <- m$y0[keep]
    m$x1 <- m$x1[keep]
    m$y1 <- m$y1[keep]
    m
  })
  x
}

现在我们可以绘制:

g <- ggplot(iris, aes(Sepal.Width, Sepal.Length)) +
  geom_point(aes(colour = Species)) +
  annotation_ticks(long = -1 * unit(0.3, "cm"),
                   mid = -1 * unit(0.2, "cm"),
                   short = -1 * unit(0.1, "cm")) +
  coord_cartesian(clip = "off")

除了左边的第一个刻度位置有点奇怪外,这似乎还算合理。

编辑:这是对代码的快速重构,以处理本机次要中断,而不是从头计算次要中断。用户函数:

annotation_ticks <- function(sides = "b",
                             scale = "identity",
                             scaled = TRUE,
                             ticklength = unit(0.1, "cm"),
                             colour = "black",
                             size = 0.5,
                             linetype = 1,
                             alpha = 1,
                             color = NULL,
                             ticks_per_base = NULL,
                             ...) {
  if (!is.null(color)) {
    colour <- color
  }

  # check for invalid side
  if (grepl("[^btlr]", sides)) {
    stop(gsub("[btlr]", "", sides), " is not a valid side: b,t,l,r are valid")
  }

  # split sides to character vector
  sides <- strsplit(sides, "")[[1]]

  if (length(sides) != length(scale)) {
    if (length(scale) == 1) {
      scale <- rep(scale, length(sides))
    } else {
      stop("Number of scales does not match the number of sides")
    }
  }

  base <- sapply(scale, function(x) switch(x, "identity" = 10, "log10" = 10, "log" = exp(1)), USE.NAMES = FALSE)

  if (missing(ticks_per_base)) {
    ticks_per_base <- base - 1
  } else {
    if ((length(sides) != length(ticks_per_base))) {
      if (length(ticks_per_base) == 1) {
        ticks_per_base <- rep(ticks_per_base, length(sides))
      } else {
        stop("Number of ticks_per_base does not match the number of sides")
      }
    }
  }

  delog <- scale %in% "identity"

  layer(
    data = data.frame(x = NA),
    mapping = NULL,
    stat = StatIdentity,
    geom = GeomTicks,
    position = PositionIdentity,
    show.legend = FALSE,
    inherit.aes = FALSE,
    params = list(
      base = base,
      sides = sides,
      scaled = scaled,
      ticklength = ticklength,
      colour = colour,
      size = size,
      linetype = linetype,
      alpha = alpha,
      ticks_per_base = ticks_per_base,
      delog = delog,
      ...
    )
  )
}

ggproto 对象:

GeomTicks <- ggproto(
  "GeomTicks", Geom,
  extra_params = "",
  handle_na = function(data, params) {
    data
  },

  draw_panel = function(data,
                        panel_scales,
                        coord,
                        base = c(10, 10),
                        sides = c("b", "l"),
                        scaled = TRUE,
                        ticklength = unit(0.1, "cm"),
                        ticks_per_base = base - 1,
                        delog = c(x = TRUE, y = TRUE)) {
    ticks <- list()

    for (s in 1:length(sides)) {
      if (grepl("[b|t]", sides[s])) {

        xticks <- panel_scales$x.minor

        # Make the grobs
        if (grepl("b", sides[s])) {
          ticks$x_b <- with(
            data,
            segmentsGrob(
              x0 = unit(xticks, "npc"),
              x1 = unit(xticks, "npc"),
              y0 = unit(0, "npc"),
              y1 = ticklength,
              gp = gpar(
                col = alpha(colour, alpha),
                lty = linetype,
                lwd = size * .pt
              )
            )
          )
        }
        if (grepl("t", sides[s])) {
          ticks$x_t <- with(
            data,
            segmentsGrob(
              x0 = unit(xticks, "npc"),
              x1 = unit(xticks, "npc"),
              y0 = unit(1, "npc"),
              y1 = unit(1, "npc") - ticklength,
              gp = gpar(
                col = alpha(colour, alpha),
                lty = linetype,
                lwd = size * .pt
              )
            )
          )
        }
      }


      if (grepl("[l|r]", sides[s])) {

        yticks <- panel_scales$y.minor

        # Make the grobs
        if (grepl("l", sides[s])) {
          ticks$y_l <- with(
            data,
            segmentsGrob(
              y0 = unit(yticks, "npc"),
              y1 = unit(yticks, "npc"),
              x0 = unit(0, "npc"),
              x1 = ticklength,
              gp = gpar(
                col = alpha(colour, alpha),
                lty = linetype, lwd = size * .pt
              )
            )
          )
        }
        if (grepl("r", sides[s])) {
          ticks$y_r <- with(
            data,
            segmentsGrob(
              y0 = unit(yticks, "npc"),
              y1 = unit(yticks, "npc"),
              x0 = unit(1, "npc"),
              x1 = unit(1, "npc") - ticklength,
              gp = gpar(
                col = alpha(colour, alpha),
                lty = linetype,
                lwd = size * .pt
              )
            )
          )
        }
      }
    }
    gTree(children = do.call("gList", ticks))
  },
  default_aes = aes(colour = "black", size = 0.5, linetype = 1, alpha = 1)
)

绘图:

ggplot(iris, aes(Sepal.Width, Sepal.Length)) +
  geom_point(aes(colour = Species)) +
  annotation_ticks(ticklength = -1 * unit(0.1, "cm"),
                   side = "b") +
  coord_cartesian(clip = "off")

上面的功能非常好。

我发现一个更简单或更容易解决的解决方案是简单地指定你的长轴中断在你想要的增量中用于主要和次要中断 - 所以如果你想要主要以 10 为增量,并且次要以 5 为增量,您仍然应该以 5 为增量指定主要增量。

然后,在主题中,要求您为轴文本指定颜色。您可以给它一个颜色列表,而不是选择一种颜色 - 指定您想要长轴编号的任何颜色,然后指定短轴颜色的 NA。这将为您提供主要标记上的文本,但 'minor' 标记上没有任何内容。同样,对于绘图内部的网格,您可以指定一个线宽列表,这样绘图中的主要和次要网格线的粗细仍然存在差异,即使您将次要网格线指定为主要网格线。作为您可以放入主题的示例:

panel.grid.major.x = element_line(colour = c("white"), size = c(0.33, 0.2)),
panel.grid.major.y = element_line(colour = c("white"), size = c(0.33, 0.2)),
axis.text.y = element_text(colour = c("black", NA), family = "Gill Sans"),
axis.text.x = element_text(colour = c("black", NA), family = "Gill Sans"),

我怀疑您可以用完全相同的方式更改外部刻度线的大小,但我还没有尝试过。