ggplot2: 改变图例的布局

10

目前,图例默认的外观类似于这样:

Legend Title
x-1 
y-2 
z-3 

但是能否使它看起来像这样呢?

Legend Title 
x-1 y-2 z-3

在 guide_legends_box() 中的 horizontal 参数决定了多个图例框的对齐方式,而不是框内元素的对齐方式。如果要尝试在多个图例框中使用 +opts(legend.position="top"),可以使用如下代码:qplot(mpg, wt, data=mtcars, colour=cyl, size=factor(am))+opts(legend.position="top")。 - kohske
如果您想要使图例框内的元素水平对齐,可能需要自己重新编写build_legend()函数。 - kohske
4个回答

22

现在你可以直接使用legend.direction="horizontal"。例如:

qplot(carat, price, data=diamonds, colour=color) + opts(legend.position="top", legend.direction="horizontal")

很可能是由于Koshke在最近几个ggplot2补丁中的开发工作!:) 感谢您指出这一点,并感谢高桥先生将其整合到ggplot中! - Brandon Bertelsen

11

这里有一个hack。可能会有bug:

build_legend <- function(name, mapping, layers, default_mapping, theme) {
  legend_data <- plyr::llply(layers, build_legend_data, mapping, default_mapping)

  # determine if the elements are aligned horizontally or vertically
  horiz<-(!is.null(theme$legend.align) && theme$legend.align=="horizontal")

  # Calculate sizes for keys - mainly for v. large points and lines
  size_mat <- do.call("cbind", plyr::llply(legend_data, "[[", "size"))
  if (is.null(size_mat)) {
    key_sizes <- rep(0, nrow(mapping))
  } else {
    key_sizes <- apply(size_mat, 1, max)
  }

  title <- theme_render(
    theme, "legend.title",
    name, x = 0, y = 0.5
  )

                                        # Compute heights and widths of legend table
  nkeys <- nrow(mapping)
  hgap <- vgap <- unit(0.3, "lines")

  numeric_labels <- all(sapply(mapping$.label, is.language)) || suppressWarnings(all(!is.na(sapply(mapping$.label, "as.numeric"))))
  hpos <- numeric_labels * 1

  labels <- lapply(mapping$.label, function(label) {
    theme_render(theme, "legend.text", label, hjust = hpos, x = hpos, y = 0.5)
  })

  # align horizontally
  if(!horiz){
    label_width <- do.call("max", lapply(labels, grobWidth))
    label_width <- convertWidth(label_width, "cm")
    label_heights <- do.call("unit.c", lapply(labels, grobHeight))
    label_heights <- convertHeight(label_heights, "cm")

    width <- max(unlist(plyr::llply(legend_data, "[[", "size")), 0)
    key_width <- max(theme$legend.key.size, unit(width, "mm"))

    widths <- unit.c(
                     hgap, key_width,
                     hgap, label_width,
                     max(
                         unit(1, "grobwidth", title) - key_width - label_width,
                         hgap
                         )
                     )
    widths <- convertWidth(widths, "cm")

    heights <- unit.c(
                      vgap, 
                      unit(1, "grobheight", title),
                      vgap, 
                      unit.pmax(
                                theme$legend.key.size, 
                                label_heights, 
                                unit(key_sizes, "mm")
                                ),
                      vgap
                      )  
    heights <- convertHeight(heights, "cm")

  }else{
    label_width <- do.call("unit.c", lapply(labels, grobWidth))
    label_width <- convertWidth(label_width, "cm")
    label_heights <- do.call("max", lapply(labels, grobHeight))
    label_heights <- convertHeight(label_heights, "cm")

    height <- max(unlist(plyr::llply(legend_data, "[[", "size")), 0)
    key_heights <- max(theme$legend.key.size, unit(height, "mm"))

    key_width <- unit.pmax(theme$legend.key.size, unit(key_sizes, "mm"))
    # width of (key gap label gap) x nkeys
    kglg_width<-do.call("unit.c",lapply(1:length(key_width), function(i)unit.c(key_width[i], hgap, label_width[i], hgap)))
    widths <- unit.c(
                      hgap,
                      kglg_width,
                      max(
                          unit(0,"lines"),
                          unit.c(unit(1, "grobwidth", title) - (sum(kglg_width) - hgap))
                          )
                      )
    widths <- convertWidth(widths, "cm")

    heights <- unit.c(
                       vgap, 
                       unit(1, "grobheight", title),
                       vgap, 
                       max(
                           theme$legend.key.size,
                           label_heights, 
                           key_heights
                           ),
                       vgap
                       )  
  heights <- convertHeight(heights, "cm")

  }

  # Layout the legend table
  legend.layout <- grid.layout(
    length(heights), length(widths), 
    widths = widths, heights = heights, 
    just = c("left", "centre")
  )

  fg <- ggname("legend", frameGrob(layout = legend.layout))
  fg <- placeGrob(fg, theme_render(theme, "legend.background"))

  fg <- placeGrob(fg, title, col = 2:(length(widths)-1), row = 2)
  for (i in 1:nkeys) {

    if(!horiz){
      fg <- placeGrob(fg, theme_render(theme, "legend.key"), col = 2, row = i+3)
    }else{
      fg <- placeGrob(fg, theme_render(theme, "legend.key"), col = 1+(i*4)-3, row = 4)
    }

    for(j in seq_along(layers)) {
      if (!is.null(legend_data[[j]])) {
        legend_geom <- Geom$find(layers[[j]]$geom$guide_geom())
        key <- legend_geom$draw_legend(legend_data[[j]][i, ],
           c(layers[[j]]$geom_params, layers[[j]]$stat_params))
        if(!horiz){
          fg <- placeGrob(fg, ggname("key", key), col = 2, row = i+3)
        }else{
          fg <- placeGrob(fg, ggname("key", key), col = 1+(i*4)-3, row = 4)
        }
      }
    }
    label <- theme_render(
      theme, "legend.text", 
      mapping$.label[[i]], hjust = hpos,
      x = hpos, y = 0.5
    )
    if(!horiz){
      fg <- placeGrob(fg, label, col = 4, row = i+3)
    }else{
      fg <- placeGrob(fg, label, col = 1+(i*4)-1, row = 4)
    }
  }
  fg
}

