1

i have the following issue right now;

I want to create plots with ggplot2 where the elements panel.grid.major.x and panel.grid.major.y form squares within the plot. My solution so far includes defining the amount of major lines from the x- and y-axis of the plot as well as the option aspect.ratio in the theme options. Following code is a MWE, my actual code right now contains more options:

library(ggplot2)
#remotes::install_github("allisonhorst/palmerpenguins")
library(palmerpenguins)


equal_breaks2 <- function(n = 3, s = 0.05, ...){
  function(x){
    # rescaling
    d <- s * diff(range(x)) / (1+2*s)
    seq(min(x)+d, max(x)-d, length=n)
  }
}
# This functions comes from a great answer here
# https://stackoverflow.com/questions/28436855/change-the-number-of-breaks-using-facet-grid-in-ggplot2

n_x <- 5
n_y <- 3

ggplot(palmerpenguins::penguins, aes(x = bill_depth_mm, y= bill_length_mm)) +
  geom_point(aes(colour = species, shape = sex)) +
  scale_color_viridis_d() +
  scale_x_continuous(breaks = equal_breaks2(n = n_x, s = 0.00), expand = c(0,0)) +
  scale_y_continuous(breaks = equal_breaks2(n = n_y, s = 0.00), expand = c(0,0)) +
  theme(aspect.ratio = n_y/n_x,
        panel.grid.minor = element_blank()) +
  coord_fixed()
  

This plot unfortunately does not produce exact squares from the grid lines. One has to manually adjust the aspect ratio (in this example n_y=3.7 looks pretty good).

Does anyone have an idea how to solve this, without having to adjust values manually?

Edit: I forgot to mention this in my initial request; Ideally my plot limits are the min and max value of my breaks, so i also have squares at the borders of the plot.

SebSta
  • 476
  • 2
  • 12

2 Answers2

2

To get a nice scale, I used scales::pretty_breaks.
Let d_x and d_y be the step size between breaks calculated by the scale function.
Let range_x and range_y be the x and y range of the data to plot.
To get squares, aspect.ratio should be :

d_x * range_y / ( d_y * range_x)

Try :

library(ggplot2)
library(scales)
data <- palmerpenguins::penguins

scale_x <- scales::pretty_breaks(n = 5)(data$bill_depth_mm)
scale_y <- scales::pretty_breaks(n = 3)(data$bill_length_mm)

d_x <- diff(scale_x)[1]
d_y <- diff(scale_y)[1]

range_x <- diff(range(scale_x))
range_y <- diff(range(scale_y))

ggplot(data, aes(x = bill_depth_mm, y= bill_length_mm)) +
  geom_point(aes(colour = species, shape = sex)) +
  scale_color_viridis_d() +
  scale_x_continuous(breaks = scale_x, expand = c(0,0)) +
  scale_y_continuous(breaks = scale_y, expand = c(0,0)) +
  theme(aspect.ratio = d_x * range_y / ( d_y * range_x),
        panel.grid.minor = element_blank()) +
  coord_fixed(xlim=range(scale_x),ylim=range(scale_y))

enter image description here

Waldi
  • 39,242
  • 6
  • 30
  • 78
  • I forgot to mention this in my initial request; Ideally my plot limits are the min and max value of my breaks, so i also have squares at the borders of the plot. – SebSta Jul 13 '20 at 09:33
  • Thank you very much for your input! Thanks to you i came up with a solution i am very much happy with. – SebSta Jul 13 '20 at 11:12
1

So, with the great help of @Waldi, i came up with an automatic solution. Its totally viable to do all the calculation beforehand, but i wanted an automatic solution, within the ggplot-chain.

I created my own coord-ggproto object, which can calculate the aspect ratio from the internals in ggplot (According to the Formula of @Waldi).

CoordOwn <- ggproto("CoordOwn", CoordCartesian,
                    is_free = function() FALSE,
                    
                    aspect = function(self, ranges) {
                      d_x = diff(ranges$x.major_source)[1]
                      d_y = diff(ranges$y.major_source)[1]
                      
                      (d_x * diff(ranges$y.range)) / (d_y * diff(ranges$x.range))
                    }
)

coord_own <- function(ratio = 1, xlim = NULL, ylim = NULL, expand = TRUE, clip = "on") {

  ggproto(NULL, CoordOwn,
          limits = list(x = xlim, y = ylim),
          ratio = ratio,
          expand = expand,
          clip = clip
  )

}

Now i can change n_x and n_y however i want them to, and coord_own fixes the aspect ratio accordingly:

n_x <- 5
n_y <- 5

ggplot(palmerpenguins::penguins, aes(x = bill_depth_mm, y= bill_length_mm)) +
  geom_point(aes(colour = species, shape = sex)) +
  scale_color_viridis_d() +
  scale_x_continuous(breaks = equal_breaks2(n = n_x, s = 0.00), expand = c(0,0)) +
  scale_y_continuous(breaks = equal_breaks2(n = n_y, s = 0.00), expand = c(0,0)) +
  theme(panel.grid.minor = element_blank()) +
  coord_own()
SebSta
  • 476
  • 2
  • 12