38

我正在寻找一种将条形标签文本动态包装在facet_wraporfacet_grid调用中的方法。我找到了一种使用 来完成此操作的方法strwrap,但我需要指定 awidth以使输出按需要工作。通常预先不知道 facets 的数量,所以这种方法需要我width根据数据集和绘图大小迭代调整参数。是否可以为 wrap 函数动态指定宽度,或者是否有另一个选项可以更好地标记构面?

library(ggplot2)
df = expand.grid(group=paste(c("Very Very Very Long Group Name "), 1:9),
                 x=rnorm(5), y=rnorm(5), stringsAsFactors=FALSE)

df$groupwrap = unlist(lapply(strwrap(df$group, width=30, simplify=FALSE), paste, 
                             collapse="\n"))
p = ggplot(df) +
  geom_point(aes(x=x, y=y)) +
  facet_wrap(~groupwrap)

更新:根据@baptiste 和@thunk 提供的指导,我提出了以下选项。目前,它仅适用于指定的字体系列和大小,但理想情况下,也应该能够使用默认theme设置。也许有更多ggplot2经验的人有一些改进建议。

library('grid')
grobs <- ggplotGrob(p)

sum = sum(sapply(grobs$width, function(x) convertWidth(x, "in")))
panels_width = par("din")[1] - sum  # inches

df$group = as.factor(df$group)
npanels = nlevels(df$group)
if (class(p$facet)[1] == "wrap") {
  cols = n2mfrow(npanels)[1]
} else {
  cols = npanels
}

ps = 12
family = "sans"
pad = 0.01  # inches
panel_width = panels_width / cols
char_width = strwidth(levels(df$group)[
  which.max(nchar(levels(df$group)))], units="inches", cex=ps / par("ps"), 
                      family=family) / max(nchar(levels(df$group)))
width = floor((panel_width - pad)/ char_width)  # characters

df$groupwrap = unlist(lapply(strwrap(df$group, width=width, simplify=FALSE), 
                             paste, collapse="\n"))
ggplot(df) +
  geom_point(aes(x=x, y=y)) +
  facet_wrap(~groupwrap) +
  theme(strip.text.x=element_text(size=ps, family=family))
4

4 回答 4

60

自从发布了这个问题以来,带有(> = 1.0.0,我认为)的新label_wrap_gen()函数很好地处理了这个问题:ggplot2

facet_wrap(~groupwrap, labeller = labeller(groupwrap = label_wrap_gen(10)))

请注意,您必须指定宽度才能使其工作。

对于较旧的 ggplot2 版本:

facet_wrap(~groupwrap, labeller = label_wrap_gen())
于 2016-11-16T02:44:29.147 回答
10

感谢@baptiste 和@thunk 的指导,我创建了下面的函数,它似乎在自动包装构面标签方面做得很好。不过,总是欢迎提出改进建议。

