【问题标题】:draw segment from angle despite changing aspect ratio尽管改变了纵横比,但仍从角度绘制片段
【发布时间】:2016-01-18 19:24:29
【问题描述】:

我正在尝试在 R 中绘制一个表格,其列名与表格成一定角度。我想以与文本相同的角度添加线条来分隔这些列名。但是,text() 函数中指定的角度似乎与绘图的纵横比无关,而我在 segments() 函数中使用的角度取决于绘图的纵横比。

这是我的意思的一个例子:

nRows <- 5
nColumns <- 3
theta <- 30

rowLabels <- paste('row', 1:5, sep='')
colLabels <- paste('col', 1:3, sep='')

plot.new()
par(mar=c(1,8,5,1), xpd=NA)
plot.window(xlim = c(0, nColumns), ylim = c(0, nRows), asp = 1)
text(labels = rowLabels, x=0, y=seq(from=0.5, to=nRows, by=1), pos=2)
text(labels = colLabels, x = seq(from = 0.4, to = nColumns, by = 1), y = nRows + 0.1, pos = 4, srt = theta, cex = 1.1)
segments(x0 = c(0:nColumns), x1 = c(0:nColumns), y0 = 0, y1 = nRows, lwd = 0.5)
segments(x0 = 0, x1 = nColumns, y0 = 0:nRows, y1 = 0:nRows, lwd = 0.5)

#column name separators, angle converted to radians
segments(x0 = 0:(nColumns - 1), x1 = 1:nColumns, y0 = nRows, y1 = nRows + tan(theta * pi/180), lwd = 0.5)

但是,如果我希望能够根据自己的喜好调整此绘图窗口的大小而不指定 asp,则角度不再匹配:

nRows <- 5
nColumns <- 3
theta <- 30

rowLabels <- paste('row', 1:5, sep='')
colLabels <- paste('col', 1:3, sep='')

plot.new()
par(mar=c(1,8,5,1), xpd=NA)
plot.window(xlim = c(0, nColumns), ylim = c(0, nRows))
text(labels = rowLabels, x=0, y=seq(from=0.5, to=nRows, by=1), pos=2)
text(labels = colLabels, x = seq(from = 0.4, to = nColumns, by = 1), y = nRows + 0.1, pos = 4, srt = theta, cex = 1.1)
segments(x0 = c(0:nColumns), x1 = c(0:nColumns), y0 = 0, y1 = nRows, lwd = 0.5)
segments(x0 = 0, x1 = nColumns, y0 = 0:nRows, y1 = 0:nRows, lwd = 0.5)

#column name separators, angle converted to radians
segments(x0 = 0:(nColumns - 1), x1 = 1:nColumns, y0 = nRows, y1 = nRows + tan(theta * pi/180), lwd = 0.5)

有没有办法指定一个设定的角度,这样当我调整窗口大小时图形看起来正确?

【问题讨论】:

  • 如果可以的话,在网格中可能更容易实现
  • 我不认为这是可能的(虽然我可能错了)因为asp 的工作方式。来自plot.window 帮助文件:“如果 asp 是有限正值,则设置窗口,以便 x 方向上的一个数据单元的长度等于 asp * y 方向上的一个数据单元”现在,如果您不要指定 asp,y1 = nRows + tan(theta * pi/180) 中断,因为弧度取决于通过窗口大小调整缩放的半径,与角度无关(保持不变)。所以,我没有在 base R 中找到解决方案,但我再次希望得到证明!
  • 选择一个asp 并在制作标题行时将thetax1 = 1:nColumns * asp 一起缩放

标签: r


【解决方案1】:

theta 值 30 弧度是数据空间角度。它仅适用于数据空间计算,例如在您对绘制对角线的segments() 的调用中。

srt 图形参数指定设备空间中的文本旋转,这意味着无论底层绘图区域的纵横比如何,文本都将按照物理设备上的指定角度呈现。

数据和设备空间之间的关系是动态确定的,并受多种因素影响:

  • 设备尺寸(GUI 窗口客户区大小或目标文件大小)。
  • 图形多重性(如果使用多重图形;请参阅 mfrowmfcol 图形参数)。
  • 任何内边距和外边距(大多数地块有内边距,外边距很少见)。
  • 任何内部间距(参见xaxsyaxs 图形参数)。
  • 绘图范围(xlimylim)。

做你想做的正确方法是 (1) 动态查询以设备空间距离单位测量的数据空间纵横比和 (2) 使用它从数据空间角度转换 theta到设备空间角度。

1:长宽比查询

