How to dynamically wrap facet label using ggplot2

前端 未结 4 914
余生分开走
余生分开走 2020-12-14 15:52

I\'m looking for a way to dynamically wrap the strip label text in a facet_wrap or facet_grid call. I\'ve found a way to accomplish this using

相关标签:
4条回答
  • 2020-12-14 16:03

    (too long as a comment, but not a real answer either)

    I don't think a general solution will exist directly within ggplot2; it's the classic problem of self-reference for grid units: ggplot2 wants to calculate the viewport sizes on-the-fly, while the strwrap would need to know a firm width to decide how to split the text. (there was a very similar question, but I forget when and where).

    You could however write a helping function to estimate how much wrapping you'll need before plotting. In pseudo code,

    # 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)
    }
    

    (with some standard unit conversions required)

    Of course, things would get more complicated if you wanted to use space="free".

    0 讨论(0)
  • 2020-12-14 16:11

    Since this question was posted, the new label_wrap_gen() function with ggplot2 (>= 1.0.0, I think) handles this nicely:

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

    Note that you have to specify a width for it to work.

    For older ggplot2 versions:

    facet_wrap(~groupwrap, labeller = label_wrap_gen())
    
    0 讨论(0)
  • 2020-12-14 16:14

    Also too long for a comment but no full answer. It goes along the lines of baptiste's answer, but with a few more pointers:

    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
    

    Sorry that this is still patchy in parts. But that is as far as I got, so I hope these ideas might help along the way...

    0 讨论(0)
  • 2020-12-14 16:15

    Thanks to the guidance from @baptiste and @thunk, I created the function below, which seems to do a pretty good job of automatically wrapping facet labels. Suggestions for improvement are always welcome, though.

    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)
    }
    

    To use the function, call it in place of 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)
    
    0 讨论(0)
提交回复
热议问题