Search code examples
rplotggplot2aspect-ratio

howto: Automatically set fixed coordinate ratio (coord_fixed) when x- and y-axis are on different scales?


My goal is to fix the coordinate aspect ratio of a plot from ggplot2 via coord_fixed(). I thought coord_fixed(ratio=1) did the job independently of the scales of the x- or y-axis. My intuition: the argument ratio refers to the ratio of the total range of coordinate x to the total range of coordinate y. Implying that a ratio of 1 always means that the x-axis will be as long as the y-axis in the plot.

Yet with x-coordinates in the 1000s and y-coordinates e.g. percent, coord_fixed does not behave not as I expect it.

2 Questions:

  1. Can you explain why coord_fixed takes the actual scale of the data into account but not the coordinate length as a whole?
  2. Can I change the coord_fixed programatically to always refer to the whole range of the x- and y-coordinate values?

Here's an illustration

library("ggplot2")
set.seed(123)
df = data.frame(x=runif(11)*1000,y=seq(0,.5,.05))
ggplot(df, aes(x,y)) +geom_point() +coord_fixed(1)

produces enter image description here

Rescaling the data by the ratio of x- and y-values in coord-fixed solves the issue

ggplot(df, aes(x,y)) +geom_point() +coord_fixed(1*max(df$x)/max(df$y))

However, this is not progammatically. I would have to specify the df$x manually to achieve the desired effect. See question 2: Is there a sensible way to automatize the re-scaling of the coordinates within coord_fixed depending on which data is on the x-/y-axis in my ggplot plot?

enter image description here


Solution

  • Can you plain why coord_fixed takes the actual scale of the data into account but not the coordinate length as a whole?

    That's the point of coord_fixed. It's especially useful when, e.g., x and y are measures of length in the same units. (Basically whenever x and y have the same units, coord_fixed with ratio = 1 is what you want.)

    For example, if my data is a square and a triangle, coord_fixed is the only way to make the square actually square

    shapes <- data.frame(x = c(1, 1, 2, 2, 1, 3, 3, 4, 3),
                         y = c(1, 2, 2, 1, 1, 1, 2, 1, 1),
                         name = c(rep("square", 5), rep("isosceles triangle", 4)))
    
    shape.plot <- ggplot(shapes, aes(x = x, y = y, group = name, color = name)) +
        geom_path()
    
    shape.plot # distorted
    shape.plot + coord_fixed() # square!
    

    Can I change the coord_fixed programatically to always refer to the whole range of the x- and y-coordinate values?

    I would recommend not overwriting it, you could try to create your own version much as in your answer (though if you want to pull the appropriate values out of the x and y specifications of aes() you'll have a challenge---and you'll learn more about ggplot's internal workings than I know). However, the default behavior (without specifying any coord) seems to be what you're looking for.

    If you compare

    # your code
    ggplot(df, aes(x,y)) + geom_point() + coord_fixed(1 * max(df$x) / max(df$y))
    
    # no coord at all
    ggplot(df, aes(x,y)) + geom_point()
    

    they're basically the same. So, the modification of coord_fixed you seem to be looking for is don't use coord_fixed.

    Aspect ratio of plot area (independent of coordinates): don't use coord_fixed

    Just found out about this from this semi-related post: if you want a specific aspect ratio of the plot area, you can get it with theme(), e.g.

    p1 <- ggplot(df, aes(x,y)) + geom_point()
    p1 + theme(aspect.ratio = 1)
    p1 + theme(aspect.ratio = (1 + sqrt(5))/ 2)  # golden ratio portrait
    p1 + theme(aspect.ratio = 2 / (1 + sqrt(5))) # golden ratio landscape
    

    This is, of course, data-agnostic. I think the take-home message is that if you want the scales of your data taken into account, relative to each other, use coord_fixed. If you want to change the aspect ratio of the plotting area but still fit the data, use theme(aspect.ratio). If you want to change the aspect ratio of a saved file, use the height and width arguments of your saving function.