I've come across several threads pointing out how to annotate bar charts, but I've tried a number of iterations of this code and can't seem to get the text left justified, starting at 0% on the x axis. I've tried to change hjust to "left", 0.95, and progressively larger numbers - none of them have the text tethered to the x origin.
dummy_data <- tibble(Proportion = c(0.87, 1),
`Person of Interest` = c("Person B", "Person A"))
dummy_data %>%
ggplot(aes(x = Proportion, y = `Person of Interest`,
fill = `Person of Interest`,
label = paste0(`Person of Interest`, "~", scales::percent(Proportion))))+
geom_col(width = 0.5) +
geom_text(position = position_dodge(width = .9), # move to center of bars
vjust = 0, # nudge above top of bar
hjust = "top",
size = 4.5,
colour = "white",
fontface = "bold") +
scale_x_continuous(labels = scales::percent,
limits = c(0, 1.01),
expand = c(0, 0)) +
ggthemes::theme_economist(horizontal = F) +
scale_fill_manual(values = alpha(c("black", "#002D62"), .5)) +
ggtitle("Lack of Skill") +
theme(title = element_text("Lack of Skill"),
plot.title = element_text(hjust = 0.5, face = "italic"),
axis.title.y = element_blank(),
axis.text.y = element_blank(),
axis.ticks.y = element_blank(),
axis.title.x = element_blank(),
axis.text.x = element_text(hjust = 0.25),
legend.position="none",
aspect.ratio = 1/3)
I've often found text data with ggplot maddening - a huge thanks to anyone willing to take a look.
Try this approach that is close to what you want. Your themes can be producing the issues with placing the labels:
#Code
dummy_data %>%
ggplot(aes(x=`Person of Interest`,
y=Proportion,
fill=`Person of Interest`,
label = paste0(`Person of Interest`, "~", scales::percent(Proportion))))+
geom_bar(stat = 'identity')+
geom_text(aes(y=0.13),
size = 4.5,
colour = "white",
fontface = "bold")+coord_flip()+
scale_y_continuous(labels = scales::percent,
limits = c(0, 1.01),
expand = c(0, 0)) +
ggthemes::theme_economist(horizontal = F) +
scale_fill_manual(values = alpha(c("black", "#002D62"), .5)) +
ggtitle("Lack of Skill") +
theme(title = element_text("Lack of Skill"),
plot.title = element_text(hjust = 0.5, face = "italic"),
axis.title.y = element_blank(),
axis.text.y = element_blank(),
axis.ticks.y = element_blank(),
axis.title.x = element_blank(),
axis.text.x = element_text(hjust = 0.25),
legend.position="none",
aspect.ratio = 1/3)
Output:
Related
I want to produce chart like this:
I go with this code:
dat <- data.frame(
name1 = c("A", "B", "C", "D", "E"),
name2 = c("F", "G", "H", "I", "J"),
value = c(-12,10,5,-7,-2)
)
ggplot(dat,aes(x = name1,y = value)) +
geom_bar(stat = "identity",
fill = "#465978",
width = 0.3) +
ylim(-50,50) +
geom_hline(yintercept = 0,
color = "#9e9e9e",
alpha = 0.3,
linetype = "dotted") +
coord_flip() +
theme_bw() +
theme(axis.line = element_blank(),
axis.title = element_blank(),
axis.text.x = element_blank(),
axis.text.y = element_text(colour = "#737373"),
axis.ticks = element_blank(),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
panel.border = element_blank(),
panel.background = element_blank())
and ended up like this: (note: there is a line in the middle, if you can't see, may be due to low resolution)
How to add dat$name2 in the right side (i.e. top x axis)?
If you don't mind, maybe you can help me produce more similar chart?
Offering an alternative to r2evans' solution, you can convert a discrete variable to a continuous one by doing match(x, unique(x)). If you then have continuous variables on both axes, it is easy to add a secondary axis.
Here is how you could do that (with some extra decorations based on your request to make the chart more similar).
library(ggplot2)
dat <- data.frame(
name1 = c("A", "B", "C", "D", "E"),
name2 = c("F", "G", "H", "I", "J"),
value = c(-12,10,5,-7,-2)
)
# Probably easiest to define `cont_name1 = match(name1, unique(name1))` in the data
# instead of having to declare it in the `aes()` every time.
ggplot(dat,aes(x = value,y = match(name1, unique(name1)))) +
geom_tile(width = Inf, height = 0.3, fill = "grey95") +
geom_bar(stat = "identity",
fill = "#465978",
width = 0.3, orientation = "y") +
geom_segment(aes(y = match(name1, unique(name1)) - 0.15,
yend = match(name1, unique(name1)) + 0.3,
xend = value)) +
geom_text(aes(x = ifelse(value < 0, value - 4, value + 4),
y = match(name1, unique(name1)) + 0.2,
label = scales::percent(value, scale = 1, accuracy = 1)),
vjust = 0) +
xlim(-50,50) +
scale_y_continuous(
breaks = match(dat$name1, unique(dat$name1)),
labels = dat$name1,
sec.axis = sec_axis(~ .x, labels = dat$name2, breaks = 1:5)
) +
geom_vline(xintercept = 0,
color = "#9e9e9e",
alpha = 0.3,
linetype = "dotted") +
ggtitle("Title here (0%)") +
theme_bw() +
theme(axis.line = element_blank(),
axis.title = element_blank(),
axis.text.x = element_blank(),
axis.text.y = element_text(colour = "#737373"),
axis.ticks = element_blank(),
plot.title = element_text(hjust = 0.5, colour = "grey60"),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
panel.border = element_blank(),
panel.background = element_blank())
Created on 2021-01-17 by the reprex package (v0.3.0)
Normally (with a continuous scale) I'd recommend scale_y_continuous(sec.axis=...). Unfortunately, scale_discrete does not yet support it (see https://github.com/tidyverse/ggplot2/issues/3171). With that, the two ways to go include geom_text and annotations. I'll offer the first.
To make it consistent on both sides, we'll need to remove the axis labels from the left axis. (I find this annoying, but consistency between axes is important to me. If it is not as much to you, then you can reduce some of the changes.)
ggplot(dat,aes(x = name1,y = value)) +
geom_bar(stat = "identity",
fill = "#465978",
width = 0.3) +
ylim(-50,50) +
geom_hline(yintercept = 0,
color = "#9e9e9e",
alpha = 0.3,
linetype = "dotted") +
geom_text(aes(y = -Inf, label = name1), hjust = 0, colour = "#737373") + # new
geom_text(aes(y = Inf, label = name2), hjust = 1, colour = "#737373") + # new
coord_flip() +
theme_bw() +
theme(axis.line = element_blank(),
axis.title = element_blank(),
axis.text.x = element_blank(),
axis.text.y = element_blank(), # change
axis.ticks = element_blank(),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
panel.border = element_blank(),
panel.background = element_blank())
One problem you will run into with multi-line labels (as in your example) is that of alignment. ggplot2 has a confounded sense of hjust=, where it means both direction of the text box from the point and alignment of the text within the text box. So hjust=0 means "text goes to the right of the point, and the text is left-aligned". There does not seem to be an easy way to have the text box go to the right of the point (first geom_text, on the left-edge) yet have the text right-justified. (I'll be happy if somebody can show an easy way to work around this!)
The workarounds to force that left-edge right-align textbox require knowing a priori the dimensions of the plot so that you can hard-position the text box (not y=-Inf, the left-edge) within the plot boundary and hard-code the limits of the plot. (Know that when you form the plot, none of the functions know what the dimensions of the rendered plot will be, in user-points, centimeters, or similar.)
As an aside, we can add a geom_text and geom_tile for a couple more features.
ggplot(dat,aes(x = name1,y = value)) +
geom_tile(height = 90, width = 0.3, fill = "gray90") +
geom_bar(stat = "identity",
fill = "#465978",
width = 0.3) +
ylim(-50,50) +
geom_hline(yintercept = 0,
color = "#9e9e9e",
alpha = 0.3,
linetype = "dotted") +
geom_text(aes(y = -Inf, label = name1), hjust = 0, colour = "#737373") +
geom_text(aes(y = Inf, label = name2), hjust = 1, colour = "#737373") +
geom_text(aes(label = value), vjust = -1.1) +
coord_flip() +
theme_bw() +
theme(axis.line = element_blank(),
axis.title = element_blank(),
axis.text.x = element_blank(),
axis.text.y = element_blank(),
axis.ticks = element_blank(),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
panel.border = element_blank(),
panel.background = element_blank())
. + > ggplot(dat,aes(x = name1,y = value)) +
geom_tile(aes(y = 0), height = 90, width = 0.3, fill = "gray90") +
geom_bar(stat = "identity",
fill = "#465978",
width = 0.3) +
ylim(-50,50) +
geom_hline(yintercept = 0,
color = "#9e9e9e",
alpha = 0.3,
linetype = "dotted") +
geom_text(aes(y = -Inf, label = name1), hjust = 0, colour = "#737373") +
geom_text(aes(y = Inf, label = name2), hjust = 1, colour = "#737373") +
geom_text(aes(label = value), vjust = -1.1) +
coord_flip() +
theme_bw() +
theme(axis.line = element_blank(),
axis.title = element_blank(),
axis.text.x = element_blank(),
axis.text.y = element_blank(),
axis.ticks = element_blank(),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
panel.border = element_blank(),
panel.background = element_blank())
I am trying to align the caption text in a bar chart, so that it will start at the same vertical margin as the left bar.
library(ggplot2)
library(tibble)
my_df <-
tibble::tribble(~response, ~estimate,
"little_bit", 0.353477,
"no", 0.307639,
"very", 0.338883)
ggplot(my_df, aes(x = reorder(response, -estimate), y = estimate)) +
geom_bar(stat = "identity", width = 0.9, fill = "royalblue") +
ggtitle("do you like swimming with fish?") +
ylab("") +
labs(caption = "this is my caption") +
theme_minimal() +
theme(
axis.line.x = element_blank(),
axis.line.y = element_blank(),
axis.text.y = element_blank(),
panel.grid = element_blank(),
panel.grid.major=element_blank(),
plot.title = element_text(hjust = 0.5, size = 26),
axis.text.x = element_text(angle = 0, size = 14, margin = margin(t = 0, r = 20, b = 0, l = 0)),
axis.title.x = element_blank(),
legend.title = element_blank(),
plot.caption = element_text(hjust = 0, size = 8),
plot.caption.position = "plot") ## <-- I thought this would help, but no...
Is there a way to align the caption such as the following?
To get your desired result set the caption position to "panel" and remove the expansion of the x-scale:
library(ggplot2)
library(tibble)
my_df <-
tibble::tribble(~response, ~estimate,
"little_bit", 0.353477,
"no", 0.307639,
"very", 0.338883)
ggplot(my_df, aes(x = reorder(response, -estimate), y = estimate)) +
geom_bar(stat = "identity", width = 0.9, fill = "royalblue") +
ggtitle("do you like swimming with fish?") +
scale_x_discrete(expand = c(0, 0)) +
labs(caption = "this is my caption", y = NULL) +
theme_minimal() +
theme(
axis.line.x = element_blank(),
axis.line.y = element_blank(),
axis.text.y = element_blank(),
panel.grid = element_blank(),
panel.grid.major=element_blank(),
plot.title = element_text(hjust = 0.5, size = 26),
axis.text.x = element_text(angle = 0, size = 14, margin = margin(t = 0, r = 20, b = 0, l = 0)),
axis.title.x = element_blank(),
legend.title = element_blank(),
plot.caption = element_text(hjust = 0, size = 8),
plot.caption.position = "panel")
Or, a very common workaround for those cases would be to annotate outside of the plot area. (I am using annotate(geom = "text",...) - you could also use geom_text, but you would only need a data frame for it.)
I personally prefer the annotate option as it is more versatile and generalisable for other purposes as well.
library(ggplot2)
my_df <-
tibble::tribble( ~response, ~estimate, "little_bit", 0.353477, "no", 0.307639, "very", 0.338883)
barwidth <- 0.9
ggplot(my_df, aes(x = reorder(response, -estimate), y = estimate)) +
geom_bar(stat = "identity", width = barwidth, fill = "royalblue") +
annotate(
geom = "text",
x = 1 - barwidth / 2, hjust = 0, # that's the trick
y = -0.1, # play around with y. you could also set it relativ to your bar heights
label = "this is my caption"
) +
coord_cartesian(clip = "off", ylim = c(0, NA)) + # necessary to set axis limits and clip off to annotate beyond
ggtitle("do you like swimming with fish?") +
theme_minimal() +
theme(plot.margin = margin(b = 1, unit = "inch")) # margin randomly chosen. unfortunately also necessary
By running this code :
g <- ggplot(results_table, aes(x = "", y = Pct*100,
fill = factor(results_table$Criteria, as.character(results_table$Criteria))),width = 0.5) +
geom_bar(stat = "identity") +
scale_color_manual(values = Palcolor) +
scale_fill_manual(values = Palcolor) +
coord_polar(theta = "y", start = 0, direction = -1) +
theme_minimal() +
theme(legend.position = "bottom", legend.title=element_blank(), axis.title.x = element_blank(),,
axis.title.y = element_blank(), panel.border = element_blank(), panel.grid = element_blank(),
axis.text = element_blank(), axis.ticks = element_blank(),
plot.title = element_text(size = 14, hjust = 0.5, vjust = 0)) +
guides(fill = guide_legend(nrow = 4, byrow = TRUE)) +
theme(
legend.key.height = unit(0.3, "lines"), #smaller squares
legend.key.width = unit(0.7, "lines"), #smaller squares
legend.margin=margin(l = 40, unit='pt'),
legend.text = element_text(margin = margin(r = 60, unit = "pt"))) +
xlab("") +
ylab("") +
geom_text(aes(x = 1.70, y =Pct*100/2 + c(0, cumsum(Pct*100)[-length(Pct*100)]),
label = paste0(sprintf("%0.1f", round(Pct*100, digits = 1)),"%")),
size = 3.2) +
labs(title = gTitle)
}
I get this pie chart :
As you can see, the slices and the color are right, but the labels are reversed. If I use
position = position_stack (vjust = 0.5, reverse = TRUE)
in geom_text, it reduces the whole pie to a small slice, then it doesn't fix my issue.
The solution is to substract the result of the formula for y in geom_text from 100 :
`geom_text(aes(x = 1.70, y = 100-(Pct*100/2 + c(0, cumsum(Pct*100)[-length(Pct*100)])),
label = paste0(sprintf("%0.1f", round(Pct*100, digits = 1)),"%")),
size = 3.2)'
I've created the following plot which I'm quite pleased with when it's static - however, I wanted to make it interactive so when people hover over you can see the seasons (like pudding.cool has done here. The static plot I have:
The code to achieve this is:
character_sentiment_season_adj %>%
mutate(sent_dummy = ifelse(sentimentc < 0, "More Negative", "More Positive")) %>%
mutate(character = reorder(character, avg_sentiment)) %>%
ggplot(aes(character, sentimentc, hoverinfo = season)) +
geom_point(colour = "#666666", size = 4, fill = "#f2f2f2", shape = 21) +
coord_flip() +
geom_hline(yintercept = 0, alpha = 0.2) +
geom_point(aes(character, avg_sentiment, color=avg_sentiment), shape = 21, colour = "#262626", fill = "#ff9933", size = 4) +
geom_text(aes(label = character),
size = 3,
data = subset(character_sentiment_season_adj, sentimentc == max_sentiment),
hjust = -0.5,
vjust = 0.3) +
labs(title = "How negative are The Office (US) characters?",
subtitle = "Sentiment of each character based on bing sentiment scores") +
theme(axis.title.x = element_blank(),
axis.title.y = element_blank(),
axis.text.y = element_blank(),
axis.text.x = element_blank(),
axis.ticks = element_blank(),
axis.line = element_blank(),
panel.background = element_rect(fill = "white"),
plot.title = element_text(hjust = 0.5, face = "bold"),
plot.subtitle = element_text(hjust = 0.5),
plot.caption = element_text(size = 6)
)
However, when I turn it into a Plotly, it starts looking like this:
I've tried using style already so the code for the above plotly is:
ggplotly(p,
tooltip = "season") %>% style(textposition = "right")
I'm quite new to R. I am happy to use a package other than ggplotly if necessary.
I created a ggplot2 object:
a <- replicate(8,rnorm(100))
colnames(a) <- letters[1:8]
b < -melt(a,id.vars=1:1)
colnames(b) <- c("c","variable","value")
ggplot(b,aes(x = c,y = value, colour = variable, linetype = variable)) +
geom_line()+
geom_point(aes(shape = factor(variable)), size = 1.7) +
scale_x_continuous(limits = c(-1, 1),
breaks = seq(-1, 1, 0.1),
expand=c(0.01, 0.01)) +
scale_y_continuous(limits = c(-1, 1),
breaks = seq(-1, 1, 0.1),
expand = c(0.01, 0.01))+
theme_bw(base_size = 12, base_family = "Helvetica") +
theme(axis.text=element_text(size = 10),
axis.title=element_text(size = 10),
text = element_text(size = 10),
axis.line = element_line(size = 0.25),
axis.ticks=element_line(size = 0.25),
panel.grid.major = element_blank(),
#panel.grid.minor = element_blank(),
panel.border = element_rect(colour = "black", fill = NA, size = 0.5),
panel.background = element_blank(),
legend.position = "top" ,
legend.direction = "vertical",
legend.title = element_blank(),
legend.text = element_text(size = 13),
legend.background = element_blank(),
legend.key = element_blank()) +
labs(x = '', y = '', title = "") +
theme(plot.title = element_text(size=10)) +
theme(strip.text.x = element_text(size = 8,color="black"),
strip.background = element_blank()) +
theme(strip.text.x = element_text(size = 8, colour = "black"))
My problem is the following:
when I create the legend, there is a separate legend for the colors and a separate one for the points.
How can I create a single legend for each of the 8 variables?
Let me minimise your code and focus on the legend issue. This is what you have now.
ggplot(b,aes(x = c, y = value, colour = variable, linetype = variable)) +
geom_line() +
geom_point(aes(shape = factor(variable)),size=1.7)
Your data frame, b has variable as factor. You use this in two ways here; variable and factor(variable). You can simply use variable for shape in geom_point; make all variable identical.
ggplot(b,aes(x = c, y = value, colour = variable, linetype = variable)) +
geom_line()+
geom_point(aes(shape = variable),size = 1.7)
I saw some warning messages related to colours and other things. You may want to take care of them. But, for legend, this is one way to go.
Take from the ideas on this page: http://www.cookbook-r.com/Graphs/Legends_(ggplot2)/#modifying-the-text-of-legend-titles-and-labels
I edited your code to make the data visible (you had problems with your x-axis limits. Note the final three lines. These commands tell ggplot to create only one legend.
a<-replicate(6,rnorm(100))
colnames(a)<-letters[1:6]
b<-melt(a,id.vars=1:1)
colnames(b)<-c("c","variable","value")
ggplot(b,aes(x=c,y=value,colour=variable,linetype=variable)) +
geom_line() + geom_point(aes(shape=factor(variable)),size=1.7)+
scale_x_continuous(limits=c(0,100))+
scale_y_continuous(limits=c(-2,2),breaks=seq(-2,2,0.1),expand=c(0.01,0.01))+
theme_bw(base_size=12, base_family="Helvetica") +
theme(axis.text=element_text(size=10),
axis.title=element_text(size=10),
text = element_text(size=10),
axis.line = element_line(size=0.25),
axis.ticks=element_line(size=0.25),
panel.grid.major = element_blank(),
#panel.grid.minor = element_blank(),
panel.border = element_rect(colour="black",fill=NA,size=0.5),
panel.background = element_blank(),
legend.position="top" ,
legend.direction="vertical",
legend.title=element_blank(),
legend.text=element_text(size=13),
legend.background=element_blank(),
legend.key=element_blank())+
labs(x='', y='',title="")+
theme(plot.title=element_text(size=10))+
theme(strip.text.x = element_text(size = 8,color="black"),strip.background=element_blank())+
theme(strip.text.x = element_text(size = 8,color="black"))+
scale_colour_discrete(name ="Factor")+
scale_linetype_discrete(name ="Factor") +
scale_shape_discrete(name ="Factor")