如何在多面板绘图中画箭头?

4
假设我们在R中有一个多面板图,使用layout()创建。我想从一个面板中的指定点绘制箭头到另一个面板中的指定点。因此,箭头跨越布局的面板。箭头的起点在其面板的坐标中指定,箭头的终点在目标面板的坐标中指定。
作为最简单的示例,请考虑以下内容:
layout( matrix( 1:2 , nrow=2 ) )
plot( x=c(1,2) , y=c(1,2) , main="Plot 1" )
plot( x=c(10,20) , y=c(10,20) , main="Plot 2" )
# I want to make an arrow 
# from point c(x=1.2,y=1.2) in Plot 1 
# to point c(x=18,y=18) in Plot 2

我已经搜索了一些方法来完成这个任务,但是没有找到任何有用的结果。感谢提供解决方案或指导。


我想你不能分别将两个图形绘制在不同的布局中,但可以在一个布局中绘制两个图形,并将第三个图形(箭头)的透明度设置为零,以实现更好的效果:请参阅https://www.r-bloggers.com/overlapping-histogram-in-r/。 - Curcuma_
1
@Cath:我已经添加了一个最小示例。 - John K. Kruschke
1个回答

2

更新

(我将保留上面的先前答案,但根据您的评论,这种更程序化的方式更好。)

诀窍在于知道如何从"用户"坐标转换为总体设备的坐标。这可以通过grconvertX*Y来完成。我在这里制作了一些粗糙的辅助函数,尽管它们几乎是不必要的。

user2ndc <- function(x, y) {
  list(x = grconvertX(x, 'user', 'ndc'),
       y = grconvertY(y, 'user', 'ndc'))
}
ndc2user <- function(x, y) {
  list(x = grconvertX(x, 'ndc', 'user'),
       y = grconvertY(y, 'ndc', 'user'))
}

为了避免神奇数字出现在代码中,我会预先定义你关注的点:
pointfrom <- list(x = 1.2, y = 1.2)
pointto <- list(x = 18, y = 18)

在图表仍然有效时,将'user'转换为'ndc'非常重要;一旦从图表1切换到2,坐标就会改变。

layout( matrix( 1:2 , nrow=2 ) )

图 1。

plot( x=c(1,2) , y=c(1,2) , main="Plot 1" )
points(y~x, data=pointfrom, pch=16, col='red')
ndcfrom <- with(pointfrom, user2ndc(x, y))

绘制2。

plot( x=c(10,20) , y=c(10,20) , main="Plot 2" )
points(y~x, data=pointto, pch=16, col='red')
ndcto <- with(pointto, user2ndc(x, y))

我之前做过的事情(在这里远下面),是重新映射下一个绘图命令将发生的区域。在幕后,layout 做了像这样的事情。(可以使用一些巧妙的技巧,比如 par(fig=..., new=T),包括在另一个图中覆盖、周围或仅部分重叠)。

par(fig=c(0:1,0:1), new=TRUE)
plot.new()
newpoints <- ndc2user(c(ndcfrom$x, ndcto$x), c(ndcfrom$y, ndcto$y))
with(newpoints, arrows(x[1], y[1], x[2], y[2], col='green', lwd=2))

我本来可以避免从ndc转换回当前用户点的ndc2user,但那样会涉及到边距和轴扩展等问题,所以我选择不这样做。

在最后一个叠加图的用户点区域之外可能存在已转换的点,这种情况下它们可能被掩盖。要解决此问题,请在arrows中添加xpd=NA(或在其前面添加par(xpd=NA))。

enter image description here


通用

好的,现在想象一下你想要在layout完成后确定任何绘图的坐标。目前有一个更复杂的实现支持你所需求的功能。唯一的要求是在每个(有意义的)绘图之后调用NDC$add()。例如:

NDC$reset()
layout(matrix(1:4, nrow=2))
plot(1)
NDC$add()
plot(11)
NDC$add()
plot(21)
NDC$add()
plot(31)
NDC$add()
with(NDC$convert(1:4, c(1,1,1,1), c(1,11,21,31)), {
  arrows(x[1], y[1], x[2], y[2], xpd=NA, col='red')
  arrows(x[2], y[2], x[3], y[3], xpd=NA, col='blue')
  arrows(x[3], y[3], x[4], y[4], xpd=NA, col='green')
})

源代码可以在这里找到:https://gist.github.com/r2evans/8a8ba8fff060bade13bf21e89f0616c5

enter image description here


前面的回答

一种方法是使用par(fig=...,new=TRUE),但它不能保存你所需的坐标信息。

layout(matrix(1:4,nr=2))
plot(1)
plot(1)
plot(1)
plot(1)
par(fig=c(0,1,0,1),new=TRUE)
plot.new()
lines(c(0.25,0.75),c(0.25,0.75),col='blue',lwd=2)

enter image description here

由于如果您对点的端点有更好(非任意)的控制,您可能更有可能使用此功能,因此这里有一个技巧,可以让您更好地控制这些点。如果我使用此功能,将左上角的点连接到右下角的点:

p <- locator(2)
str(p)
# List of 2
#  $ x: num [1:2] 0.181 0.819
#  $ y: num [1:2] 0.9738 0.0265

然后我使用以下代码替换上面的lines

with(p, arrows(x[1], y[1], x[2], y[2], col='green', lwd=2))

我得到了

enter image description here

(这张图片和p中的值展示了坐标的不同。当使用par(fig=...,new=T);plot.new();时,坐标会返回到

par('usr')
# [1] -0.04  1.04 -0.04  1.04

可能会有一些诡计来尝试解决这个问题(例如,如果您需要自动化此步骤),但这很可能是非常困难的(而且不够健壮)。


谢谢聪明的建议!但我真的想能够使用起始和结束图的坐标来指定起始和结束点。 - John K. Kruschke
通常来说,涉及“设备”坐标(而不是“用户”坐标)的操作可能会过于深入内部,但它已经运行良好了几年。 - r2evans
太棒了!我不知道grconvertX()函数和par(fig=...)。我已将答案标记为已接受。但是我想知道,是否有一种方法可以在绘制所有图形后指定布局中正在引用的面板,以便可以在制作所有面板图形后指定从哪里到哪里的点? - John K. Kruschke
John,看一下我的新编辑,源代码在这里 - r2evans
1
太棒了!非常感谢您的辛勤努力。我希望很多人会发现这个有用,并引用您在这里的工作! - John K. Kruschke

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接