我们可以通过沿 x 轴找到相当于 1 个数据空间单元的设备空间单元来计算纵横比,对 y 轴执行相同的操作,然后取比率 y/x。函数grconvertX() and grconvertY() 就是为此目的而创建的。

calcAspectRatio <- function() abs(diff(grconvertY(0:1,'user','device'))/diff(grconvertX(0:1,'user','device')));

转换函数在单个坐标上运行,而不是距离。但是它们是矢量化的,所以我们可以通过0:1来转换输入坐标系中相距1个单位的两个坐标,然后再通过diff()得到输出坐标系中的等效单位距离。

您可能想知道为什么需要调用abs()。对于许多图形设备,y 轴向下而不是向上增加,因此较小的数据空间坐标将转换为较大的设备空间坐标。因此,在这些情况下,第一次diff() 调用的结果将是否定的。从理论上讲,这绝不应该发生在 x 轴上,但我们不妨将整个商包装在 abs() 调用中以防万一。

2:将 theta 从数据空间转换为设备空间

这里可以采用几种数学方法,但我认为最简单的方法是取角度的tan() 得到三角函数y/x 的比率,乘以纵横比,然后再转换回使用atan2()的角度。

dataAngleToDevice <- function(rad,asp) {
    rad <- rad%%(pi*2); ## normalize to [0,360) to make following ops easier
    y <- abs(tan(rad))*ifelse(rad<=pi,1,-1)*asp; ## derive y/x trig ratio with proper sign for y and scale by asp
    x <- ifelse(rad<=pi/2 | rad>=pi*3/2,1,-1); ## derive x component with proper sign
    atan2(y,x)%%(pi*2); ## use atan2() to derive result angle in (-180,180], and normalize to [0,360)
}; ## end dataAngleToDevice()

简而言之,我发现这是一个非常有趣的数学变换。角度 0、90、180 和 270 不受影响,这是有道理的;纵横比的变化不应影响这些角度。垂直伸长将角度拉向 y 轴,水平伸长将角度拉向 x 轴。至少我是这么想象的。


因此,将所有这些放在一起,我们有以下解决方案。请注意,为了更简洁,我重写了您的代码并进行了一些小的更改,但大多数情况下是相同的。显然最重要的变化是我在theta 周围添加了对dataAngleToDevice() 的调用,第二个参数传递calcAspectRatio()。此外,我使用更小(按字体)但更长(按字符串)的列名称来更清楚地展示文本的角度,我将文本移近对角线,我从一开始就以弧度存储theta,我重新排序了位。

nRows <- 5;
nColumns <- 3;
theta <- 30*pi/180;

rowLabels <- paste0('row',1:5);
colLabels <- do.call(paste,rep(list(paste0('col',1:3)),5L));

plot.new();
par(mar=c(1,8,5,1),xpd=NA);
plot.window(xlim=c(0,nColumns),ylim=c(0,nRows));
segments(0:nColumns,0,0:nColumns,nRows,lwd=0.5);
segments(0,0:nRows,nColumns,0:nRows,lwd=0.5);
text(0,seq(0.5,nRows,1),rowLabels,pos=2);
## column name separators
segments(0:(nColumns-1),nRows,1:nColumns,nRows+tan(theta),lwd=0.5);
text(seq(0.3,nColumns,1),nRows+0.1,colLabels,pos=4,srt=dataAngleToDevice(theta,calcAspectRatio())*180/pi);

这是一个大致为正方形纵横比的演示:

宽:

又高:


我做了一个转换图:

xlim <- ylim <- c(0,360);
xticks <- yticks <- seq(0,360,30);
plot(NA,xlim=xlim,ylim=ylim,xlab='data',ylab='device',axes=F);
box();
axis(1L,xticks);
axis(2L,yticks);
abline(v=xticks,col='grey');
abline(h=yticks,col='grey');
lineParam <- data.frame(asp=c(1/1,1/2,2/1,1/4,4/1),col=c('black','darkred','darkblue','red','blue'),stringsAsFactors=F);
for (i in seq_len(nrow(lineParam))) {
    x <- 0:359;
    y <- dataAngleToDevice(x*pi/180,lineParam$asp[i])*180/pi;
    lines(x,y,col=lineParam$col[i]);
};
with(lineParam[order(lineParam$asp),],
    legend(310,70,asp,col,title=expression(bold(aspect)),title.adj=c(NA,0.5),cex=0.8)
);

【讨论】:

  • 非常感谢您提供如此完整的解决方案!
猜你喜欢
  • 1970-01-01
  • 2022-11-17
  • 2013-02-26
  • 2017-03-28
  • 1970-01-01
  • 2023-04-03
  • 1970-01-01
  • 1970-01-01
  • 2020-04-20
相关资源
最近更新 更多