title: How do I manually fit a viewport with a fixed aspect ratio into its parent such that no space is wasted like ggplot can do?
body: <p>I have a viewport which has to have a fixed aspect ratio as it has to have equal distance between x and y units in its native coordinate system.</p>
  
  <p>I want to fit this viewport into a parent viewport such that it will scale to the largest extent possible, but maintains its aspect ratio. </p>
  
  <p>Using the grid unit 'snpc', I was able to maintain the aspect ratio, though I could not reach the largest extent possible. See my code below, which prints out what I have archieved so far at four different device aspect ratios. </p>
  
  <p><a href="https://i.stack.imgur.com/hiNwp.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/hiNwp.png" alt="Plot at four different output device widths"></a></p>
  
  <p>While the viewport of interest (gray and with grid) fills the maximal area available when the device has a small width, the approach fails if the device width becomes so large that the device height is the limiting factor for the viewport size. The viewport does not cover the whole possible height. I want the viewport of interest to cover the whole device height in the rightmost plot.</p>
  
  <p><strong>EDIT</strong>: I found out that ggplot can do this and have updated my example to show that. Note how ggplot touches the upper and lower device border in the rightmost image and the left and right border at the leftmost image, why my self-made solution does not touch the upper and lower device border in the rightmost image even if there would be space. I cannot use ggplot however, as I want to include a custom drawing built only with grid but which is dependent on equal distances on native x and y coordinate system. </p>
  
  <p><a href="https://i.stack.imgur.com/1fOd7.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/1fOd7.png" alt="ggplot output at four different output device widths"></a></p>
  
  <pre><code># -- Helper functions ------------------------------------------------------
  
  # Draw something (inside fun) for different paper sizes
  forDifferentSizes &lt;- function(names, width, height, fun, ...){
    cyc &lt;- function(x, along) rep_len(x, length(along))
    mapply( names, cyc(width, names), cyc(height, names)
          , FUN = function(n, w, h){
              png(paste0(n,'.png'), width = w, height = h, ...)
              on.exit(dev.off())
              fun(n, w, h)
          })
  }
  
  # -- Own attempt -----------------------------------------------------------
  library(grid)
  
  # Coordinate system
  x &lt;- c(1,6)
  y &lt;- c(1,4)
  range &lt;- c(diff(x), diff(y))
  dims &lt;- range / max(range)
  
  annot &lt;- function(name){
    grid.rect(gp = gpar(fill = NA))
    grid.text( name,unit(1, 'npc'),unit(0,'npc'), just = c(1,0))
  }
  
  forDifferentSizes( paste0('X',letters[1:4]), seq(100, 500, length.out = 4), 250
    , fun = function(...){
    grid.newpage()
  
    pushViewport(
      viewport( width  = unit( dims[1], 'snpc')
                , height = unit( dims[2], 'snpc')
                , xscale = x
                , yscale = y
      )
    )
    annot('vp2')
    grid.grill(v = x[1]:x[2], h = y[1]:y[2], default.units = 'native')
  })
  
  # --- ggplot2 can do it -----------------------------------------------------
  
  library(ggplot2)
  data("mtcars")
  
  forDifferentSizes(paste0('G',letters[1:4]), seq(100, 500, length.out = 4), 250
    , pointsize = 8
    , fun = function(...){
    p &lt;- ggplot(mtcars) + aes(x = drat, y = mpg) + geom_point() + 
      theme(aspect.ratio = dims[2]/dims[1])
    print(p)
  })
  
  # --- Make the output images for post (imagemagick required) ---------------
  system('convert G*.png -bordercolor black -border 1x1 +append G.png')
  system('convert X*.png -bordercolor black -border 1x1 +append X.png')
  </code></pre>