关于 r:如何手动将具有固定纵横比的视口放入其父级中,这样不会像 ggplot 那样浪费空间?

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?

我有一个视口,它必须具有固定的纵横比,因为它在其原生坐标系中的 x 和 y 单位之间的距离必须相等。

我想将此视口调整到父视口中,以便它可以最大程度地缩放,但保持其纵横比。

使用网格单元"snpc",我能够保持纵横比,尽管我无法达到最大程度。请参阅下面的代码,它打印出我到目前为止以四种不同的设备纵横比归档的内容。

Plot

虽然感兴趣的视口(灰色和网格)在设备宽度较小时填充了最大可用区域,但如果设备宽度变得如此之大以至于设备高度成为视口大小的限制因素,则该方法会失败。视口没有覆盖整个可能的高度。我希望感兴趣的视口覆盖最右边图中的整个设备高度。

编辑:我发现 ggplot 可以做到这一点,并更新了我的示例以显示这一点。注意ggplot如何触摸最右边图像的上下设备边框和最左边图像的左右边框,为什么我的自制解决方案即使有空间也不会触摸最右边图像的上下设备边框.但是,我不能使用 ggplot,因为我想包含一个仅使用网格构建的自定义绘图,但它依赖于原生 x 和 y 坐标系上的相等距离??。

ggplot

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# -- Helper functions ------------------------------------------------------

# Draw something (inside fun) for different paper sizes
forDifferentSizes <- function(names, width, height, fun, ...){
  cyc <- 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 <- c(1,6)
y <- c(1,4)
range <- c(diff(x), diff(y))
dims <- range / max(range)

annot <- 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 <- 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')


ggplot2 使用具有空单位的网格布局和 respect 参数来强制纵横比。这是一个例子,

1
2
3
4
5
6
7
library(grid)

ar <- (1+sqrt(5))/2
gl <- grid.layout(1,1,widths=unit(1,"null"), height=unit(1/ar,"null"), respect = TRUE)
grid.newpage()
grid.rect(vp=vpTree(viewport(layout = gl),
                    vpList(viewport(layout.pos.row = 1, layout.pos.col = 1))))


user9169915 做到了!惊人的!我在这里以程序网格样式发布他的解决方案,以供参考。另外,我添加了等距坐标系。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
ar <- (1+sqrt(5))/2 # aspect ratio
# Native coordinate system of the target viewport: make x and y equidistant
xrange <- c(0,5)
yrange <- xrange/arN

forDifferentSizes( paste0('L',letters[1:4]), seq(100, 500, length.out = 4), 250
  , fun = function(...){

  gl <- grid.layout(1,1,widths=unit(1,"null"), height=unit(1/ar,"null"), respect = TRUE)
  grid.newpage()
  pushViewport(viewport(layout = gl))
  annot('vp1') # see question for definition
  pushViewport(viewport(layout.pos.row = 1, layout.pos.col = 1,
                        xscale = xrange, yscale = yrange))
  annot('vp2')
  grid.grill(h=0:floor(yrange[2]), v=0:floor(xrange[2]), default.units = 'native')
  popViewport(2)

})

plot