assignInNamespace("build_legend", build_legend, "ggplot2")

# test and usage
# specify by opts(legend.align="horizontal")
p1<-qplot(mpg, wt, data=mtcars, colour=cyl)+opts(legend.align="horizontal",legend.position="bottom")
p2<-qplot(mpg, wt, data=mtcars, colour=cyl)

很好的修复 - 你显然非常熟悉ggplot2!非常慷慨地与我们其他人分享你的专业知识。 - aaron
最近有人尝试使用这个函数(ggplot2 0.9.0版本)吗?我在assignInNamespace命令后得到一个错误:Error in bindingIsLocked(x, ns) : no binding for "build_legend" - Andy Barbour

2

最新版的 ggplot2opts 已被弃用,推荐使用 theme()

qplot(carat, price, data=diamonds, colour=color) +
  theme(legend.position="top", legend.direction="horizontal")

1

有一个类似 guide_legends_box 的东西,带有一个“horizontal”的选项,但我无法使其工作。

> d <- qplot(carat, price, data=dsamp, colour=clarity) +
+  scale_color_hue("clarity") +
+  guide_legends_box("clarity",horizontal=T)

提供:

scales$legend_desc 中的错误:无效的原子向量操作符 $

也许你知道这里出了什么问题。个人认为,文档中提到的部分功能尚未实现。


看一下创建图例的函数,似乎强制设置水平为false。 - Brandon Bertelsen
你已经问过ggplot2的开发者了吗?实际上,默认值是false,但问题在于图形对象中的比例尺对象为空(尝试d $ scales,它基本上什么也没有)。 - Joris Meys

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