My goal is to write a custom geom_ method that calculates and plots, e.g., confidence intervals and these should be plotted either as polygons or as lines. The question now is, where to check which "style" should be plotted?
So far I have tried out three different approaches,
- (i) write two different
geom_/stat_for line and polygon style plots, - (ii) write a single
geom_/stat_which uses a customGeomMethod, - (iii) write a single
geom_/stat_which uses eitherGeomPolygonorGeomLine.
In my opinion, to sum up
- (i) is more or less straightforward but only bypasses the problem,
- (ii) works when you use either
GeomPath$draw_panel()orGeomPolygon$draw_panel()depending on an extra parameterstyle. But here I can't work it out to setdefault_aesdepending also on the extra argumentstyle. Compare also the answer here. - (iii) works when calling
geom_but fails for callingstat_as the name matching within ggplot2 fails. See minimal example below.
Setting up the methods of approach (iii):
geom_my_confint <- function(mapping = NULL, data = NULL, stat = "my_confint",
position = "identity", na.rm = FALSE,
show.legend = NA, inherit.aes = TRUE,
style = c("polygon", "line"), ...) {
style <- match.arg(style)
ggplot2::layer(
geom = if (style == "line") GeomPath else GeomPolygon,
mapping = mapping,
data = data,
stat = stat,
position = position,
show.legend = show.legend,
inherit.aes = inherit.aes,
params = list(
na.rm = na.rm,
style = style,
...
)
)
}
stat_my_confint <- function(mapping = NULL, data = NULL, geom = "my_confint",
position = "identity", na.rm = FALSE,
show.legend = NA, inherit.aes = TRUE,
style = c("polygon", "line"), ...) {
style <- match.arg(style)
ggplot2::layer(
geom = geom,
stat = StatMyConfint,
data = data,
mapping = mapping,
position = position,
show.legend = show.legend,
inherit.aes = inherit.aes,
params = list(
na.rm = na.rm,
style = style,
...
)
)
}
StatMyConfint <- ggplot2::ggproto("StatMyConfint", ggplot2::Stat,
compute_group = function(data, scales, style) {
if (style == "polygon") {
nd <- data.frame(
x = c(data$x, rev(data$x)),
y = c(data$y - 1, rev(data$y) + 1)
)
nd
} else {
nd <- data.frame(
x = rep(data$x, 2),
y = c(data$y - 1, data$y + 1),
group = c(rep(1, 5), rep(2, 5))
)
nd
}
},
required_aes = c("x", "y")
)
Trying out the methods of approach (iii):
library("ggplot2")
d <- data.frame(
x = seq(1, 5),
y = seq(1, 5)
)
ggplot(d, aes(x = x, y = y)) + geom_line() + geom_my_confint(style = "polygon", alpha = 0.2)
ggplot(d, aes(x = x, y = y)) + geom_line() + geom_my_confint(style = "line", linetype = 2)
This works well so far. However when calling the stat_ there is an error in ggplot2:::check_subclass because there is no GeomMyConfint method.
ggplot(d, aes(x = x, y = y)) + geom_line() + stat_my_confint()
# Error: Can't find `geom` called 'my_confint'
Any solutions or suggestions for alternative approaches?

The following isn't very elegant but seems to work. Let's define the following constructor, wherein the
geomis set toGeomMyConfint, which we'll define further down.Below is the paired ggproto class. I've amended the
use_defaultsmethod to replace a defaulted colour by some text. Then later, thedraw_panel()method chooses the actual default to replace the text we've inserted earlier, depending on thestyleargument.Then then works with the rest of the functions from your example.
A more elegant method might be to use the
vctrspackage to define a custom S3 class for defaulted values that is easy to recognise, but I haven't seen people trying to useaes(colour = I("default_colour"))before, so you're probably safe aside from this one single edge case.