How to Fit Long Text into Ggplot2 Facet Titles

How to Fit Long Text into Ggplot2 facet Titles

A commonly used package already has this functionality: use stringr::str_wrap().

library(stringr)
library(plyr)
library(dplyr)

var_width = 60
my_plot_data <- mutate(my_plot_data, pretty_varname = str_wrap(long_varname, width = var_width))

And then proceed with your plot.

How to dynamically wrap facet label using ggplot2

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)

ggplot2: Splitting facet/strip text into two lines

I tried this a variety of ways but was frustrated getting the paste(strwrap(text, width=40), collapse=" \n") to give me results for the single row of data and not concatenate the each bit of text from the entire list.

I came up with a solution that worked best for me. I wrote a function like the one below. Given a dataframe data with column text

wrapit <- function(text) {
wtext <- paste(strwrap(text,width=40),collapse=" \n ")
return(wtext)
}

data$wrapped_text <- llply(data$text, wrapit)
data$wrapped_text <- unlist(data$wrapped_text)

After I called this function, I just applied my labeller function to the wrapped_text column instead of the text column.

r - How can I get a lengthy label to fit using facet_grid in ggplot2?

While the answers so far are helpful in wrapping the text, the labels remain difficult to read. I've decided to go with adding a couple of zero values on both sides of the variable of interest and manually added a hyphenated name with a space for the factor of interest, changing "PythonhasaREALLYWAYTOOlonglabel" to "PythonhasaREALLY- WAYTOOlonglabel", which does what I'd like better so far. While it leaves perhaps too big of spaces on both sides of the "PythonhasaREALLY- WAYTOOlonglabel", it gives me the space I need.

Using the following code:

label_wrap_gen <- function(width = 100) {
function(variable, value) {
lapply(strwrap(as.character(value), width=width, simplify=FALSE),
paste, collapse="\n")
}
}

Data <- data.frame(Language=c("C++","C++","C++", "Java","Java","Java","Java", "PythonhasaREALLY- WAYTOOlonglabel","PythonhasaREALLY- WAYTOOlonglabel","PythonhasaREALLY- WAYTOOlonglabel"), #note that I add a hyphen here and two placeholders that will have 0 values
Files=c(400, 210, 35,55,330,220,213,0,76,0), #note that I add two 0 values here
Difficulty=c("a","b","c","d","e","f","g","h","i","j"),
stringsAsFactors=FALSE)

Data
g <- ggplot(Data,aes(x=Difficulty,y=Files,fill=Difficulty)) #replaced fill=feetype,
h <- g + geom_bar(stat="identity",position="dodge") + facet_grid(.~ Language, scales = "free_x", space="free",labeller=label_wrap_gen(width=.1))

There is probably a way to make the spaces on either side a bit narrower, but I'm not sure how to do that yet...

how to fit strip.text.x if the heading string is too long

If you can use facet_grid rather than facet_wrap, you can use the functionality of the labeller argument to arbitrarily wrap the text. (facet_wrap does not (yet) have a labeller argument.)

Make x reproducible

x <-
structure(list(Date = structure(c(-719143, -718778), class = "Date"),
Duration = c(10L, 3L), Test = c("This is the first that took place on1/1/2012",
"This test peformed the best result")), .Names = c("Date",
"Duration", "Test"), row.names = c(NA, -2L), class = "data.frame")

Helper function to wrap labels (discussed in more detail on the ggplot2 wiki)

library("plyr")
label_wrap_gen <- function(width = 25) {
function(variable, value) {
laply(strwrap(as.character(value), width=width, simplify=FALSE),
paste, collapse="\n")
}
}

Plotting code. Changed to points since just two values were given so lines and smoothing don't make sense. But those were not the focus of the question anyway.

ggplot(x, aes(Date, Duration, group=Test, colour=Test)) + 
geom_point() +
facet_grid(~Test, scale="free", labeller=label_wrap_gen(width=10)) +
opts(strip.text.x = theme_text(size=8, colour="navyblue"))

Sample Image

EDIT:

Since facet_grid won't work for you, you could embed the newlines into the Test variable directly and then use facet_wrap.

x$Test <- laply(strwrap(as.character(x$Test), width=10, simplify=FALSE), 
paste, collapse="\n")
ggplot(x, aes(Date, Duration, group=Test, colour=Test)) +
geom_point() +
facet_wrap(~Test, scale="free") +
opts(strip.text.x = theme_text(size=8, colour="navyblue"))

Sample Image

I don't understand your other question about headers made in the comment. Perhaps make a new, clearer question out of that.

Left justify text from multi-line facet labels

This is fairly straightforward using grid's grid.gedit function to edit the strips.

library(ggplot2)  # v2.1.0
library(grid)

# Your data
set.seed(3)
df = data.frame(facet_label_text = rep(c("Label A",
"Label B\nvery long label",
"Label C\nshort",
"Label D"),
each = 5),
time = rep(c(0, 4, 8, 12, 16), times = 4),
value = runif(20, min=0, max=100))

# Your plot
p = ggplot(df, aes(x = time, y = value)) +
geom_line() +
facet_grid(facet_label_text ~ .) +
theme(strip.text.y = element_text(angle = 0, hjust = 0))
p

# Get a list of grobs in the plot
grid.ls(grid.force())

# It looks like we need the GRID.text grobs.
# But some care is needed:
# There are GRID.text grobs that are children of the strips;
# but also there are GRID.text grobs that are children of the axes.
# Therefore, a gPath should be set up
# to get to the GRID.text grobs in the strips

# The edit
grid.gedit(gPath("GRID.stripGrob", "GRID.text"),
just = "left", x = unit(0, "npc"))

Or, a few more lines of code to work with a grob object (in place of editing on screen as above):

# Get the ggplot grob
gp = ggplotGrob(p)
grid.ls(grid.force(gp))

# Edit the grob
gp = editGrob(grid.force(gp), gPath("GRID.stripGrob", "GRID.text"), grep = TRUE, global = TRUE,
just = "left", x = unit(0, "npc"))

# Draw it
grid.newpage()
grid.draw(gp)

Sample Image

facet label font size

This should get you started:

R> qplot(hwy, cty, data = mpg) + 
facet_grid(. ~ manufacturer) +
theme(strip.text.x = element_text(size = 8, colour = "orange", angle = 90))

See also this question: How can I manipulate the strip text of facet plots in ggplot2?

How to change facet labels?

Change the underlying factor level names with something like:

# Using the Iris data
> i <- iris
> levels(i$Species)
[1] "setosa" "versicolor" "virginica"
> levels(i$Species) <- c("S", "Ve", "Vi")
> ggplot(i, aes(Petal.Length)) + stat_bin() + facet_grid(Species ~ .)


Related Topics



Leave a reply



Submit