【问题标题】:Adding minor tick marks to the x axis in ggplot2 (with no labels)在 ggplot2 的 x 轴上添加次要刻度线(没有标签)
【发布时间】:2021-06-04 09:51:06
【问题描述】:

下面是一个情节的示例代码,它几乎完全符合我的要求。我唯一要添加的是根据下面定义的 minor_breaks 在 x 轴上的刻度线(与主要刻度线大小相同)。

df <- data.frame(x = c(1900,1950,2000), y = c(50,75,60))

p <- ggplot(df, aes(x=x, y=y))
  p + geom_line() + 
  scale_x_continuous(minor_breaks = seq(1900,2000,by=10), breaks = seq(1900,2000,by=50), limits = c(1900,2000), expand = c(0,0)) +
  scale_y_continuous(breaks = c(20,40,60,80), limits = c(0,100)) +
  theme(legend.position="none", panel.background = element_blank(), 
  axis.line = element_line(color='black'), panel.grid.minor = element_blank())

提前致谢, --JT

【问题讨论】:

  • 我已经多次看到这个请求,但没有看到一个好的答案。我见过/使用的选项是 1:使用次要网格线。 2:手动添加刻度线..(有点痛苦的方法,但我可以设想未来的功能/包可以做到这一点。)
  • 无耻的自我推销:看这个answer

标签: r ggplot2


【解决方案1】:

现在可以使用 {ggh4x} 包轻松添加不带标签的短轴刻度。只需对原始图进行少量修改(参见代码中的 cmets)。

library(ggh4x)
#> Loading required package: ggplot2
df <- data.frame(x = c(1900, 1950, 2000), y = c(50, 75, 60))

ggplot(df, aes(x, y)) +
  geom_line() +
  scale_x_continuous(
    minor_breaks = seq(1900, 2000, by = 10),
    breaks = seq(1900, 2000, by = 50), limits = c(1900, 2000),
    guide = "axis_minor" # this is added to the original code
  ) +
  theme(ggh4x.axis.ticks.length.minor = rel(1)) # add this to get the same length

reprex package (v2.0.0) 于 2021-04-19 创建

