Search code examples
rplotaxis-labels

Automatically choose "line" in axis with stacked axis labels in R


Long story short, I'm creating a type of plot where on the x-axis, I have two different variables whose values are crossed with one another (in this minimal example, I have a=1,2 and b=1:5). I want to show how another variable (the one on the y axis) varies as a function of both a and b. What I'm trying to do is figure out a way to automatically place the second group of labels (in this case, the "b" variable). Here's a minimal example:

set.seed(1)
par(mar=c(7,3.5,1,1))
plot(1:10, 1:10+runif(10, -1, 1), xaxt="n", xlab="", ylab="", type="o")
axis(1, 1:10, rep(c("a=1", "a=2"), 5), las=2)
axis(1, seq(from=1.5, to=9.5, by=2), paste0("b=", 1:5), las=2, line=2, lwd=0)

Which produces the following graphic: enter image description here

In this case, I got lucky and chose "line=2" as the correct placement. But if I modify things a bit (and make my variable labels a little more "bulky"):

set.seed(1)
par(mar=c(7,3.5,1,1))
plot(1:10, 1:10+runif(10, -1, 1), xaxt="n", xlab="", ylab="", type="o")
axis(1, 1:10, rep(c("Control", "Treatment"), 5), las=2)
axis(1, seq(from=1.5, to=9.5, by=2), paste0("b=", 1:5), las=2, line=2, lwd=0)

Now the groups of labels overlap:

enter image description here

Is there a way to automatically determine what "line" the axis is at so I can place my second group of labels without overlapping the first?


Solution

  • Here's something you could try. Instead of using line, which can be unreliable as you noted, you could pad white spaces to you second axis call using sprintf. Below, I first calculate the maximum number of characters in the first axis call, and multiply by 2.5, which seems to work well in this case. You might want to tweak this. Then, I use the default line=1 but the with spaces will "push" the second axis call away from the axis line. BTW, you might want to do something like max(nchar(first axis)) + max(nchar(second axis)) to calculate the padding.Finally, note that the "-" sign inside sprintf means add white spaces to the right.

    set.seed(1)
    par(mar=c(7,3.5,1,1))
    
    max_nchar <- max(nchar(rep(c("Control", "Treatment"), 5))) *2.5
    
    plot(1:10, 1:10+runif(10, -1, 1), xaxt="n", xlab="", ylab="", type="o")
    axis(1, 1:10, rep(c("Control", "Treatment"), 5), las=2)
    axis(1, seq(from=1.5, to=9.5, by=2), 
     sprintf(paste0("%-",max_nchar,"s"), paste0("b=", 1:5)), las=2, lwd=0)
    

    enter image description here