I would like to create a ggplot2 with both the y-axis and x-axis labels on the inside, i.e., facing inwards and placed inside the plot area.
This previous SO answer by Z.Lin solves it for the case of the y-axis, and I've got that working just fine. But extending that approach to both axes has me stumped. grobs
is hard, I think.
So I attempted to start small, by adapting Z.Lin's code to work for the x-axis instead of the y-axis, but I have not been able to achieve even that. grobs
is really complicated. My attempt (below) runs without errors/warnings until grid.draw()
, where it crashes and burns (I think I'm misusing some args somewhere, but I can't identify which and at this point I'm just guessing).
# locate the grob that corresponds to x-axis labels
x.label.grob <- gp$grobs[[which(gp$layout$name == "axis-b")]]$children$axis
# remove x-axis labels from the plot, & shrink the space occupied by them
gp$grobs[[which(gp$layout$name == "axis-b")]] <- zeroGrob()
gp$widths[gp$layout$l[which(gp$layout$name == "axis-b")]] <- unit(0, "cm")
# create new gtable
new.x.label.grob <- gtable::gtable(widths = unit(1, "npc"))
# place axis ticks in the first row
new.x.label.grob <-
gtable::gtable_add_rows(
new.x.label.grob,
heights = x.label.grob[["heights"]][1])
new.x.label.grob <-
gtable::gtable_add_grob(
new.x.label.grob,
x.label.grob[["grobs"]][[1]],
t = 1, l = 1)
# place axis labels in the second row
new.x.label.grob <-
gtable::gtable_add_rows(
new.x.label.grob,
heights = x.label.grob[["heights"]][2])
new.x.label.grob <-
gtable::gtable_add_grob(
new.x.label.grob,
x.label.grob[["grobs"]][[2]],
t = 1, l = 2)
# add third row that takes up all the remaining space
new.x.label.grob <-
gtable::gtable_add_rows(
new.x.label.grob,
heights = unit(1, "null"))
gp <-
gtable::gtable_add_grob(
x = gp,
grobs = new.x.label.grob,
t = gp$layout$t[which(gp$layout$name == "panel")],
l = gp$layout$l[which(gp$layout$name == "panel")])
grid.draw(gp)
# Error in unit(widths, default.units) :
# 'x' and 'units' must have length > 0
I guess my question can be split into three semi-independent parts, where each subsequent question supersedes the earlier ones (so if you can answer a later question, there will be no need to bother with the earlier ones):
- can anyone adapt the existing answer to the x-axis?
- can anyone provide code in that vein to get both axes inside?
- does anyone know of a neater way to achieve both axes inside for ggplot2?
Here's my MWE (mostly replicating Z.Lin's answer, but with new data):
library(dplyr)
library(magrittr)
library(ggplot2)
library(grid)
library(gtable)
library(errors)
df <- structure(list(
temperature = c(200, 300, 400, 500, 600, 700, 800, 900),
diameter =
structure(
c(13.54317, 10.32521, 10.23137, 17.90464, 29.98183, 55.65514, 101.60747, 147.3074),
id = "<environment>",
errors = c(1.24849, 0.46666, 0.36781, 0.48463, 0.94639, 1.61459, 6.98346, 12.18353),
class = "errors")),
row.names = c(NA, -8L),
class = "data.frame")
p <- ggplot() +
geom_smooth(data = df %>% filter(temperature >= 400),
aes(x = temperature, y = diameter),
method = "lm", formula = "y ~ x",
se = FALSE, fullrange = TRUE) +
# experimental errors as red ribbon (instead of errorbars)
geom_ribbon(data = df,
aes(x = temperature,
ymin = errors_min(diameter),
ymax = errors_max(diameter)),
fill = alpha("red", 0.2),
colour = alpha("red", 0.2)) +
geom_point(data = df,
aes(x = temperature, y = diameter),
size = 0.7) +
geom_line(data = df,
aes(x = temperature, y = diameter),
size = 0.15) +
scale_x_continuous(breaks = seq(200, 900, 200)) +
scale_y_log10(breaks = c(10, seq(30, 150, 30)),
labels = c("10", "30", "60", "90", "120", "150=d/nm")) +
theme(panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
axis.title.y = element_blank(),
axis.text.y = element_text(hjust = 0))
# convert from ggplot to grob object
gp <- ggplotGrob(p)
y.label.grob <- gp$grobs[[which(gp$layout$name == "axis-l")]]$children$axis
gp$grobs[[which(gp$layout$name == "axis-l")]] <- zeroGrob()
gp$widths[gp$layout$l[which(gp$layout$name == "axis-l")]] <- unit(0, "cm")
new.y.label.grob <- gtable::gtable(heights = unit(1, "npc"))
new.y.label.grob <-
gtable::gtable_add_cols(
new.y.label.grob,
widths = y.label.grob[["widths"]][2])
new.y.label.grob <-
gtable::gtable_add_grob(
new.y.label.grob,
y.label.grob[["grobs"]][[2]],
t = 1, l = 1)
new.y.label.grob <-
gtable::gtable_add_cols(
new.y.label.grob,
widths = y.label.grob[["widths"]][1])
new.y.label.grob <-
gtable::gtable_add_grob(
new.y.label.grob,
y.label.grob[["grobs"]][[1]],
t = 1, l = 2)
new.y.label.grob <-
gtable::gtable_add_cols(
new.y.label.grob,
widths = unit(1, "null"))
gp <-
gtable::gtable_add_grob(
x = gp,
grobs = new.y.label.grob,
t = gp$layout$t[which(gp$layout$name == "panel")],
l = gp$layout$l[which(gp$layout$name == "panel")])
grid.draw(gp)
> sessionInfo()
R version 3.6.2 (2019-12-12)
Platform: x86_64-pc-linux-gnu (64-bit)
Running under: Ubuntu 18.04.5 LTS
Matrix products: default
BLAS: /usr/lib/x86_64-linux-gnu/blas/libblas.so.3.7.1
LAPACK: /usr/lib/x86_64-linux-gnu/lapack/liblapack.so.3.7.1
locale:
[1] LC_CTYPE=en_GB.UTF-8 LC_NUMERIC=C
[3] LC_TIME=en_GB.UTF-8 LC_COLLATE=en_GB.UTF-8
[5] LC_MONETARY=en_GB.UTF-8 LC_MESSAGES=en_GB.UTF-8
[7] LC_PAPER=en_GB.UTF-8 LC_NAME=C
[9] LC_ADDRESS=C LC_TELEPHONE=C
[11] LC_MEASUREMENT=en_GB.UTF-8 LC_IDENTIFICATION=C
attached base packages:
[1] grid stats graphics grDevices utils datasets methods
[8] base
other attached packages:
[1] errors_0.3.4 gtable_0.3.0 ggplot2_3.3.2 magrittr_1.5 dplyr_1.0.2
loaded via a namespace (and not attached):
[1] rstudioapi_0.11 splines_3.6.2 tidyselect_1.1.0 munsell_0.5.0
[5] lattice_0.20-41 colorspace_1.4-1 R6_2.5.0 rlang_0.4.8
[9] tools_3.6.2 nlme_3.1-148 mgcv_1.8-31 withr_2.3.0
[13] ellipsis_0.3.1 digest_0.6.27 yaml_2.2.1 tibble_3.0.4
[17] lifecycle_0.2.0 crayon_1.3.4 Matrix_1.2-18 purrr_0.3.4
[21] farver_2.0.3 vctrs_0.3.4 glue_1.4.2 compiler_3.6.2
[25] pillar_1.4.6 generics_0.1.0 scales_1.1.1 pkgconfig_2.0.3