我如何手动将具有固定纵横比的视口放入其父视口,这样就不会像 ggplot 那样浪费 space 了?
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',我能够保持纵横比,尽管我无法达到最大可能。请参阅下面的代码,它打印出到目前为止我在四种不同设备纵横比下取得的成果。
虽然当设备宽度较小时感兴趣的视口(灰色和带网格)填充最大可用区域,但如果设备宽度变得太大以至于设备高度成为视口的限制因素,则该方法失败尺寸。视口没有覆盖整个可能的高度。我希望感兴趣的视口覆盖最右侧图中的整个设备高度。
编辑:我发现 ggplot 可以做到这一点,并更新了我的示例以证明这一点。请注意 ggplot 如何触及最右侧图像中的上下设备边界以及最左侧图像中的左右边界,为什么我自制的解决方案不会触及最右侧图像中的上下设备边界,即使有 space。但是我不能使用 ggplot,因为我想包括一个只用网格构建的自定义绘图,但它依赖于本地 x 和 y 坐标系上的相等距离。
# -- 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
参数来强制纵横比。这是一个例子,
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))))
太棒了!我在这里发布他的程序网格样式的解决方案,以供参考。另外,我添加了等距坐标系。
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)
})
我有一个视口,它必须具有固定的宽高比,因为它必须在其本地坐标系中的 x 和 y 单位之间具有相等的距离。
我想将此视口放入父视口中,以便它可以最大程度地缩放,但保持其纵横比。
使用网格单元'snpc',我能够保持纵横比,尽管我无法达到最大可能。请参阅下面的代码,它打印出到目前为止我在四种不同设备纵横比下取得的成果。
虽然当设备宽度较小时感兴趣的视口(灰色和带网格)填充最大可用区域,但如果设备宽度变得太大以至于设备高度成为视口的限制因素,则该方法失败尺寸。视口没有覆盖整个可能的高度。我希望感兴趣的视口覆盖最右侧图中的整个设备高度。
编辑:我发现 ggplot 可以做到这一点,并更新了我的示例以证明这一点。请注意 ggplot 如何触及最右侧图像中的上下设备边界以及最左侧图像中的左右边界,为什么我自制的解决方案不会触及最右侧图像中的上下设备边界,即使有 space。但是我不能使用 ggplot,因为我想包括一个只用网格构建的自定义绘图,但它依赖于本地 x 和 y 坐标系上的相等距离。
# -- 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
参数来强制纵横比。这是一个例子,
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))))
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)
})