I am creating an R package that will produce graphs that naturally go into all four quadrants - positive and negative values on the x and y axis.
I would like to create a ggplot2 theme that will center the axes at (0,0) to help make this look nice!
Obviously I could do this on a graph-by-graph basis by adding geom_vline and geom_hline. Effectively, this is what I want the result to look like:
library(ggplot2)
ggplot(mtcars, aes(x = mpg-20, y = hp-150)) +
geom_point() +
theme_void() +
geom_vline(aes(xintercept = 0)) +
geom_hline(aes(yintercept = 0))
This is perfect - add some decoration and I'm done.
However, I would like to do this in the theme so that users of the package can just do +theme_fourquadrant() and have it done for them.
When I try to define a theme like this...
theme_fourquadrant <- function() {
theme_void() %+replace%
geom_vline(aes(xintercept = 0)) +
geom_hline(aes(yintercept = 0))
}
I get the error that %+replace% requires a theme object on either side. If I replace that with + I get that you can't add geoms to a theme object, naturally.
I also haven't had luck trying to only get a major grid axis to show up for 0.
Is this possible to do? I'm a little worried that "0" counts as data and so the theme will refuse to have anything to do with it. I'm sure there are some ways I can do this by not making it happen in the theme, but having it done in the theme would be ideal.
Related
I'm using ggplot and I get those weird horizontal lines out of geom_bar. I cannot provide a minimal working example: the same code works with few observations and it relies on data I am importing and transforming. However, I can show the relevant line of codes and cross my fingers someone ran into this issue:
ggplot(data) + geom_bar(aes(x=Horizon, y=Importance, fill=Groups),
position='fill', stat='identity') +
theme_timeseries2() +
scale_fill_manual(values=c('#1B9E77', 'orange2', 'black',
'red2', 'blue4')) +
xlab('') + ylab('')
My personal function, theme_timeseries2() isn't the source of the problem: it happens even if I stop after geom_bar. I checked for missing values in Importance and every other column of my data frame and there are none.
It's also very odd: the white lines aren't the same on the zoomed page as in the plot window of RStudio. They do print in .png format when I save the file, so there really is something going on with those horizontal bars. Any theory about why geom_bar() does this would be highly appreciated.
You can fix it by adding the fill as color. Like this:
geom_bar(aes(x=Horizon, y=Importance, fill=Groups, color=Groups),
position='fill', stat='identity')
This was suggested here.
I'm guessing the lines are due to a plotting bug between observations that go into each bar. (That could be related to the OS, the graphics device, and/or how ggplot2 interacts with them...)
I expect it'd go away if you summarized before ggplot2, e.g.:
library(dplyr);
data %>%
count(Horizon, Groups, wt = Importance, name = "Importance") %>%
ggplot() +
geom_col(aes(x = Horizon, y= Importance, fill = Groups), position = "fill") + ....
Mine went away when changing the size of the graphs in rmarkdown.
I'm working on a custom ggplot2 theme and was thinking it could be nifty to automatically modify elements of the theme depending on certain characteristics of the the plot object. For instance, is there a way to specify that if the plot contains facets, add a border to each panel?
I guess the question is really, can I access the current gg object from within a custom theme() call and then conditionally apply certain theme elements? In my head I would define my theme function to be something like this:
theme_custom <- function() {
if (plot$facet$params > 0) {
theme_minimal() +
theme(panel.border = element_rect(color = "gray 50", fill = NA))
}
else {
theme_minimal()
}
}
If this is possible, it would look like this in use:
library(ggplot2)
# plot with facets automatically adds panel borders
ggplot(mtcars, aes(mpg, wt)) +
geom_point() +
facet_wrap(vars(cyl)) +
theme_custom()
# plot without facets no panel border
ggplot(mtcars, aes(mpg, wt)) +
geom_point() +
theme_custom()
NOTE: This was originally posted on RStudio Community and did not receive an answer.
I think Oliver was thinking in the correct direction.
I don't think the theme_custom function is the correct place to check the plot for conditional theming, because theme functions are mostly agnostic about the precise plot that they are added to.
Instead, I think the appropriate place to check the plot is when the theme is added to the plot. You could write a theme function like the following, which sets a different class to the output.
theme_custom <- function() {
out <- theme_minimal()
class(out) <- c("conditional_theme", class(out))
out
}
Now everytime a theme is added to a plot, this is done through the ggplot_add.theme function, which we can rewrite for the conditional_theme class. In my opinion, the correct way to check if a plot is facetted, is to check the class of the plot$facet slot, which can be FacetGrid, FacetWrap etc when a proper facet is added and defaults to FacetNull when no facet is set.
ggplot_add.conditional_theme <- function(object, plot, object_name) {
if (!inherits(plot$facet, "FacetNull")) {
object <- object + theme(panel.border = element_rect(colour = "grey50", fill = NA))
}
plot$theme <- ggplot2:::add_theme(plot$theme, object, object_name)
plot
}
And now the use cases should work as intended:
ggplot(mtcars, aes(mpg, wt)) +
geom_point() +
facet_wrap(vars(cyl)) +
theme_custom()
ggplot(mtcars, aes(mpg, wt)) +
geom_point() +
theme_custom()
The only downside is that you would literally have to add the theme to the plot every time and you can't use the theme_set(theme_custom()) to have this apply to any plot in the session.
This requires a bit more knowledge than my current level of expertise in ggproto and ggproto_method objects. So this is not a complete answer, but a possible direction.
If you can gain access to the plot ggproto object, this object contains a ggproto_method in stored in the ggproto$facet$compute_layout. Depending on whether the plot contains a call to geom_facet, this will have a varying function length, as illustrated below
data(mtcars)
library(ggplot2)
p <- ggplot(mtcars, mapping = aes(x = hp, y = mpg)) +
geom_point()
pfacet <- p + facet_wrap(.~cyl)
nchar(format(p$facet$compute_layout))
[1] 139
nchar(format(pfacet$facet$compute_layout))
[1] 1107
(Note that 139 seems to be standard for any ggproto not containing a facet)
This assumes you can gain access to the proto object every time the plot is called or that you place your method as the a call after facet_wrap or similar methods are called, and is indeed just a hacky method due to my lack of knowledge of the intricates of gg, ggproto and ggproto_method objects.
From a related post about conditionally adding ggplot elements it transpires one can add elements using {if(cond)expr}+ formatting, i.e. put the whole element in {} then follow with the +.
One can combine this with theme element replacement formatting, e.g.
theme_minimal() %+replace% theme(axis.title.y.right = element_text(angle = 90)) +
To give:
{if(cond) theme() %+replace% theme(element = value)} +
So, shamelessly stealing from (/standing on the gigantic shoulders of) #teunbrand 's answer:
{if (!inherits(plot$facet, "FacetNull")) theme() %+replace% theme(panel.border = element_rect(colour = "grey50", fill = NA))} +
This works for my code but I'm not 100% sure about your example, apologies for not testing, in the middle of a huge function edit, but wanted to share this approach for its general applicability.
One nice thing about this approach is that it's easy to chain element edits within the same condition, and have different conditions in their own {if}.
I've built a function that utilizes ggplot2 to create a bar chart for a given style of summary table, but there are a few changes I'd like to make that I haven't quite figured out. Here's what the function looks like:
bar_chart_dist <- function(df, x_var, y_var, title) {
title_in_fun <- title
p <- ggplot(df, aes_string(x = df[,x_var], y = df[,y_var])) +
geom_bar(stat = "identity", fill="#005a8c") +
geom_text(aes_string(label= y_var, vjust = -0.2)) +
xlab("") + ylab(y_var) + my_theme() +
ggtitle(title_in_fun) + scale_y_continuous(limits=c(0,100))
return(p)
}
The my_theme function edits the font family to Open Sans and changes the color to grey, among other things.
The data frames I am using with this function are each three variables long -- topic (this name changes with each given dataframe), n (number of observations) and percent_of_population (pre-calculated percent of total population in a given group). I'm using topic as my x_var and percent_of_population as my y_var.
There are a few things here I haven't gotten to work:
1) I'd like the y-axis to be labeled with a percent sign (%) and to span 0% to 100%. I've tried to edit the scale_y_continuous argument to be:
scale_y_continuous(labels=percent, limits=c(0,100))
but that changes the scale such that my upper boundary is 10,000%.
2) I'd like to change the font color, size, and family in the geom_text argument, as well as add a % sign to the label. The family I'd like to use is Open Sans, but it doesn't seem to recognize that. When I set size = 4, a legend is created, which does not seem to happen in the examples I've looked at.
Any help you guys can provide is very much appreciated. I'm not sure what's not working because this is wrapped in a function, and what's not working because it's the wrong approach. Here's what the plot looks like in current state:
I would like to use customized linetypes in ggplot. If that is impossible (which I believe to be true), then I am looking for a smart hack to plot arrowlike symbols above, or below, my line.
Some background:
I want to plot some water quality data and compare it to the standard (set by the European Water Framework Directive) in a red line. Here's some reproducible data and my plot:
df <- data.frame(datum <- seq.Date(as.Date("2014-01-01"),
as.Date("2014-12-31"),by = "week"),y=rnorm(53,mean=100,sd=40))
(plot1 <-
ggplot(df, aes(x=datum,y=y)) +
geom_line() +
geom_point() +
theme_classic()+
geom_hline(aes(yintercept=70),colour="red"))
However, in this plot it is completely unclear if the Standard is a maximum value (as it would be for example Chloride) or a minimum value (as it would be for Oxygen). So I would like to make this clear by adding small pointers/arrows Up or Down. The best way would be to customize the linetype so that it consists of these arrows, but I couldn't find a way.
Q1: Is this at all possible, defining custom linetypes?
All I could think of was adding extra points below the line:
extrapoints <- data.frame(datum2 <- seq.Date(as.Date("2014-01-01"),
as.Date("2014-12-31"),by = "week"),y2=68)
plot1 + geom_point(data=extrapoints, aes(x=datum2,y=y2),
shape=">",size=5,colour="red",rotate=90)
However, I can't seem to rotate these symbols pointing downward. Furthermore, this requires calculating the right spacing of X and distance to the line (Y) every time, which is rather inconvenient.
Q2: Is there any way to achieve this, preferably as automated as possible?
I'm not sure what is requested, but it sounds as though you want arrows at point up or down based on where the y-value is greater or less than some expected value. If that's the case, then this satisfies using geom_segment:
require(grid) # as noted by ?geom_segment
(plot1 <-
ggplot(df, aes(x=datum,y=y)) + geom_line()+
geom_segment(data = data.frame( df$datum, y= 70, up=df$y >70),
aes(xend = datum , yend =70 + c(-1,1)[1+up]*5), #select up/down based on 'up'
arrow = arrow(length = unit(0.1,"cm"))
) + # adjust units to modify size or arrow-heads
geom_point() +
theme_classic()+
geom_hline(aes(yintercept=70),colour="red"))
If I'm wrong about what was desired and you only wanted a bunch of down arrows, then just take out the stuff about creating and using "up" and use a minus-sign.
I know I'm not the first to ask a question in this arena but I haven't been able to figure out the solution to my particular quandary. Here's a stripped-down example of my problem.
data<-data.frame(Est=c(1:20,1:20),Measured=c(1:5,5:9,1:6,3:6,1:6,3:6,1:4,4,4:8),variable=c(rep("Plot1",20),rep("Plot2",20)))
p<-ggplot(data,aes(y=Est,x=Measured,shape=variable))
p<- p + geom_point(stat="identity") +coord_fixed(xlim=c(0,25),ylim=c(0,25)) + theme_bw()
p #If you plot the figure at this point, the points stand alone in the legend
p<-p+ geom_abline(intercept=0,slope=1,aes(linetype="1:1",color="1:1"),show_guide=TRUE)
p # Once the geom_abline is added, there are lines through the points. :(
p<-p+scale_color_manual(name="Lines",
values=c("1:1"="black"))
p<- p + scale_linetype_manual(name="Lines",
values=c("1:1"=2))
p<-p + scale_shape_manual(values=c(0,20), name = "")
p<- p+xlab(expression(paste("Measured volume(",ducks^{3},"",ha^{-1},")",sep="")))
p<-p+ ylab(expression(paste("Estimated volume (",ducks^{3},"",ha^{-1},")",sep="")))
As you can see, the legend for the points includes slashes (which I think are actually a line), and I would really prefer that the points were alone.
While the example code has only 1 line and linetype, the actual figure I've made includes five different lines of varying colors and linetypes, and thus I need a solution that allows me to include multiple geom_abline calls with color and linetype specified.
And no, I'm not really measuring the volume of anything in ducks, although that would be really entertaining to study...
Override the aesthetic mapping:
p + guides(shape = guide_legend(override.aes = list(linetype = 0)))
I always end up trying to override aesthetics by setting them to NULL, but for some reason that intuition is usually wrong.