In ggplot2 version 0.9 the behaviour of the alignment of a plot title changed. Whereas in v0.8.9 the alignment was relative to the plot window, in v0.9 the alignment is relative to the plotting grid.
Now, whereas I mostly agree that this is desirable behaviour, I quite often have very long plot titles.
Question: Is there a way of aligning the plot title with the plot window rather than the plot grid?
I'm looking for an solution that does automatic alignment of the plot. In other words, manual alignment using hjust would not work for me (I run this on hundreds of plots for each project).
Any solution that used grid directly is also acceptable.
Some sample code and plot: (Notice how the title gets truncated at the right of window).
dat <- data.frame(
text = c(
"It made me feel very positive to brand X",
"It was clear and easy to understand",
"I didn't like it al all"),
value=runif(3)
)
library(ggplot2)
ggplot(dat, aes(text, value)) +
geom_bar(stat="identity") +
coord_flip() +
opts(title="Thinking about the ad that you've just seen, do you agree with the following statements? I agree that...") +
theme_bw(16)
In ggplot2 0.9 you can easily change the layout.
p <- ggplot(dat, aes(text, value)) +
geom_bar(stat="identity") +
coord_flip() +
opts(title="Thinking about the ad that you've just seen,\ndo you agree with the following statements?\nI agree that...") +
theme_bw(16)
gt <- ggplot_gtable(ggplot_build(p))
gt$layout[which(gt$layout$name == "title"), c("l", "r")] <- c(1, max(gt$layout$r))
grid::grid.draw(gt)
Perhaps, in the future version, ggplot2 will provide consistent interfaces for tweaking the layout.
Here's a solution under ggplot2 2.2.1. A function arranges the title text object over the top center of the ggplot.
library(gridExtra)
# A function that puts a title text object centered above a ggplot object "p"
add_centered_title <- function(p, text){
grid.arrange(p, ncol = 1, top = text)
}
# Create the chart from your sample data
test_chart <- ggplot(dat, aes(text, value)) +
geom_bar(stat="identity") +
coord_flip() +
theme_bw(16)
# Usage:
add_centered_title(test_chart,
"Thinking about the ad that you've just seen, do you agree with the following statements? I agree that...")
# Or you can pipe a ggplot into this function using the %>% dplyr pipe:
library(dplyr)
test_chart %>%
add_centered_title("Thinking about the ad that you've just seen, do you agree with the following statements? I agree that...")
Related
Looking for some gglot help here, for a pretty non-standard plot type. Here is code for one sample graph, but the final product will have dozens like this.
library(ggplot2)
library(data.table)
SOURCE <- c('SOURCE.1','SOURCE.1','SOURCE.2','SOURCE.2')
USAGE <- rep(c('USAGE.1','USAGE.2'),2)
RATIO <- c(0.95,0.05,0.75,0.25)
x <- data.table(SOURCE,USAGE,RATIO)
ggplot(x, aes(x=SOURCE,y=RATIO,group=USAGE)) +
geom_point() +
geom_line() +
geom_label(aes(label=USAGE))
This produces a graph with two lines, as desired. But the label geom adds text to the endpoints. What we want is the text to label the line (appearing once per line, around the middle). The endpoints will always have the same labels so it just creates redundancy and clutter (each graph will have different labels). See the attached file, mocked up in a paint programme (ignore the font and size):
I know we could use geom_line(aes(linetype=USAGE)), but we prefer not to rely on legends due to the sheer number of graphs required and because each graph is quite minimal as the vast majority will have just the two lines and the most extreme cases will only have four.
(Use of stacked bars deliberately avoided.)
You can achieve this with annotate and can move the label around by changing the x and y values.
library(ggplot2)
#library(data.table)
SOURCE<-c('SOURCE.1','SOURCE.1','SOURCE.2','SOURCE.2')
USAGE<-rep(c('USAGE.1','USAGE.2'),2)
RATIO<-c(0.95,0.05,0.75,0.25)
#x<-data.table(SOURCE,USAGE,RATIO)
df <- data.frame(SOURCE, USAGE, RATIO)
ggplot(df, aes(x=SOURCE,y=RATIO,group=USAGE)) +
geom_point() +
geom_line() +
#geom_label(aes(label=USAGE))+
annotate('text', x=1.5, y=1, label = USAGE[1])+
annotate('text', x=1.5, y=0.25, label = USAGE[2])
I'm trying to make a stacked bar chart with text labels, this some example data / code:
library(reshape2)
ConstitutiveHet <- c(7,13)
Enhancer <- c(12,6)
FacultativeHet <- c(25,39)
LowConfidence <- c(3,4)
Promoter <- c(5,4)
Quiescent <- c(69,59)
RegPermissive <- c(23,18)
Transcribed <- c(12,11)
Bivalent <- c(6,22)
group <- c("all","GWS")
meanComb <- data.frame(ConstitutiveHet,Enhancer,LowConfidence,Promoter,Quiescent,RegPermissive,Transcribed,Bivalent,group)
meanCombM <- melt(meanComb,id.vars = "group")
ggplot(meanCombM,aes(group,value,label=value)) +
geom_col(aes(fill=variable))+
geom_text(position = "stack")+
coord_flip()
The text labels appear out of order, they seem to be the mirror image of their intended order. (you get the same problem with or without the coord_flip())
A poster had a similar problem here:
ggplot2: add ordered category labels to stacked bar chart
An answer to their post propsed reversing the order of the values in the groups, which I tried (see below), the resulting order on the plot is not one i've been able to figure out. Also this approach seems hacky, is there a bug here or am I missing something?
x <- c(rev(meanCombM[meanCombM$group=="GWS",]$value),rev(meanCombM[meanCombM$group=="all",]$value))
ggplot(meanCombM,aes(group,value,label=x)) +
geom_col(aes(fill=variable))+
geom_text(position = "stack")+
coord_flip()
ggplot(meanCombM,aes(group,value,label=value)) +
geom_col(aes(fill=variable))+
geom_text(aes(group=variable),position = position_stack(vjust = 0.5))+
coord_flip()
Hadley answered a question similar to my own in this issue in ggplot2's git repository: https://github.com/tidyverse/ggplot2/issues/1972
Apparently the default grouping behaviour (see:
http://ggplot2.tidyverse.org/reference/aes_group_order.html) does not partition the data correctly here without specifying a group aesthetic, which should map to the same value as fill in geom_col in this example.
I have a plot that is a simple barplot of number of each type of an event. I need the labels of the plot to be under the plot as some of the events have very long names and were squashing the plot sideways. I tried to move the labels underneath the plot but it now gets squashed upwards when there are lots of event types. Is there a way of having a static plot size (i.e. for the bar graph) so that long legends don't squash the plot?
My code:
ggplot(counts_df, aes(x = Var2, y = value, fill - Var1)+
geom_bar(stat = "identity") +
theme(legend.position = "bottom") +
theme(legen.direction = "vertical") +
theme(axis.text.x = element_text(angle = -90)
The result:
I think this is because the image size must be static so the plot gets sacrificed for the axis. The same thing happens when I put a legend beneath the plot.
There a several ways to avoid overplotting of labels or squeezing the plot area or to improve readability in general. Which of the proposed solutions is most suitable will depend on the lengths of the labels and the number of bars, and a number of other factors. So, you will probably have to play around.
Dummy data
Unfortunately, the OP hasn't included a reproducible example, so we we have to make up our own data:
V1 <- c("Long label", "Longer label", "An even longer label",
"A very, very long label", "An extremely long label",
"Long, longer, longest label of all possible labels",
"Another label", "Short", "Not so short label")
df <- data.frame(V1, V2 = nchar(V1))
yaxis_label <- "A rather long axis label of character counts"
"Standard" bar chart
Labels on the x-axis are printed upright, overplotting each other:
library(ggplot2) # version 2.2.0+
p <- ggplot(df, aes(V1, V2)) + geom_col() + xlab(NULL) +
ylab(yaxis_label)
p
Note that the recently added geom_col() instead of geom_bar(stat="identity") is being used.
OP's approach: rotate labels
Labels on x-axis are rotated by 90° degrees, squeezing the plot area:
p + theme(axis.text.x = element_text(angle = 90))
Horizontal bar chart
All labels (including the y-axis label) are printed upright, improving readability but still squeezing the plot area (but to a lesser extent as the chart is in landscape format):
p + coord_flip()
Vertical bar chart with labels wrapped
Labels are printed upright, avoiding overplotting, squeezing of plot area is reduced. You may have to play around with the width parameter to stringr::str_wrap.
q <- p + aes(stringr::str_wrap(V1, 15), V2) + xlab(NULL) +
ylab(yaxis_label)
q
Horizontal bar chart with labels wrapped
My favorite approach: All labels are printed upright, improving readability,
squeezing of plot area are is reduced. Again, you may have to play around with the width parameter to stringr::str_wrap to control the number of lines the labels are split into.
q + coord_flip()
Addendum: Abbreviate labels using scale_x_discrete()
For the sake of completeness, it should be mentioned that ggplot2 is able to abbreviate labels. In this case, I find the result disappointing.
p + scale_x_discrete(labels = abbreviate)
To clarify, what this question appears to be asking about is how to specify the panel size in ggplot2.
I believe that the correct answer to this question is 'you just can't do that'.
As of the present time, there does not seem to be any parameter that can be set in any ggplot2 function that would achieve this. If there was one, I think it would most likely be in the form of height and width arguments to an element_rect call within a call to theme (which is how we make other changes to the panel, e.g. altering its background colour), but there's nothing resembling those in the docs for element_rect so my best guess is that specifying the panel size is impossible:
https://ggplot2.tidyverse.org/reference/element.html
The following reference is old but I can't find anything more up to date that positively confirms whether or not this is the case:
https://groups.google.com/forum/#!topic/ggplot2/nbhph_arQ7E
In that discussion, someone asks whether it is possible to specify the panel size, and Hadley says 'Not yet, but it's on my to do list'. That was nine years ago; I guess it's still on his to do list!
One more solution in addition to those above - use staggered labels. These can be used with text wrapping to get a fairly readable result:
p + scale_x_discrete(guide = ggplot2::guide_axis(n.dodge = 2),
labels = function(x) stringr::str_wrap(x, width = 20))
(Using the plot p from #Uwe's answer)
I found other methods didn't quite get what I wanted. I made this function to add a couple of dots after long names
tidy_name <- function(name, n_char) {
ifelse(nchar(name) > (n_char - 2),
{substr(name, 1, n_char) %>% paste0(., "..")},
name)
}
vec <- c("short", "medium string", "very long string which will be shortened")
vec %>% tidy_name(20)
# [1] "short" "medium string" "very long string whi.."
Context: R/ggplot2.
Is there an automated way (or even a manual way) to put the legend factors inside the plot like the energies here (Co, 4,6,10,...), instead of having them in a regular legend box next to the plot ?
Source: Radiation Oncology Physics: A Handbook for Teachers and Students, EB. Podgorsak
So this seems close. I'd characterize this as "semi-automatic": there's definitely some tweaking needed, but most of the work is done for you...
The tricky bit is not placing the text labels (geom_text(...)), but creating the breaks in the plotted curves. This is done with geom_rect(...), where the width of the rectangles are set to the maximum label width, as determined using strwidth(...).
# create sample data
df <- data.frame(x=rep(seq(0,20,.01),5),k=rep(1:5,each=2001))
df$y <- with(df,x*exp(-x/k))
library(ggplot2)
eps.x <- max(strwidth(df$k)) # maximum width of legend label
eps.y <- eps.x*diff(range(df$y))/diff(range(df$x))
ggplot(df,aes(x,y))+
geom_line(aes(group=factor(k)))+
geom_rect(data=df[df$x==5,],
aes(xmax=x+eps.x, xmin=x-eps.x, ymax=y+eps.y, ymin=y-eps.y),
fill="white", colour=NA)+
geom_text(data=df[df$x==5,],aes(x,y,label=k))+
theme_bw()
If you want to color the lines too:
ggplot(df,aes(x,y))+
geom_line(aes(color=factor(k)))+
geom_rect(data=df[df$x==5,],
aes(xmax=x+eps.x, xmin=x-eps.x, ymax=y+eps.y, ymin=y-eps.y),
fill="white", colour=NA)+
geom_text(data=df[df$x==5,],aes(x,y,label=k), colour="black")+
scale_color_discrete(guide="none")+
theme_bw()
Hi I've got the following code
d1=data.frame(a=c(4,5,6,7),b=as.Date(c('2005-12-31','2006-12-31','2007-12-31','2008-12-31'),"%Y-%m-%d"))
a = ggplot(d1,aes(x=b,y=a)) + geom_line()
a + annotate('text',x=as.Date('2006-12-31','%Y-%m-%d'),y=5.5,label='blah')
But annotating the graph is really clunky. I'd like to be able to specify the x axis using percentage of axis (for example) or inches or something else. Is this possible and how would I go about it?
Thanks
Your only option, I think, is to post-process the graph using grid. You'll need to expose the viewports and navigate to the plot panel, and there you have access to all grid units. Following Paul Murrell's example:
library(ggplot2)
library(grid)
qplot(1:10, rnorm(10))
# grid.force() # doesn't seem necessary?
# grid.ls()
downViewport("panel.3-4-3-4")
grid.text(label = "Some text", x = unit(0,"inch"),hjust=0)
grid.text(label = "Some text", x = unit(0.5,"npc"),hjust=0.5)
upViewport(0)
The package 'scales' includs a ton of formatter options: e.g. to format the y-axis in your example to percent use "scale_y_continuous(labels = percent)"
require(ggplot2)
require(scales)
d1=data.frame(a=c(4,5,6,7),b=as.Date(c('2005-12-31','2006-12-31','2007-12-31','2008-12- 31'),"%Y-%m-%d"))
a = ggplot(d1,aes(x=b,y=a)) + geom_line() + scale_y_continuous(labels = percent)
a + annotate('text',x=as.Date('2006-12-31','%Y-%m-%d'),y=5.5,label='blah')
Have a look at the ggplot docs as well.