【发布时间】:2020-04-07 18:47:43
【问题描述】:
我正在寻找一种在不使用 coord_cartesian(clip = "off") 的情况下向 ggplots 添加次要刻度线的方法。或者一种可重现地将裁剪应用于 x 轴而不是 y 轴的方法,反之亦然。
到目前为止,我一直在使用这个出色的答案 here 中定义的 annotation_ticks() 函数和 GeomTicks geom(进行了一些小的修改以使其与 ggplot2 v3.3.0 一起使用)。不幸的是,要使刻度线注释出现在绘图外部,必须使用coord_cartesian(clip = "off"),这意味着绘图区域之外的任何其他内容也会被暴露(参见下面的reprex)。
或者,也许有一种方法可以利用 ggplot2 v3.3.0 的任何新功能来绘制小刻度,而不是作为注释,而是作为轴/绘图的实际部分,以便可以将它们绘制到外部绘图区。
我不是软件开发人员,但也许有人可以使用 register_theme_elements 定义一个名为 axis.minor.ticks 的新主题元素,其行为类似于 axis.ticks,但从 panel_params$y$break_positions_minor 而不是 panel_params$y$break_positions 获取小刻度的适当位置.或者以某种方式使用新的guide_x() S3 函数。
任何帮助将不胜感激!
注解函数和ggproto对象
annotation_ticks() 函数(合并 this fix 用于分面问题):
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,
data = data.frame(x = NA),
...) {
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,
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 对象(现在与 ggplot2 v3.3.0 一起使用):
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])) {
# for ggplot2 < 3.3.0 use: xticks <- panel_params$x.minor
if (utils::packageVersion("ggplot2") >= "3.2.1.9000") {
x_minor_breaks <- panel_scales$x$break_positions_minor()
x_major_breaks <- panel_scales$x$break_positions()
} else {
x_minor_breaks <- panel_scales$x.minor
x_major_breaks <- panel_scales$x.major
}
xticks <- setdiff(x_minor_breaks, x_major_breaks)
# 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])) {
# for ggplot2 < 3.3.0 use: yticks <- panel_params$y.minor
if (utils::packageVersion("ggplot2") >= "3.2.1.9000") {
y_minor_breaks <- panel_scales$y$break_positions_minor()
y_major_breaks <- panel_scales$y$break_positions()
} else {
y_minor_breaks <- panel_scales$y.minor
y_major_breaks <- panel_scales$y.major
}
yticks <- setdiff(y_minor_breaks, y_major_breaks)
# 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)
)
带有coord_cartesian(clip = "on")的图表
线宽很粗的列看起来不错,但看不到刻度注释。
library(ggplot2)
library(grid)
ggplot(mpg, aes(x = class, y = displ, fill = class)) +
stat_summary(fun = mean, geom = "col", colour = "black", size = 1) +
theme_classic(base_size = 8) +
scale_y_continuous(limits = c(0, 8), expand = c(0, 0)) +
annotation_ticks(sides = "l", ticklength = -1 * unit(0.2, "cm")) +
coord_cartesian(clip = "on")
ggsave("clip_on.png", device = "png", width = 4, height = 3)
用coord_cartesian(clip = "off") 绘制图表
勾选注释可见,但线宽很粗的列显示在绘图区域之外。
ggplot(mpg, aes(x = class, y = displ, fill = class)) +
stat_summary(fun = mean, geom = "col", colour = "black", size = 1) +
theme_classic(base_size = 8) +
scale_y_continuous(limits = c(0, 8), expand = c(0, 0)) +
annotation_ticks(sides = "l", ticklength = -1 * unit(0.2, "cm")) +
coord_cartesian(clip = "off")
ggsave("clip_off.png", device = "png", width = 4, height = 3)
【问题讨论】: