How to lengthen specific tick marks in facet gridded ggplot?
Asked Answered
S

2

5

I want longer tick marks for those with labels in a facet grid. So I worked through this attempt and tried to adapt it to facet gridded plots like so:

Defining breaks and labels, minor and major:

range.f <- range(unique(df1$weeks))
minor.f <- 1  # every 1 week, NOTE: range.f[2] should be divisible by minor.f!
major.f <- 5  # every 5 weeks

breaks.f <- seq(range.f[1], range.f[2], minor.f)

every_nth.lt <- function (x, nth) {x[1:nth != 1] <- ""; x}
# (lite version of https://mcmap.net/q/371811/-insert-blanks-into-a-vector-for-e-g-minor-tick-labels-in-r
# works better for me than `insert_minor()`)

labels.f <- every_nth.lt(sequence(range.f[2]), major.f)

n_minor.f <- major.f / minor.f - 1

Normal plot:

library(ggplot2)
p.f <- ggplot(df1, aes(weeks, births)) +
  geom_bar(stat="identity", fill="#F48024") + theme_bw() +
  scale_x_continuous(breaks=breaks.f, labels=labels.f) +
  coord_cartesian(xlim=range.f) +
  facet_wrap(year ~ .) +
  theme(panel.grid = element_blank(),
        axis.text.x = element_text(margin=margin(t=5, unit="pt")))

Manipulating plot:

g.f <- ggplotGrob(p.f)
xaxis.f <- g.f$grobs[grep("^axis-b", g.f$layout$name)]  # get x-axes
ticks.f <- do.call(c, lapply(lapply(xaxis.f, "["), 
                             function(x) x$children[[2]]))  # get ticks
marks.f <- ticks.f$grobs[[1]]  # get tick marks
# editing y-positions of tick marks
marks.f$y <- unit.c(unit.c(unit(1, "npc") - unit(6, "pt"), 
                           unit(1, "npc"), 
                           rep(unit.c(unit(1, "npc") - unit(3, "pt"), 
                                      unit(1, "npc")), n_minor.f)))

# putting tick marks back into plot
ticks.f$grobs[[1]] <- marks.f
for(i in seq_along(xaxis.f)) {
  xaxis.f[[i]]$children[[2]]$grob <- ticks.f[[i]]
}
g.f$grobs[grep("^axis-b", g.f$layout$name)] <- xaxis.f

Drawing the plot:

library(grid)
grid.newpage()
grid.draw(g.f)

Yielding:

enter image description here

I followed all the steps of the linked answer, just adapted it to the situation that there are lists in the grob. But, the longer tick marks won't show up.

Does anybody see what I did wrong?

Or, maybe is there another way how to lengthen the axis ticks of those axis ticks which have labels?

Expected Output:

At the end the tick marks of all three plots should look like this:

enter image description here


Data:

tmp <- data.frame(date=as.Date(sample(1:1095, 10000, replace=TRUE), 
                               origin="2014-01-01"),
                  births=sample(0:10, 10000, replace=TRUE))
tmp$year <- factor(substr(tmp$date, 1, 4))
df1 <- aggregate(births ~ date + year, tmp, sum)
rm(tmp)  # remove tmp
df1$weeks <- as.integer(strftime(lubridate::floor_date(as.Date(df1$date, 
                                                               format="%m/%d/%Y"), 
                                                       unit="week"), "%W")) + 1
Statampere answered 6/12, 2018 at 22:32 Comment(0)
I
1

I'm sure you could improve upon this. I just worked through it and got things correctly pulled out, and put back in. Mostly by comparing it to a single plot, and then making it loop over a list of grobs.

The range and breaks may need to change, since here they're all the same, but with different x-axes you could customize the breaks appropriately.

tmp <- data.frame(date=as.Date(sample(1:1095, 10000, replace=TRUE), 
                               origin="2014-01-01"),
                  births=sample(0:10, 10000, replace=TRUE))
tmp$year <- factor(substr(tmp$date, 1, 4))
df1 <- aggregate(births ~ date + year, tmp, sum)
rm(tmp)  # remove tmp
df1$weeks <- as.integer(strftime(lubridate::floor_date(as.Date(df1$date, 
                                                               format="%m/%d/%Y"), 
                                                       unit="week"), "%W")) + 1

# breaks and labels, minor and major
range.f <- 1:(max(unique(df1$weeks)))
minor.f <- 1  # every 1 week, NOTE: range.f[2] should be divisible by minor.f!
major.f <- 5  # every 5 weeks

