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"))
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"))
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)
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
How to Use Superscript with Ggplot2
How to Print the Structure of an R Object to the Console
Geom_Rect Failure: Error in Eval(Expr, Envir, Enclos):Object 'Variable' Not Found
How to Remove All Rows from a Data.Frame
Test for Na and Select Values Based on Result
How to Rotate the Axis Labels in Ggplot2
Align Plots Next to Each Other with Knitr
Rmarkdown Error "Attempt to Use Zero-Length Variable Name"
How to Make a Data Frame into a Simple Features Data Frame
Conditionally Apply Pipeline Step Depending on External Value
How to Jitter Two Ggplot Geoms in the Same Way
How to Convert .Rdata Format into Text File Format
How to Use Custom Functions in Mutate (Dplyr)
Rolling Regression by Group in the Tidyverse
String Split on Last Comma in R
Pretty Axis Labels for Log Scale in Ggplot
How to Apply a Hierarchical or K-Means Cluster Analysis Using R