【讨论】:

    【解决方案2】:

    上面的功能非常好。

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

    然后,在主题中,您被要求为轴文本指定颜色。除了选择 one 颜色,您还可以给它一个 list 颜色 - 指定您希望长轴编号的颜色,然后指定短轴颜色为 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"),
    

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

    【讨论】:

    • 我发现这种方式对于避免创建新函数非常有用。但是,它会显示一条警告:警告消息:element_text() 的矢量化输入不受官方支持。结果可能出乎意料,或者在 ggplot2 的未来版本中可能会发生变化。
    • 嗨@Paul - 是的,它会发出警告。 tidyverse 团队(制作 ggplot)对将来可能会被删除的东西给出了很多警告,但现在它似乎工作得很好。警告并不是说有什么问题,而只是一个指示,以防它在未来发生变化或不符合您的预期(例如,如果您使用函数 as.tibble() 而不是 as_tibble( ) 它会警告你 as.tibble() 已被弃用)
    【解决方案3】:

    虽然上面的响应可以添加中断,但这些实际上并不是minor_breaks,为此你可以使用annotation_ticks函数,它的工作原理类似于annotation_logticks

    代码功能可用here.你可能需要加载grid

    annotation_ticks <- function(sides = "b",
                                 scale = "identity",
                                 scaled = TRUE,
                                 short = unit(0.1, "cm"),
                                 mid = unit(0.2, "cm"),
                                 long = unit(0.3, "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,
          short = short,
          mid = mid,
          long = long,
          colour = colour,
          size = size,
          linetype = linetype,
          alpha = alpha,
          ticks_per_base = ticks_per_base,
          delog = delog,
          ...
        )
      )
    }
    
    #' Base ggproto classes for ggplot2
    #'
    #' If you are creating a new geom, stat, position, or scale in another package,
    #' you'll need to extend from ggplot2::Geom, ggplot2::Stat, ggplot2::Position, or ggplot2::Scale.
    #'
    #' @seealso \code{\link[ggplot2]{ggplot2-ggproto}}
    #' @usage NULL
    #' @format NULL
    #' @rdname ggplot2-ggproto
    #' @export
    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,
                            short = unit(0.1, "cm"),
                            mid = unit(0.2, "cm"),
                            long = unit(0.3, "cm"),
                            ticks_per_base = base - 1,
                            delog = c(x = TRUE, y = TRUE)) {
        ticks <- list()
    
        # Convert these units to numbers so that they can be put in data frames
        short <- convertUnit(short, "cm", valueOnly = TRUE)
        mid <- convertUnit(mid, "cm", valueOnly = TRUE)
        long <- convertUnit(long, "cm", valueOnly = TRUE)
    
        for (s in 1:length(sides)) {
          if (grepl("[b|t]", sides[s])) {
    
            # Get positions of x tick marks
            xticks <- calc_ticks(
              base = base[s],
              minpow = floor(panel_scales$x.range[1]),
              maxpow = ceiling(panel_scales$x.range[2]),
              majorTicks = panel_scales$x.major_source,
              start = 0,
              shortend = short,
              midend = mid,
              longend = long,
              ticks_per_base = ticks_per_base[s],
              delog = delog[s]
            )
    
            if (scaled) {
              if (!delog[s]) {
                xticks$value <- log(xticks$value, base[s])
              }
            }
    
            names(xticks)[names(xticks) == "value"] <- "x" # Rename to 'x' for coordinates$transform
    
            xticks <- coord$transform(xticks, panel_scales)
    
            # Make the grobs
            if (grepl("b", sides[s])) {
              ticks$x_b <- with(
                data,
                segmentsGrob(
                  x0 = unit(xticks$x, "native"),
                  x1 = unit(xticks$x, "native"),
                  y0 = unit(xticks$start, "cm"),
                  y1 = unit(xticks$end, "cm"),
                  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$x, "native"),
                  x1 = unit(xticks$x, "native"),
                  y0 = unit(1, "npc") - unit(xticks$start, "cm"),
                  y1 = unit(1, "npc") - unit(xticks$end, "cm"),
                  gp = gpar(
                    col = alpha(colour, alpha),
                    lty = linetype,
                    lwd = size * .pt
                  )
                )
              )
            }
          }
    
    
          if (grepl("[l|r]", sides[s])) {
            yticks <- calc_ticks(
              base = base[s],
              minpow = floor(panel_scales$y.range[1]),
              maxpow = ceiling(panel_scales$y.range[2]),
              majorTicks = panel_scales$y.major_source,
              start = 0,
              shortend = short,
              midend = mid,
              longend = long,
              ticks_per_base = ticks_per_base[s],
              delog = delog[s]
            )
    
            if (scaled) {
              if (!delog[s]) {
                yticks$value <- log(yticks$value, base[s])
              }
            }
    
            names(yticks)[names(yticks) == "value"] <- "y" # Rename to 'y' for coordinates$transform
            yticks <- coord$transform(yticks, panel_scales)
    
            # Make the grobs
            if (grepl("l", sides[s])) {
              ticks$y_l <- with(
                data,
                segmentsGrob(
                  y0 = unit(yticks$y, "native"),
                  y1 = unit(yticks$y, "native"),
                  x0 = unit(yticks$start, "cm"),
                  x1 = unit(yticks$end, "cm"),
                  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$y, "native"),
                  y1 = unit(yticks$y, "native"),
                  x0 = unit(1, "npc") - unit(yticks$start, "cm"),
                  x1 = unit(1, "npc") - unit(yticks$end, "cm"),
                  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)
    )
    
    
    # Calculate the position of log tick marks Returns data frame with: - value: the
    # position of the log tick on the data axis, for example 1, 2, ..., 9, 10, 20, ...
    # - start: on the other axis, start position of the line (usually 0) - end: on the
    # other axis, end position of the line (for example, .1, .2, or .3)
    calc_ticks <- function(base = 10,
                           ticks_per_base = base - 1,
                           minpow = 0,
                           maxpow = minpow + 1,
                           majorTicks = 0,
                           start = 0,
                           shortend = 0.1,
                           midend = 0.2,
                           longend = 0.3,
                           delog = FALSE) {
    
      # Number of blocks of tick marks
      reps <- maxpow - minpow
    
      # For base 10: 1, 2, 3, ..., 7, 8, 9, 1, 2, ...
      ticknums <- rep(seq(1, base - 1, length.out = ticks_per_base), reps)
    
      # For base 10: 1, 1, 1, ..., 1, 1, 1, 2, 2, ... (for example)
      powers <- rep(seq(minpow, maxpow - 1), each = ticks_per_base)
    
      ticks <- ticknums * base ^ powers
    
      ticks <- c(ticks, base ^ maxpow) # Add the last tick mark
    
      # Set all of the ticks short
      tickend <- rep(shortend, length(ticks))
    
      # Get the position within each cycle, 0, 1, 2, ..., 8, 0, 1, 2. ...
      cycleIdx <- ticknums - 1
    
      # Set the 'major' ticks long
      tickend[cycleIdx == 0] <- longend
    
      # Where to place the longer tick marks that are between each base For base 10, this
      # will be at each 5
      longtick_after_base <- floor(ticks_per_base / 2)
      tickend[cycleIdx == longtick_after_base] <- midend
    
      if (delog) {
        ticksCopy <- ticks
    
        regScale <- log(ticks, base)
    
        majorTicks <- sort(
          unique(
            c(
              minpow,
              regScale[which(regScale %in% majorTicks)],
              maxpow,
              majorTicks
            )
          )
        )
    
        expandScale <- c()
    
        if (length(majorTicks) > 1) {
          for (i in 1:(length(majorTicks) - 1)) {
            expandScale <- c(
              expandScale,
              seq(majorTicks[i], majorTicks[i + 1], length.out = (ticks_per_base + 1))
            )
          }
    
          ticks <- unique(expandScale)
    
          # Set all of the ticks short
          tickend <- rep(shortend, length(ticks))
    
          # Set the 'major' ticks long
          tickend[which(ticks %in% majorTicks)] <- longend
        }
      }
    
      tickdf <- data.frame(value = ticks, start = start, end = tickend)
    
      tickdf
    }
    

    【讨论】:

    • 多么令人印象深刻的答案!这可能会让我的一天:) 唯一留给我的问题:我怎样才能让刻度出现在情节之外而不是情节内部?
    • 这是个好问题,我在这里试过但找不到答案
    • 这个问题在这里解决了:stackoverflow.com/questions/58485334/…
    【解决方案4】:

    这将在确切的情况下做到这一点:

    scale_x_continuous(breaks= seq(1900,2000,by=10), 
                      labels = c(1900, rep("",4), 1950, rep("",4), 2000), 
                      limits = c(1900,2000), expand = c(0,0)) +
    

    这是一个函数,它不是防弹的,但可以在开始和结束主要标签与 at 参数的开始和结束值对齐时插入空白标签:

    insert_minor <- function(major_labs, n_minor) {labs <- 
                                  c( sapply( major_labs, function(x) c(x, rep("", 4) ) ) )
                                  labs[1:(length(labs)-n_minor)]}
    

    测试:

    p <- ggplot(df, aes(x=x, y=y))
      p + geom_line() + 
      scale_x_continuous(breaks= seq(1900,2000,by=10), 
                         labels = insert_minor( seq(1900, 2000, by=50), 4 ), 
                         limits = c(1900,2000), expand = c(0,0)) +
      scale_y_continuous(breaks = c(20,40,60,80), limits = c(0,100)) +
      theme(legend.position="none", panel.background = element_blank(), 
            axis.line = element_line(color='black'), panel.grid.minor = element_blank())
    

    【讨论】:

    • 感谢 DWin。这将适用于我正在做的事情。甚至没有想过用空白标签有效地使它们成为所有主要的刻度线。
    • 我猜真正需要的是将空格插入序列的函数。可能是一个很好的后续问题。
    • Re: 在序列中插入空格:here's 我用一个小刻度标签的示例应用来解决这个问题
    • 如果您在这里,请查看@adamdsmith 的解决方案 - 它既优雅又易于整合。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-02-27
    • 2017-09-23
    • 1970-01-01
    相关资源
    最近更新 更多