在基础绘图中,如何将图例放置在绘图区域之外?

218

正如标题所说:当使用基本图形时,我该如何在绘图区域外绘制图例?

我考虑过使用layout进行调整并生成一个空的绘图,只包含图例,但我更想知道一种仅使用基本图形设施和例如par(mar = )来获取一些用于图例的绘图区域右侧的空间的方法。


以下是一个示例:

plot(1:3, rnorm(3), pch = 1, lty = 1, type = "o", ylim=c(-2,2))
lines(1:3, rnorm(3), pch = 2, lty = 2, type="o")
legend(1,-1,c("group A", "group B"), pch = c(1,2), lty = c(1,2))

产生的结果如下:

alt text

但是,如我所说,我希望图例在绘图区域之外(例如,在图形/绘图右侧)。


你还可以使用虚拟容器来模拟图例,这样做简单方便,随时可行。类似的问题可以参考这里 - hhh
5
@hhh,链接已失效,请更新或使用此方法发表答案。 - Henrik
10个回答

173

目前还没有提到使用负的inset值来对legend进行调整。下面是一个示例,其中图例位于绘图区域的右侧,并与其顶部对齐(使用关键字"topright")。

# Random data to plot:
A <- data.frame(x=rnorm(100, 20, 2), y=rnorm(100, 20, 2))
B <- data.frame(x=rnorm(100, 21, 1), y=rnorm(100, 21, 1))

# Add extra space to right of plot area; change clipping to figure
par(mar=c(5.1, 4.1, 4.1, 8.1), xpd=TRUE)

# Plot both groups
plot(y ~ x, A, ylim=range(c(A$y, B$y)), xlim=range(c(A$x, B$x)), pch=1,
               main="Scatter plot of two groups")
points(y ~ x, B, pch=3)

# Add legend to top right, outside plot region
legend("topright", inset=c(-0.2,0), legend=c("A","B"), pch=c(1,3), title="Group")

根据图例的宽度,inset=c(-0.2,0) 的第一个值可能需要调整。

legend_right


