【问题标题】:ggplot2 annotation_ticks on the outside of the plot regionggplot2 annotation_ticks 在绘图区域的外部
【发布时间】:2020-02-17 11:40:11
【问题描述】:

我试图找到一种优雅的方式在使用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),其中剪切被关闭 - 不幸的是 - 导致刻度超出绘图-范围)。

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

【问题讨论】:

    标签: r ggplot2 plot axes


    【解决方案1】:

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

    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")
    

    【讨论】:

    • 实际上,这导致了一个非常有前途的方向。次要滴答声在开头(以及结尾,随着其他数据变得可见)的奇怪行为当然是不受欢迎的。你能看出它是从哪里来的吗?也许有可能修复它......
    • 我不知道,在我改编之前它已经在函数中了。可能最好的查看位置是您发布的链接中的calc_ticks,但我不熟悉那段代码在做什么。如果您在此语句中注释掉minpowmaxpow:`majorTicks
    • 我想可能可以为我的目的编写一个更简单的calc_ticks 版本 - 似乎代码改编自annotation_logticks,这需要更复杂的计算地点。
    • 或者,您可以通过简单地使用天平已经计算出的小中断来避免在计算中断时几乎为零的努力。请参阅上面的编辑。
    • 哇,它成功了。我几乎不敢相信!谢谢你,@teunbrand!
    【解决方案2】:

    上面的功能非常好。

    我发现一个更简单或更容易理解的解决方案是简单地以您想要的主要和次要中断的增量指定主轴中断 - 因此,如果您希望主要以 10 为增量,而次要以增量为单位5,您仍然应该以 5 为单位指定主要增量。

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

    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"),
    

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

    【讨论】:

      猜你喜欢
      • 2023-03-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-01-24
      • 2019-01-15
      • 1970-01-01
      • 2022-01-18
      • 1970-01-01
      相关资源
      最近更新 更多