strwrap_strip_text = function(p, pad=0.05) { 
  # get facet font attributes
  th = theme_get()
  if (length(p$theme) > 0L)
    th = th + p$theme

  require("grid")
  grobs <- ggplotGrob(p)

  # wrap strip x text
  if ((class(p$facet)[1] == "grid" && !is.null(names(p$facet$cols))) ||
        class(p$facet)[1] == "wrap")
  {
    ps = calc_element("strip.text.x", th)[["size"]]
    family = calc_element("strip.text.x", th)[["family"]]
    face = calc_element("strip.text.x", th)[["face"]]

    if (class(p$facet)[1] == "wrap") {
      nm = names(p$facet$facets)
    } else {
      nm = names(p$facet$cols)
    }

    # get number of facet columns
    levs = levels(factor(p$data[[nm]]))
    npanels = length(levs)
    if (class(p$facet)[1] == "wrap") {
      cols = n2mfrow(npanels)[1]
    } else {
      cols = npanels
    }

    # get plot width
    sum = sum(sapply(grobs$width, function(x) convertWidth(x, "in")))
    panels_width = par("din")[1] - sum  # inches
    # determine strwrap width
    panel_width = panels_width / cols
    mx_ind = which.max(nchar(levs))
    char_width = strwidth(levs[mx_ind], units="inches", cex=ps / par("ps"), 
                          family=family, font=gpar(fontface=face)$font) / 
      nchar(levs[mx_ind])
    width = floor((panel_width - pad)/ char_width)  # characters

    # wrap facet text
    p$data[[nm]] = unlist(lapply(strwrap(p$data[[nm]], width=width, 
                                         simplify=FALSE), paste, collapse="\n"))
  }

  if (class(p$facet)[1] == "grid" && !is.null(names(p$facet$rows))) {  
    ps = calc_element("strip.text.y", th)[["size"]]
    family = calc_element("strip.text.y", th)[["family"]]
    face = calc_element("strip.text.y", th)[["face"]]

    nm = names(p$facet$rows)

    # get number of facet columns
    levs = levels(factor(p$data[[nm]]))
    rows = length(levs)

    # get plot height
    sum = sum(sapply(grobs$height, function(x) convertWidth(x, "in")))
    panels_height = par("din")[2] - sum  # inches
    # determine strwrap width
    panels_height = panels_height / rows
    mx_ind = which.max(nchar(levs))
    char_height = strwidth(levs[mx_ind], units="inches", cex=ps / par("ps"), 
                           family=family, font=gpar(fontface=face)$font) / 
      nchar(levs[mx_ind])
    width = floor((panels_height - pad)/ char_height)  # characters

    # wrap facet text
    p$data[[nm]] = unlist(lapply(strwrap(p$data[[nm]], width=width, 
                                         simplify=FALSE), paste, collapse="\n"))
  }

  invisible(p)
}

要使用该函数,请调用它来代替print.

library(ggplot2)
df = expand.grid(group=paste(c("Very Very Very Long Group Name "), 1:4),
                 group1=paste(c("Very Very Very Long Group Name "), 5:8),
                 x=rnorm(5), y=rnorm(5), stringsAsFactors=FALSE)

p = ggplot(df) +
  geom_point(aes(x=x, y=y)) +
  facet_grid(group1~group)
strwrap_strip_text(p)
于 2013-05-24T13:11:59.013 回答
3

(太长的评论,但也不是真正的答案)

我认为 ggplot2 中不会直接存在通用解决方案;这是网格单元自引用的经典问题:ggplot2 想要即时计算视口大小,而 strwrap 需要知道一个确定的宽度来决定如何分割文本。(有一个非常相似的问题,但我忘记了何时何地)。

但是,您可以编写一个帮助函数来估计在绘图之前需要多少包装。在伪代码中,

# takes the facetting variable and device size
estimate_wrap = function(f, size=8, fudge=1){ 

    n = nlevels(f)
    for (loop over the labels of strwidth wider than (full.size * fudge) / n){
     new_factor_level[ii] = strwrap(label[ii], available width)
    }

  return(new_factor)
}

(需要一些标准单位转换)

当然,如果你想使用space="free".

于 2013-05-20T21:51:06.203 回答
1

评论也太长了,但没有完整的答案。它符合巴蒂斯特的回答,但还有一些提示:

p <- ggplot(df) + geom_point(aes(x=x, y=y)) + facet_wrap(~groupwrap)

# get the grobs of the plot and get the widths of the columns
grobs <- ggplotGrob(p)
grobs$width

# here you would have to use convertWidth from gridDebug package
# to convert all the units in the widths to the same unit (say 'pt'),
# including exctraction from the strings they are in -- also, I
# couldn't make it work neither for the unit 'null' nor for 'grobwidth',
# so you'll have to add up all the other ones, neglect grobwidth, and
# subtract all the widths that are not null (which is the width of each
# panel) from the device width
library('grid')
convertWidth(DO FOR EACH ELEMENT OF grobs$width)
sum <- SUM_UP_ALL_THE_NON-PANEL_WIDTHS

# get the width of the graphics device
device <- par('din')[1]

# get width of all panels in a row
panels_width <- device - sum

# get total number of panels in your case
df$group <- as.factor(df$group)
npanels <- nlevels(df$group)

# get number of panels per row (i.e. number of columns in graph) with
# the function that ggplot2 uses internally
cols <- n2mfrow(npanels)

# get estimate of width of single panel
panel_width <- panels_width / cols

很抱歉,这仍然是不完整的部分。但这就是我所得到的,所以我希望这些想法可能会有所帮助......

于 2013-05-21T09:57:19.763 回答