breaks.f <- seq(min(range.f), max(range.f), minor.f)

every_nth.lt <- function (x, nth) {x[1:nth != 1] <- ""; x}
# (lite version of https://mcmap.net/q/371811/-insert-blanks-into-a-vector-for-e-g-minor-tick-labels-in-r)

labels.f <- every_nth.lt(range.f, major.f)

n_minor.f <- major.f / minor.f - 1

# plot
library(ggplot2)
library(grid)
p.f <- ggplot(df1, aes(weeks, births)) +
  geom_bar(stat="identity", fill="#F48024") + theme_bw() +
  scale_x_continuous(breaks=breaks.f, labels=labels.f) +
  coord_cartesian(xlim=range.f) +
  facet_wrap(year ~ .) +
  theme(panel.grid = element_blank(),
        axis.text.x = element_text(margin=margin(t=5, unit="pt")))

# manipulating plot
g.f <- ggplotGrob(p.f)
xaxis.f <- g.f$grobs[grep("^axis-b", g.f$layout$name)]  # get x-axes


ticks.f <- c()
for(i in seq_along(xaxis.f)) {
  ticks.f[[i]] <- xaxis.f[[i]]$children[[2]]
}


marks.f <- c()
for(i in seq_along(ticks.f)) {
  marks.f[[i]] <- ticks.f[[i]][1]$grobs
}



# editing y-positions of tick marks
for(i in seq_along(marks.f)) {
  marks.f[[i]][[1]]$y <- unit.c(unit.c(unit(1, "npc") - unit(6, "pt"), 
                                       unit(1, "npc"), 
                                       rep(unit.c(unit(1, "npc") - unit(3, "pt"), 
                                                  unit(1, "npc")), n_minor.f)))
}
# putting tick marks back into plot
for(i in seq_along(ticks.f)) {
  ticks.f[[i]]$grobs[[1]] <- marks.f[[i]][[1]]
}

for(i in seq_along(xaxis.f)) {
  xaxis.f[[i]]$children[[2]] <- ticks.f[[i]]
}

g.f$grobs[grep("^axis-b", g.f$layout$name)] <- xaxis.f

# plot
grid.newpage()
grid.draw(g.f)

Irina answered 7/12, 2018 at 17:30 Comment(1)
Great, you did it! Thanks, that's a pretty fiddly one. You motivated me to finish the way I started (see below).Statampere
S
3

And here's the revised code of the way I started, with a few less for loops.

# Defining breaks and labels, minor and major:

range.f <- range(unique(df1$weeks))
minor.f <- 1  # every 1 week, NOTE: range.f[2] should be divisible by minor.f!
major.f <- 5  # every 5 weeks

breaks.f <- seq(range.f[1], range.f[2], minor.f)

every_nth.lt <- function (x, nth) {x[1:nth != 1] <- ""; x}
# (lite version of https://mcmap.net/q/371811/-insert-blanks-into-a-vector-for-e-g-minor-tick-labels-in-r
# works better for me than `insert_minor()`)

labels.f <- every_nth.lt(sequence(range.f[2]), major.f)

n_minor.f <- major.f / minor.f - 1

# Normal plot:

library(ggplot2)
p.f <- ggplot(df1, aes(weeks, births)) +
  geom_bar(stat="identity", fill="#F48024") + theme_bw() +
  scale_x_continuous(breaks=breaks.f, labels=labels.f) +
  coord_cartesian(xlim=range.f) +
  facet_wrap(year ~ .) +
  theme(panel.grid = element_blank(),
        axis.text.x = element_text(margin=margin(t=5, unit="pt")))

# Manipulating plot:

g.f <- ggplotGrob(p.f)
xaxis.f <- g.f$grobs[grep("^axis-b", g.f$layout$name)]  # get x-axes

ticks.f <- lapply(lapply(xaxis.f, "["), 
                   function(x) x$children[[2]])  # get ticks

marks.f <- lapply(lapply(ticks.f, "["), 
                   function(x) x[1]$grobs)  # get ticks

# editing y-positions of tick marks
library(grid)
marks.f <- lapply(marks.f, function(x) {
  x[[1]]$y <- unit.c(unit.c(unit(1, "npc") - unit(6, "pt"), 
                            unit(1, "npc"),
                            rep(unit.c(unit(1, "npc") - unit(3, "pt"), 
                                       unit(1, "npc")), n_minor.f)))
  x
  })

# putting tick marks back into plot
for(i in seq_along(ticks.f)) {
  ticks.f[[i]]$grobs[[1]] <- marks.f[[i]][[1]]
}