15
@Henrik,如果不加xpd=TRUE的话,它是无效的。另外请注意,最好将xpd=TRUE作为legend()函数的一个参数进行设置。 - Stéphane Laurent
3
有时候,为了使负的插图生效,必须将 xpd 设置为 TRUE。但有时候不需要。在 barplot(... 中使用命令 args.legend=list(x="bottom", horiz=TRUE, inset=-0.2) 似乎不需要 xpd=TRUE,但是只使用 legend(x="bottom", horiz=TRUE, inset=-0.2) 就需要 xpd=TRUE。有什么见解吗?我只是在传递我的参数时感到困惑? - user3386170

133

也许你需要使用 par(xpd=TRUE)来使得图形元素绘制在绘图区域之外。因此,如果你使用 bty='L' 来绘制主图,就会给右侧留下一些空间用于放置图例。通常情况下,这个部分会被截断到绘图区域内,但是使用 par(xpd=TRUE) 并进行一些调整,你就可以将图例放到最右边了:

 set.seed(1) # just to get the same random numbers
 par(xpd=FALSE) # this is usually the default

 plot(1:3, rnorm(3), pch = 1, lty = 1, type = "o", ylim=c(-2,2), bty='L')
 # this legend gets clipped:
 legend(2.8,0,c("group A", "group B"), pch = c(1,2), lty = c(1,2))

 # so turn off clipping:
 par(xpd=TRUE)
 legend(2.8,-1,c("group A", "group B"), pch = c(1,2), lty = c(1,2))

34
请注意,您可以直接将xpd传递给图例,因此无需担心在之后重置par。另外,查看grconvertX和Y,以了解一种不依赖于所绘制数据的限制来指定图例位置的方法。 - Charles
7
由于这个问题和答案仍然非常受欢迎,因此 par(xpd=NA) 更加强大(即绘制到更多区域)。 - Henrik
我们应该提到,在图例之前单独使用par调用是有意义的。在我的绘图中,我在几个其他场合使用了par(new=T),并且只是想在同一个调用中添加xpd参数,这会导致问题。 - Matt Bannert
即使在第二个示例中,图例也会被略微裁剪出屏幕。即使您尝试调整图形大小,图例框仍部分位于屏幕外。有没有办法让它保持在屏幕内? - numbermaniac

39

除了已经提到的解决方案(使用layoutpar(xpd=TRUE)),另一个解决方案是在整个设备上覆盖一个透明的图形,然后将图例添加到其中。

关键是在整个绘图区域上覆盖一个(空的)图形,并在其中添加图例。我们可以使用par(fig=...)选项。首先,我们指示R创建一个在整个绘图设备上的新图:

par(fig=c(0, 1, 0, 1), oma=c(0, 0, 0, 0), mar=c(0, 0, 0, 0), new=TRUE)

我们需要设置omamar,这样才能使绘图的内部覆盖整个设备。使用new=TRUE可以防止R启动新设备。然后我们可以添加一个空的绘图:

plot(0, 0, type='n', bty='n', xaxt='n', yaxt='n')

现在我们可以加入图例:

legend("bottomright", ...)

将图例添加到设备的右下角。同样,我们可以将图例添加到顶部或右侧边距。唯一需要确保的是原始图的边距足够大以容纳图例。

将所有这些放入一个函数中;

add_legend <- function(...) {
  opar <- par(fig=c(0, 1, 0, 1), oma=c(0, 0, 0, 0), 
    mar=c(0, 0, 0, 0), new=TRUE)
  on.exit(par(opar))
  plot(0, 0, type='n', bty='n', xaxt='n', yaxt='n')
  legend(...)
}

还有一个例子。首先创建绘图,确保我们在底部有足够的空间添加图例:

par(mar = c(5, 4, 1.4, 0.2))
plot(rnorm(50), rnorm(50), col=c("steelblue", "indianred"), pch=20)

然后添加图例

add_legend("topright", legend=c("Foo", "Bar"), pch=20, 
   col=c("steelblue", "indianred"),
   horiz=TRUE, bty='n', cex=0.8)

导致:

示例图例显示在顶部边距中


2
这是列表中的一个很好的补充。这里有一个关于如何在图形中使用多个绘图来使其工作的解释链接 - shiri
Jan,有没有办法在图例中增加字体大小,而不会剪切一些文本?例如,我有一个图形,其中包含4种不同类型的标签,但它们之间有很多空白空间。 - An old man in the sea.
我已经写了一个更详细的问题,链接为http://stackoverflow.com/questions/42707308/r-using-an-answer-from-legend-outside-the-plot-in-r-but-it-still-has-some-p。 - An old man in the sea.

19

我喜欢这样做:

par(oma=c(0, 0, 0, 5))
plot(1:3, rnorm(3), pch=1, lty=1, type="o", ylim=c(-2,2))
lines(1:3, rnorm(3), pch=2, lty=2, type="o")
legend(par('usr')[2], par('usr')[4], bty='n', xpd=NA,
       c("group A", "group B"), pch=c(1, 2), lty=c(1,2))

输入图像描述

唯一需要调整的是将右边距设置得足够宽以容纳图例。

但是,这也可以自动化:

dev.off() # to reset the graphics pars to defaults
par(mar=c(par('mar')[1:3], 0)) # optional, removes extraneous right inner margin space
plot.new()
l <- legend(0, 0, bty='n', c("group A", "group B"), 
            plot=FALSE, pch=c(1, 2), lty=c(1, 2))
# calculate right margin width in ndc
w <- grconvertX(l$rect$w, to='ndc') - grconvertX(0, to='ndc')
par(omd=c(0, 1-w, 0, 1))
plot(1:3, rnorm(3), pch=1, lty=1, type="o", ylim=c(-2, 2))
lines(1:3, rnorm(3), pch=2, lty=2, type="o")
legend(par('usr')[2], par('usr')[4], bty='n', xpd=NA,
       c("group A", "group B"), pch=c(1, 2), lty=c(1, 2))

这里输入图片描述


使用xpd=T或xpd=NA并不能防止我的“主要”(标题)在被拉伸以尝试使用宽右边距添加的区域时被裁剪。 - Phil Goetz
@PhilGoetz 你确定你在绘图区内绘制主要内容吗?有可能你的边距行数不够,无法进行绘制吗? - jbaums

18

很抱歉打扰了一个老帖子,但我今天也遇到了同样的问题。我找到的最简单的方法如下:

# Expand right side of clipping rect to make room for the legend
par(xpd=T, mar=par()$mar+c(0,0,0,6))

# Plot graph normally
plot(1:3, rnorm(3), pch = 1, lty = 1, type = "o", ylim=c(-2,2))
lines(1:3, rnorm(3), pch = 2, lty = 2, type="o")

# Plot legend where you want
legend(3.2,1,c("group A", "group B"), pch = c(1,2), lty = c(1,2))

# Restore default clipping rect
par(mar=c(5, 4, 4, 2) + 0.1)

链接在此处: http://www.harding.edu/fmccown/R/


4
更好的方法是: oldpar <- par(xpd=T, mar=par()$mar+c(0,0,0,6)) ... par(oldpar)(请参见 par 的帮助文档) - rakensi
这个解决方案更好,因为图例的空间是固定的,无论图例字符串的长度如何。 - Sergio

13

我认为可以添加另一种简单而优雅的选择。

你的情节:

plot(1:3, rnorm(3), pch = 1, lty = 1, type = "o", ylim=c(-2,2))
lines(1:3, rnorm(3), pch = 2, lty = 2, type="o")

传说:

legend("bottomright", c("group A", "group B"), pch=c(1,2), lty=c(1,2),
       inset=c(0,1), xpd=TRUE, horiz=TRUE, bty="n"
       )

结果:

带图例的图片

此处只添加了图例的第二行。具体来说:

  • inset=c(0,1) - 将图例移动到绘图区域的一部分(x,y)。在这种情况下,图例位于"bottomright"位置。它在x方向上不移动(因此保持在"右侧"),在y方向上移动了一个绘图区域(从底部到顶部)。然后它出现在绘图正上方。
  • xpd=TRUE - 允许图例出现在绘图区域之外。
  • horiz=TRUE - 指示生成水平图例。
  • bty="n" - 一种样式细节,用于消除图例的边界框。

当在侧面添加图例时同样适用:

par(mar=c(5,4,2,6))
plot(1:3, rnorm(3), pch = 1, lty = 1, type = "o", ylim=c(-2,2))
lines(1:3, rnorm(3), pch = 2, lty = 2, type="o")

legend("topleft", c("group A", "group B"), pch=c(1,2), lty=c(1,2),
       inset=c(1,0), xpd=TRUE, bty="n"
       )

在这里,我们只是调整了图例的位置,并在绘图的右侧添加了额外的边距空间。结果:

带图例的图片2


尝试过这个,它有效。简单多了。 - CK7

10

我只能提供一个已经指出的布局解决方案的示例。

layout(matrix(c(1,2), nrow = 1), widths = c(0.7, 0.3))
par(mar = c(5, 4, 4, 2) + 0.1)
plot(1:3, rnorm(3), pch = 1, lty = 1, type = "o", ylim=c(-2,2))
lines(1:3, rnorm(3), pch = 2, lty = 2, type="o")
par(mar = c(5, 0, 4, 2) + 0.1)
plot(1:3, rnorm(3), pch = 1, lty = 1, ylim=c(-2,2), type = "n", axes = FALSE, ann = FALSE)
legend(1, 1, c("group A", "group B"), pch = c(1,2), lty = c(1,2))

一个难看的图片 :S


10

最近我发现了一个非常简单有趣的功能,可以在你想要的绘图区域外部打印图例。

将绘图区域的右侧外边缘留出空间。

par(xpd=T, mar=par()$mar+c(0,0,0,5))

创建一个图表。
plot(1:3, rnorm(3), pch = 1, lty = 1, type = "o", ylim=c(-2,2))
lines(1:3, rnorm(3), pch = 2, lty = 2, type="o")

添加图例并像下面这样使用locator(1)函数。然后在加载以下脚本后,您只需单击所需位置即可。

legend(locator(1),c("group A", "group B"), pch = c(1,2), lty = c(1,2))

试一试


4
你可以使用Plotly R API中的任意一种方式,通过代码或从GUI中拖动图例来实现此操作。
这里有一个例子。图表和代码也在这里
x = c(0,1,2,3,4,5,6,7,8) 
y = c(0,3,6,4,5,2,3,5,4) 
x2 = c(0,1,2,3,4,5,6,7,8) 
y2 = c(0,4,7,8,3,6,3,3,4)

你可以通过将x和y值中的一个分配为100或-100来将图例放置在图形外部。
legendstyle = list("x"=100, "y"=1)
layoutstyle = list(legend=legendstyle)

以下是其他选项:
  • list("x" = 100, "y" = 0) 表示在右侧下方
  • list("x" = 100, "y"= 1) 表示在右侧上方
  • list("x" = 100, "y" = .5) 表示在右侧中间
  • list("x" = 0, "y" = -100) 表示在左侧下方
  • list("x" = 0.5, "y" = -100) 表示在正中下方
  • list("x" = 1, "y" = -100) 表示在右侧下方
然后是响应。

response = p$plotly(x,y,x2,y2, kwargs=list(layout=layoutstyle));

当您进行调用时,Plotly会返回一个包含图形的URL。您可以通过调用browseURL(response$url)来更快地访问它,这样它就会在浏览器中为您打开图形。
url = response$url
filename = response$filename

这给我们呈现了这张图表。您也可以在GUI中移动图例,然后图表将相应缩放。全面披露:我是Plotly团队的成员。

Legend on side of graph


3
尝试使用layout(),我过去曾经使用它,只需在下方创建一个空白图表,适当缩放到大约1/4左右,并手动将图例部分放置其中即可。
这里有一些旧的问题涉及到legend(),可以帮助你入门。

正如问题中所说的那样,这也是我考虑过的。但如果有另一种方法就更理想了。不过我认为可能并没有其他方法。 - Henrik

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