Search code examples
rlayoutplotscale

R: Matching x-axis scales on upper and lower plot using layout with base graphics


I am trying to arrange 3 plots together. All 3 plots have the same y axis scale, but the third plot has a longer x axis than the other two. I would like to arrange the first two plots side by side in the first row and then place the third plot on the second row aligned to the right. Ideally I would like the third plot's x values to align with plot 2 for the full extent of plot 2 and then continue on below plot one. I have seen some other postings about using the layout function to reach this general configuration (Arrange plots in a layout which cannot be achieved by 'par(mfrow ='), but I haven't found anything on fine tuning the plots so that the scales match. Below is a crappy picture that should be able to get the general idea across.

horrible picture


Solution

  • I thought you could do this by using par("plt"), which returns the coordinates of the plot region as a fraction of the total figure region, to programmatically calculate how much horizontal space to allocate to the bottom plot. But even when using this method, manual adjustments are necessary. Here's what I've got for now.

    First, set the plot margins to be a bit thinner than the default. Also, las=1 rotates the y-axis labels to be horizontal, and xaxs="i" (default is "r") sets automatic x-axis padding to zero. Instead, we'll set the amount of padding we want when we create the plots.

    par(mar=c(3,3,0.5,0.5), las=1, xaxs="i")
    

    Some fake data:

    dat1=data.frame(x=seq(-5000,-2500,length=100), y=seq(-0.2,0.6,length=100))
    dat2=data.frame(x=seq(-6000,-2500,length=100), y=seq(-0.2,0.6,length=100))
    

    Create a layout matrix:

    # Coordinates of plot region as a fraction of the total figure region
    # Order c(x1, x2, y1, y2)
    pdim = par("plt")
    
    # Constant padding value for left and right ends of x-axis
    pad = 0.04*diff(range(dat1$x))
    
    # If total width of the two top plots is 2 units, then the width of the 
    #  bottom right plot is: 
    p3w = diff(pdim[1:2]) * (diff(range(dat2$x)) + 2*pad)/(diff(range(dat1$x)) + 2*pad) + 
      2*(1-pdim[2]) + pdim[1]
    
    # Create a layout matrix with 200 "slots"
    n=200
    
    # Adjustable parameter for fine tuning to get top and bottom plot lined up
    nudge=2
    
    # Number of slots needed for the bottom right plot
    l = round(p3w/2 * n) - nudge
    
    # Create layout matrix
    layout(matrix(c(rep(1:2, each=0.5*n), rep(4:3,c(n - l, l))), nrow=2, byrow=TRUE))
    

    Now create the graphs: The two calls to abline are just to show us whether the graphs' x-axes line up. If not, we'll change the nudge parameter and run the code again. Once we've got the layout we want, we can run all the code one final time without the calls to abline.

    # Plot first two graphs
    with(dat1, plot(x,y, xlim=range(dat1$x) + c(-pad,pad)))
    with(dat1, plot(x,y, xlim=range(dat1$x) + c(-pad,pad)))
    abline(v=-5000, xpd=TRUE, col="red")
    
    # Lower right plot
    plot(dat2, xaxt="n", xlim=range(dat2$x) + c(-pad,pad))
    abline(v=-5000, xpd=TRUE, col="blue")
    axis(1, at=seq(-6000,-2500,500))
    

    Here's what we get with nudge=2. Note the plots are lined up, but this is also affected by the pixel size of the saved plot (for png files), and I adjusted the size to get the upper and lower plots exactly lined up.

    I would have thought that casting all the quantities in ratios that are relative to the plot area (by using par("plt")) would have both ensured that the upper and lower plots lined up and that they would stay lined up regardless of the number of pixels in the final image. But I must be missing something about how base graphics work or perhaps I've messed up a calculation (or both). In any case, I hope this helps you get the plot layout you wanted.

    enter image description here