for(i in seq_along(xaxis.f)) {
  xaxis.f[[i]]$children[[2]] <- ticks.f[[i]]
}

g.f$grobs[grep("^axis-b", g.f$layout$name)] <- xaxis.f

# Drawing the plot:

grid.newpage()
grid.draw(g.f)

data

tmp <- data.frame(date=as.Date(sample(1:1095, 10000, replace=TRUE), 
                               origin="2014-01-01"),
                  births=sample(0:10, 10000, replace=TRUE))
tmp$year <- factor(substr(tmp$date, 1, 4))
df1 <- aggregate(births ~ date + year, tmp, sum)
rm(tmp)  # remove tmp
df1$weeks <- as.integer(strftime(lubridate::floor_date(as.Date(df1$date, 
                                                               format="%m/%d/%Y"), 
                                                       unit="week"), "%W")) + 1
Statampere answered 9/12, 2018 at 13:14 Comment(0)
I
1

I'm sure you could improve upon this. I just worked through it and got things correctly pulled out, and put back in. Mostly by comparing it to a single plot, and then making it loop over a list of grobs.

The range and breaks may need to change, since here they're all the same, but with different x-axes you could customize the breaks appropriately.

tmp <- data.frame(date=as.Date(sample(1:1095, 10000, replace=TRUE), 
                               origin="2014-01-01"),
                  births=sample(0:10, 10000, replace=TRUE))
tmp$year <- factor(substr(tmp$date, 1, 4))
df1 <- aggregate(births ~ date + year, tmp, sum)
rm(tmp)  # remove tmp
df1$weeks <- as.integer(strftime(lubridate::floor_date(as.Date(df1$date, 
                                                               format="%m/%d/%Y"), 
                                                       unit="week"), "%W")) + 1

# breaks and labels, minor and major
range.f <- 1:(max(unique(df1$weeks)))
minor.f <- 1  # every 1 week, NOTE: range.f[2] should be divisible by minor.f!
major.f <- 5  # every 5 weeks

breaks.f <- seq(min(range.f), max(range.f), minor.f)

every_nth.lt <- function (x, nth) {x[1:nth != 1] <- ""; x}
# (lite version of https://mcmap.net/q/371811/-insert-blanks-into-a-vector-for-e-g-minor-tick-labels-in-r)

labels.f <- every_nth.lt(range.f, major.f)

n_minor.f <- major.f / minor.f - 1

# plot
library(ggplot2)
library(grid)
p.f <- ggplot(df1, aes(weeks, births)) +
  geom_bar(stat="identity", fill="#F48024") + theme_bw() +
  scale_x_continuous(breaks=breaks.f, labels=labels.f) +
  coord_cartesian(xlim=range.f) +
  facet_wrap(year ~ .) +
  theme(panel.grid = element_blank(),
        axis.text.x = element_text(margin=margin(t=5, unit="pt")))

# manipulating plot
g.f <- ggplotGrob(p.f)
xaxis.f <- g.f$grobs[grep("^axis-b", g.f$layout$name)]  # get x-axes


ticks.f <- c()
for(i in seq_along(xaxis.f)) {
  ticks.f[[i]] <- xaxis.f[[i]]$children[[2]]
}


marks.f <- c()
for(i in seq_along(ticks.f)) {
  marks.f[[i]] <- ticks.f[[i]][1]$grobs
}



# editing y-positions of tick marks
for(i in seq_along(marks.f)) {
  marks.f[[i]][[1]]$y <- unit.c(unit.c(unit(1, "npc") - unit(6, "pt"), 
                                       unit(1, "npc"), 
                                       rep(unit.c(unit(1, "npc") - unit(3, "pt"), 
                                                  unit(1, "npc")), n_minor.f)))
}
# putting tick marks back into plot
for(i in seq_along(ticks.f)) {
  ticks.f[[i]]$grobs[[1]] <- marks.f[[i]][[1]]
}

for(i in seq_along(xaxis.f)) {
  xaxis.f[[i]]$children[[2]] <- ticks.f[[i]]
}

g.f$grobs[grep("^axis-b", g.f$layout$name)] <- xaxis.f

# plot
grid.newpage()
grid.draw(g.f)

Irina answered 7/12, 2018 at 17:30 Comment(1)
Great, you did it! Thanks, that's a pretty fiddly one. You motivated me to finish the way I started (see below).Statampere

© 2022 - 2025 — McMap. All rights reserved.