65

I can force ggplot2 scatter plot to be square shaped with the same x and y scaling using xlim() and ylim(), but it needs manual calculation of the limits. Is there any more convenient way of doing it?

By square shape I mean two requirements:

  1. The same scale on x and y axis.
  2. The equal length of x and y axis.
zx8754
  • 52,746
  • 12
  • 114
  • 209
Ali
  • 9,440
  • 12
  • 62
  • 92
  • 2
    By "square shaped" do you mean you want the length of one unit in the `x` direction to be the same as in the `y` direction (meaning if `x` goes from 0 to 5 and `y` goes from 0 to 6 the `y` dimension will be one unit longer), or do you mean that you want the length of the entire x axis to be the same as the y axis (so in the previous example, each unit of `y` is shorter than each unit of `x` but the entire graph looks square)? – mathematical.coffee Nov 18 '12 at 23:41
  • 1
    @baptiste: I think you should post that as an answer. It appears to be the ggplot analogue of `asp=1` in base plotting. – IRTFM Nov 18 '12 at 23:44
  • @DWin It seems none of the proposed answers make the plot square shaped, they all make the x and y scales the same. – Ali Nov 19 '12 at 15:24
  • @baptiste Your answer makes the x and y scales the same, but does not necessarily produce a square shaped plot – Ali Nov 19 '12 at 15:25
  • 16
    if the shape matters, go with `theme(aspect.ratio=1)` – baptiste Nov 19 '12 at 18:37
  • 1
    but to answer your question, I don't think there is a function to do that in ggplot2. You could work with `expand_limits`, but I believe one way or another you'll have to compute min and max of your data manually. – baptiste Nov 19 '12 at 18:41
  • @Ali you cannot have both, **unless** you set your X and Y ranges to be the same (e.g. both X and Y will go from 0 to 5) - in which case calculate the range you want and use the answers listed. – mathematical.coffee Nov 20 '12 at 00:16
  • Only manual computation gets close for me. – PatrickT Nov 07 '17 at 19:43

6 Answers6

73

Note: for a square shape (irrespective of the data being plotted),

ggplot() + theme(aspect.ratio=1)

enter image description here

baptiste
  • 75,767
  • 19
  • 198
  • 294
  • 2
    This forces the plot background area to be square, not (necessarily) the plot data area. In addition, ``ggsave`` may not (almost surely will not) output a square image, but will pad it with white, so that if your background color is white, your saved graph will not appear to be square (and if your background is colored, it will be surrounded by white padding). Depending on your purpose, this option ought to used together with other options. You can always tweak ``width`` and ``height`` options inside ``ggsave``. – PatrickT Feb 09 '18 at 05:46
72

If you want to make the distance scale points the same, then use coord_fixed():

p <- ggplot(...)
p <- p + coord_fixed() # ratio parameter defaults to 1 i.e. y / x = 1

If you want to ensure that the resulting plot is square then you would also need to specify the x and y limits to be the same (or at least have the same range). xlim and ylim are both arguments to coord_fixed. So you could do this manually using those arguments. Or you could use a function to extract out limits from the data.

Jeromy Anglim
  • 33,939
  • 30
  • 115
  • 173
mys
  • 2,355
  • 18
  • 21
  • 13
    Thanks, it makes the x and y axis to be the same scale, but does not force the plot to be square shaped -i.e. the length of x and y axis can be different – Ali Nov 19 '12 at 14:28
7

Probably the ugliest code you'll see today, but it does the trick.

The ranges of your x and y axes are accessible from ggplot_build:

r<-max(abs(ggplot_build(your_plot)$panel$ranges[[1]]$x.range))
s<-max(abs(ggplot_build(your_plot)$panel$ranges[[1]]$y.range))
t<-round(max(r,s),1)
your_plot<-your_plot+coord_equal(xlim=c(-t,t),ylim=c(-t,t))
Benjamin W.
  • 46,058
  • 19
  • 106
  • 116
Ramon
  • 133
  • 1
  • 5
4

All the solutions provided in the previous answers do not work for my R version: R version 3.6.1.

ggplot_build(pot)$panel$ranges[[1]]$x.range # return NULL value

The solution mentioned by @Gerhard Burger in the linked URL works for my case:

r<-max(abs(layer_scales(plt)$x$range$range))
s<-max(abs(layer_scales(plt)$y$range$range))
t<-round(max(r,s),1)
plt<-plt+coord_equal(xlim=c(0,t),ylim=c(0,t))
Good Will
  • 1,220
  • 16
  • 10
4

Although an older post, this is still a top hit on google for me.

This function in the tune package seems to accomplish this now:

p <- 
  mtcars %>% 
  ggplot(aes(drat, wt)) + 
  geom_abline(slope = 1, intercept = 0) +
  geom_point()
p

enter image description here

p + tune::coord_obs_pred()

enter image description here

bryanL
  • 51
  • 2
2

Building on Ramons answer, this function works nicely for me and I consider it not as ugly, since one can hide the function definition...

squarePlot <- function(plt){
    return(plt+coord_equal()+
            expand_limits(x=ggplot_build(plt)$panel$ranges[[1]]$y.range,
                          y=ggplot_build(plt)$panel$ranges[[1]]$x.range))
}

just wrapping Ramon's code in a function didn't work out for me because the t variable is defined in the "wrong" environment.

  • I like the idea, but this was not working for my ggplot object. The closest I got was inside ``ggplot_build(qq)$plot$scales$scales``, but it still wasn't working. So I ended up computing the limits manually and scaling by 1.3... – PatrickT Nov 07 '17 at 19:37
  • 1
    The `ggplot_build` changes all the time, I think at the moment [this](https://stackoverflow.com/a/35372274/1439843) is the preferred method – Gerhard Burger Oct 10 '18 at 20:10