Posting this for a more general audience
Consider the following:
ggplot2::ggplot(ggplot2::mpg, ggplot2::aes(class)) + ggplot2::geom_bar()
This yields
However
ggplot2::ggplot(ggplot2::mpg, ggplot2::aes(class)) + ggstance::geom_barh()
produces the (broken!?)
Where am I getting this wrong?
Use the following stat:
stat_counth <- function(mapping = NULL, data = NULL,
geom = "fillbar", position = "stack",
...,
width = NULL,
na.rm = FALSE,
show.legend = NA,
inherit.aes = TRUE) {
params <- list(
na.rm = na.rm,
width = width,
...
)
if (!is.null(params$y)) {
stop("stat_count() must not be used with a y aesthetic.", call. = FALSE)
}
layer(
data = data,
mapping = mapping,
stat = StatCounth,
geom = geom,
position = position,
show.legend = show.legend,
inherit.aes = inherit.aes,
params = params
)
}
#' #rdname ggplot2-ggproto
#' #format NULL
#' #usage NULL
#' #export
#' #include stat-.r
StatCounth <- ggproto("StatCounth", Stat,
required_aes = "y",
default_aes = aes(x = calc(count), weight = 1),
setup_params = function(data, params) {
if (!is.null(data$y)) {
stop("stat_counth() must not be used with a x aesthetic.", call. = FALSE)
}
params
},
compute_group = function(self, data, scales, width = NULL) {
y <- data$y
weight <- data$weight %||% rep(1, length(y))
width <- width %||% (resolution(y) * 0.9)
count <- as.numeric(tapply(weight, y, sum, na.rm = TRUE))
count[is.na(count)] <- 0
data.frame(
count = count,
prop = count / sum(abs(count)),
x = sort(unique(y)),
width = width
)
}
)
Then plot as follows:
ggplot2::ggplot(ggplot2::mpg, ggplot2::aes(y = class)) + ggstance::geom_barh(stat = "counth")
Edit: Actually this stat is also implemented in ggstance, seems to just have been forgotten to make it default.
This works too:
ggplot2::ggplot(ggplot2::mpg, ggplot2::aes(y = class)) + ggstance::geom_barh(stat = ggstance::StatCounth)
If you are just trying to flip the axes you can use the following:
ggplot(mpg, aes(class)) + geom_bar() + coord_flip()
I think this is a problem with ggstance::geom_barh.
Remember, with ggstance, you must supply aesthetics in their natural order. geom_barh will flip the geom only; it will not flip the axes like coord_flip(). For your example to work, you would have to do this:
ggplot(mpg, (aes(y = class) +
geom_barh()
However, this produces an error:
Error: stat_count() must not be used with a y aesthetic.
Related
a function that I'm writing uses ggplot2::geom_text(). However, I need the fontsize to stay scaled to the window size, meaning: If the size of the window is decreased the fontsize has to decrease, too, and vice versa in case of increasing the window size. I did not find a solution online. Therefore, I'm trying to create a ggplot2 GeomTextScaled that works slightly different than GeomText does. Inspired by this post I wrote the preliminary solution below. This is probably incredibly awkward to everybody who is familiar with grid and grobs etc.
# awkward temporary solution
# other fns. resizingGrobText, drawDetails.resizingTextGrob,
# preDrawDetails.resizingGrobText, postDrawDetails.resizingGrobText
#' #export
resizingTextGrob <- function(...){
grid::grob(tg = grid::textGrob(...), cl = "resizingTextGrob")
}
# draw --------------------------------------------------------------------
drawDetails <- grid::drawDetails
#' #exportS3Method
drawDetails.resizingTextGrob <- function(x, recording = TRUE){
grid::grid.draw(x$tg)
}
# pre ---------------------------------------------------------------------
preDrawDetails <- grid::preDrawDetails
#' #exportS3Method
preDrawDetails.resizingTextGrob <- function(x){
# awkward...
size.x <-
base::get(
x = "temp_x.size.scale.bar.text.x_temp",
envir = .GlobalEnv
)
h <- grid::convertHeight(unit(size.x, "snpc"), "mm", valueOnly=TRUE)
fs <- scales::rescale(h, to=c(18, 7), from=c(120, 20))
grid::pushViewport(viewport(gp = grid::gpar(fontsize = fs)))
}
# post --------------------------------------------------------------------
postDrawDetails <- grid::postDrawDetails
#' #exportS3Method
postDrawDetails.resizingTextGrob <- function(x){ grid::popViewport()}
# ggplot2 --------------------------------------------------------------------
#' #title GeomTextScaled
#' #format NULL
#' #usage NULL
#' #export
GeomTextScaled <- ggplot2::ggproto(
`_class` = "GeomTextScaled",
`_inherit` = ggplot2::Geom,
required_aes = c("x", "y", "label"),
default_aes = aes(
colour = "black", size = 3.88, angle = 0, hjust = 0.5,
vjust = 0.5, alpha = NA, family = "", fontface = 1, lineheight = 1.2
),
draw_panel = function(data, panel_params, coord, parse = FALSE,
na.rm = FALSE, check_overlap = FALSE) {
lab <- data$label
data <- coord$transform(data, panel_params)
size.x <- data$size
# awkward...
base::assign(
x = "temp_x.size.scale.bar.text.x_temp",
value = size.x,
envir = .GlobalEnv
)
resizingTextGrob(
label = lab,
x = data$x,
y = data$y,
default.units = "native",
rot = data$angle,
gp = grid::gpar(
col = ggplot2::alpha(data$colour, data$alpha),
fontfamily = data$family,
fontface = data$fontface,
lineheight = data$lineheight
),
check.overlap = check_overlap
)
},
draw_key = ggplot2::draw_key_text
)
#' #export
geom_text_scaled <- function(...,
mapping = ggplot2::aes(),
data = NULL,
stat = "identity",
position = "identity",
na.rm = FALSE,
show.legend = NA,
inherit.aes = TRUE){
ggplot2::layer(
geom = GeomTextScaled,
data = data,
stat = stat,
position = position,
params = c(..., list(na.rm = na.rm)),
show.legend = show.legend,
inherit.aes = inherit.aes,
mapping = mapping
)
}
This is an example that works...
# example -----------------------------------------------------------------
library(ggplot2)
ggplot() +
geom_text_fixed(
data = data.frame(x = 25, y = 25, label = "scaled_to_window"),
mapping = aes(x = x, y= y, label = label),
size = 1
) +
geom_text(
data = data.frame(x = 75, y = 75, label = "stays the same"),
mapping = aes(x = x, y= y, label = label),
size = 5
) +
coord_cartesian(xlim = c(0,100), ylim = c(0,100))
But it only works under two conditions that I'd like to have removed:
I can not "communicate" via the size argument with preDrawDetails() and have to assign a variable to the global environment every time I use it. I'd prefer not to do this but I don't know how to access the method for resizingGrobText().
I'm actually writing a package where I have to export everything. If I load the code below in the global environment everything works. If I only load the package via devtools::load_all() it does not.
How can I solve this?
If there is an easier solution than writing a whole new Geom that I just don't find with google I am happy to use it! I just need the text in this plot to remain scaled to the viewport size.
Any help is appreciated. Thanks so much!
....
I think in this case it is just easier to make a makeContent.my_class method than all the three methods you wrote before. Below, we scale the fontsize such that is a fraction of the width of the panel.
#' #export
resizingTextGrob <- function(...){
grobTree(tg = textGrob(...), cl = "resizingTextGrob")
}
#' #export
#' #method makeContent resizingTextGrob
makeContent.resizingTextGrob <- function(x) {
width <- convertWidth(unit(1, "npc"), "pt", valueOnly = TRUE)
fontsize <- x$children[[1]]$gp$fontsize
fontsize <- if (is.null(fontsize)) 12 else fontsize
x$children[[1]]$gp$fontsize <- fontsize * width / 100
x
}
I have a geom_foo() which will do some transformation of the input data and I have a scale transformation. My problem is that these work not as I would expect together with other geom_*s in terms of scaling.
To illustrate the behavior, consider foo() which will be used in the setup_data method of GeomFoo, defined at the end of the question.
foo <- function(x, y) {
data.frame(
x = x + 2,
y = y + 2
)
}
foo(1, 1)
The transformer is:
foo_trans <- scales::trans_new(
name = "foo",
transform = function(x) x / 5,
inverse = function(x) x * 5
)
Given this input data:
df1 <- data.frame(x = c(1, 2), y = c(1, 2))
Here is a basic plot:
library(ggplot2)
ggplot(df1, aes(x = x, y = y)) +
geom_foo()
When I apply the transformation to the vertical scale, I get this
ggplot(df1, aes(x = x, y = y)) +
geom_foo() +
scale_y_continuous(trans = foo_trans)
What I can say is that the y-axis limits are calculate as 11 = 1 + (2*5) and 12 = 2 + (2*5), where 1 and 2 are df1$y, and (2 * 5) are taken from the setup_data method and from trans_foo.
My real problem is, that I would like add a text layer with labels. These labels and their coordinates come from another dataframe, as below.
df_label <- foo(df1$x, df1$y)
df_label$label <- c("A", "B")
Label and point layers are on same x-y positions without the scale transformation
p <- ggplot(df1, aes(x = x, y = y)) +
geom_foo(color = "red", size = 6) +
geom_text(data = df_label, aes(x, y, label = label))
p
But when I apply the transformation, the coordinates do not match anymore
p +
scale_y_continuous(trans = foo_trans)
How do I get the to layer to match in x-y coordinates after the transformation? Thanks
ggproto object:
GeomFoo <- ggproto("GeomFoo", GeomPoint,
setup_data = function(data, params) {
cols_to_keep <- setdiff(names(data), c("x", "y"))
cbind(
foo(data$x, data$y),
data[, cols_to_keep]
)
}
)
geom constructor:
geom_foo <- function(mapping = NULL, data = NULL, ...,
na.rm = FALSE, show.legend = NA,
inherit.aes = TRUE) {
layer(
data = data,
mapping = mapping,
stat = "identity",
geom = GeomFoo,
position = "identity",
show.legend = show.legend,
inherit.aes = inherit.aes,
params = list(
na.rm = na.rm,
...
)
)
}
Doing data transformations isn't really the task of a geom, but a task of a stat instead. That said, the larger issue is that scale transformations are applied before the GeomFoo$setup_data() method is called. There are two ways one could accomplish this task that I could see.
Apply foo() before scale transformation. I don't think geoms or stats ever have access to the data before scale transformation. A possible place for this is in the ggplot2:::Layer$setup_layer() method. However, this isn't exported, which probably means the devs would like to discourage this even before we make an attempt.
Inverse the scale transformation, apply foo(), and transform again. For this, you need a method with access to the scales. AFAIK, no geom method has this access. However Stat$compute_panel() does have access, so we can use this.
To give an example of (2), I think you could get away with the following:
StatFoo <- ggproto(
"StatFoo", Stat,
compute_panel = function(self, data, scales) {
cols_to_keep <- setdiff(names(data), c("x", "y"))
food <- foo(scales$x$trans$inverse(data$x),
scales$y$trans$inverse(data$y))
cbind(
data.frame(x = scales$x$trans$transform(food$x),
y = scales$y$trans$transform(food$y)),
data[, cols_to_keep]
)
}
)
geom_foo <- function(mapping = NULL, data = NULL, ...,
na.rm = FALSE, show.legend = NA,
inherit.aes = TRUE) {
layer(
data = data,
mapping = mapping,
stat = StatFoo,
geom = GeomPoint,
position = "identity",
show.legend = show.legend,
inherit.aes = inherit.aes,
params = list(
na.rm = na.rm,
...
)
)
}
If someone else has brighter ideas to do this, I'd also like to know!
I can plot a function in ggplot2, like so:
library(ggplot2)
ggplot(data.frame(x=0), aes(x)) + geom_function(fun = sin) + xlim(c(-5,5))
Can I use ggplot2's facetting to make the plot for multiple functions, one in each facet? (for example sin and cos)
Looks like you in fact can facet by the function if you feed each layer its own data with the faceting variable specified:
library(ggplot2) # using ggplot2 3.3.5
ggplot(data.frame(x=0), aes(x)) +
geom_function(fun = sin, data = data.frame(x = -5:5, fun_name = "sin")) +
geom_function(fun = cos, data = data.frame(x = -5:5, fun_name = "cos")) +
facet_wrap(~fun_name)
... fun is not an aesthetic ... you can make it one :)
It's a bit of an overkill, but just a quick demonstration what is possible by modifying the Geoms and Stats. The below is a quick hack and I've referenced with quite a lot of ugly :::, which you wouldn't do if you would properly package this. Also this is clearly not properly tested on many use cases. Few more comments in the code.
This was quite ... fun :)
df <- data.frame(x = 0, fun = c("sin", "cos", "tan", "mean"))
ggplot(df, aes(x)) +
stat_function2(aes(fun = fun)) +
xlim(c(-5,5)) +
facet_wrap(~fun, scales = "free_y")
Modifying Geom and Stat - StatFunction2
StatFunction2 <- ggproto(NULL, StatFunction)
## removing fun from the arguments
StatFunction2$compute_group <- function (data, scales, xlim = NULL, n = 101, args = list())
{
if (is.null(scales$x)) {
## need to change that here a bit
range <- rlang::`%||%`(xlim, c(0, 1))
xseq <- seq(range[1], range[2], length.out = n)
x_trans <- xseq
}
else {
## same same
range <- rlang::`%||%`(xlim, scales$x$dimension())
xseq <- seq(range[1], range[2], length.out = n)
if (scales$x$is_discrete()) {
x_trans <- xseq
}
else {
x_trans <- scales$x$trans$inverse(xseq)
}
}
## get the function, this is the trick :)
fun <- unique(data$fun)
if (plyr::is.formula(fun))
fun <- as_function(fun)
y_out <- do.call(fun, c(list(quote(x_trans)), args))
if (!is.null(scales$y) && !scales$y$is_discrete()) {
y_out <- scales$y$trans$transform(y_out)
}
ggplot2:::new_data_frame(list(x = xseq, y = y_out))
}
## update stat_function - remove fun argument and reference new geom_function2
stat_function2 <- function (mapping = NULL, data = NULL, geom = "function2", position = "identity",
..., fun, xlim = NULL, n = 101, args = list(), na.rm = FALSE,
show.legend = NA, inherit.aes = TRUE)
{
if (is.null(data)) {
### those ::: are just for to make it work here
data <- ggplot2:::ensure_nonempty_data
}
layer(data = data, mapping = mapping, stat = StatFunction2,
geom = geom, position = position, show.legend = show.legend,
## fun needs to be removed here too.
inherit.aes = inherit.aes, params = list(n = n,
args = args, na.rm = na.rm, xlim = xlim, ...))
}
## This is the correct way to create copies (children) of ggproto objects
## see https://stackoverflow.com/a/70637511/7941188
GeomFunction2 <- ggproto(NULL, GeomFunction)
## change the required aesthetics - this removes the warning that aesthetics are not known
GeomFunction2$required_aes <- c("x", "y", "fun")
## update the corresponding geom (two locations in this function definition)
geom_function2 <- function (mapping = NULL, data = NULL, stat = "function2", position = "identity",
..., na.rm = FALSE, show.legend = NA, inherit.aes = TRUE)
{
if (is.null(data)) {
data <- ensure_nonempty_data
}
layer(data = data, mapping = mapping, stat = stat, geom = GeomFunction2,
position = position, show.legend = show.legend, inherit.aes = inherit.aes,
params = list(na.rm = na.rm, ...))
}
Because fun= is not an aesthetic, I think you cannot facet on it. However, you can faux-facet using the patchwork package.
library(ggplot2)
gsin <- ggplot(data.frame(x=0), aes(x)) +
geom_function(fun = sin) +
xlim(c(-5,5)) +
labs(title = "sin()")
gtan <- ggplot(data.frame(x=0), aes(x)) +
geom_function(fun = tan) +
xlim(c(-5,5)) +
labs(title = "tan()")
gsin + gtan
If you prefer the "look" of ggplot2's facets, you can choose this method instead:
gsin <- ggplot(data.frame(x=0, fun="sin"), aes(x)) +
facet_wrap(~fun) +
geom_function(fun = sin) +
xlim(c(-5,5))
gtan <- ggplot(data.frame(x=0, fun="tan"), aes(x)) +
facet_wrap(~fun) +
geom_function(fun = tan) +
xlim(c(-5,5))
gsin + gtan
All of this so far has the effect of facet_*(scales="free_y") (because we fixed xlim(.)). If you want to mimic faceting more closely, you need to control the limits of all facets:
ylims <- c(-1, 1)
gsin <- ggplot(data.frame(x=0, fun="sin"), aes(x)) +
facet_wrap(~fun) +
geom_function(fun = sin) +
xlim(c(-5,5)) +
scale_y_continuous(limits = ylims)
gtan <- ggplot(data.frame(x=0, fun="tan"), aes(x)) +
facet_wrap(~fun) +
geom_function(fun = tan) +
xlim(c(-5,5)) +
scale_y_continuous(name = NULL, guide = NULL, limits = ylims)
gsin + gtan
# Warning: Removed 22 row(s) containing missing values (geom_path).
Technically you are not required here to set the y-limits on all, but ... unless you know with certainty that the limits on unconstrained y-axes will be what you need, it is possible that they could be slightly off. For instance, if you change the initial function (in a quick hack) to be 2*sin(x) but forget to update the remaining facets' y-limits, then your plots will be lying. It's best to set the limits in a single place (ylims <- ...) and reference in all plots.
I'm looking for a more convenient way to get a Q-Q plot in ggplot2 where the quantiles are computed for the data set as a whole. but I can use mappings (colour/shapes) for groups in the data.
library(dplyr)
library(ggplot2)
library(broom) ## for augment()
Make up some data:
set.seed(1001)
N <- 1000
G <- 10
dd <- data_frame(x=runif(N),
f=factor(sample(1:G,size=N,replace=TRUE)),
y=rnorm(N)+2*x+as.numeric(f))
m1 <- lm(y~x,data=dd)
dda <- cbind(augment(m1),f=dd$f)
Basic plot:
ggplot(dda)+stat_qq(aes(sample=.resid))
if I try to add colour, the groups get separated for the quantile computation (which I don't want):
ggplot(dda)+stat_qq(aes(sample=y,colour=f))
If I use stat_qq(aes(sample=y,colour=f,group=1)) ggplot ignores the colour specification and I get the first plot back.
I want a plot where the points are positioned as in the first case, but coloured as in the second case. I have a qqnorm-based manual solution that I can post but am looking for something nicer ...
You could calculate the quantiles yourself and then plot using geom_point:
dda = cbind(dda, setNames(qqnorm(dda$.resid, plot.it=FALSE), c("Theoretical", "Sample")))
ggplot(dda) +
geom_point(aes(x=Theoretical, y=Sample, colour=f))
Ah, I guess I should have read to the end of your question. This is the manual solution you were referring to, right? Although you could just package it as a function:
my_stat_qq = function(data, colour.var) {
data=cbind(data, setNames(qqnorm(data$.resid, plot.it=FALSE), c("Theoretical", "Sample")))
ggplot(data) +
geom_point(aes_string(x="Theoretical", y="Sample", colour=colour.var))
}
my_stat_qq(dda, "f")
Here's a ggproto-based approach that attempts to change StatQq, since the underlying issue here (colour specification gets ignored when group is specified explicitly) is due to how its compute_group function is coded.
Define alternate version of StatQq with modified compute_group (last few lines of code):
StatQq2 <- ggproto("StatQq", Stat,
default_aes = aes(y = after_stat(sample), x = after_stat(theoretical)),
required_aes = c("sample"),
compute_group = function(data, scales, quantiles = NULL,
distribution = stats::qnorm, dparams = list(),
na.rm = FALSE) {
sample <- sort(data$sample)
n <- length(sample)
# Compute theoretical quantiles
if (is.null(quantiles)) {
quantiles <- stats::ppoints(n)
} else if (length(quantiles) != n) {
abort("length of quantiles must match length of data")
}
theoretical <- do.call(distribution, c(list(p = quote(quantiles)), dparams))
res <- ggplot2:::new_data_frame(list(sample = sample,
theoretical = theoretical))
# NEW: append remaining columns from original data
# (e.g. if there were other aesthetic variables),
# instead of returning res directly
data.new <- subset(data[rank(data$sample), ],
select = -c(sample, PANEL, group))
if(ncol(data.new) > 0) res <- cbind(res, data.new)
res
}
)
Define geom_qq2 / stat_qq2 to use modified StatQq2 instead of StatQq for their stat:
geom_qq2 <- function (mapping = NULL, data = NULL, geom = "point",
position = "identity", ..., distribution = stats::qnorm,
dparams = list(), na.rm = FALSE, show.legend = NA,
inherit.aes = TRUE) {
layer(data = data, mapping = mapping, stat = StatQq2, geom = geom,
position = position, show.legend = show.legend, inherit.aes = inherit.aes,
params = list(distribution = distribution, dparams = dparams,
na.rm = na.rm, ...))
}
stat_qq2 <- function (mapping = NULL, data = NULL, geom = "point",
position = "identity", ..., distribution = stats::qnorm,
dparams = list(), na.rm = FALSE, show.legend = NA,
inherit.aes = TRUE) {
layer(data = data, mapping = mapping, stat = StatQq2, geom = geom,
position = position, show.legend = show.legend, inherit.aes = inherit.aes,
params = list(distribution = distribution, dparams = dparams,
na.rm = na.rm, ...))
}
Usage:
cowplot::plot_grid(
ggplot(dda) + stat_qq(aes(sample = .resid)), # original
ggplot(dda) + stat_qq2(aes(sample = .resid, # new
color = f, group = 1))
)
I try to use the new functionality of ggplot2 in R that allows creating our own stat_ functions. I'm creating a simple one to compute and plot an interpolated surface between points arranged on a 2d array.
I would like to create a stat_topo() requiring x, y, and val aesthetics, plotting a simple geom_raster of interpolated val mapped to fill.
library(ggplot2)
library(dplyr)
library(akima)
cpt_grp <- function(data, scales) {
#interpolate data in 2D
itrp <- akima::interp(data$x,data$y,data$val,linear=F,extrap=T)
out <- expand.grid(x=itrp$x, y=itrp$y,KEEP.OUT.ATTRS = F)%>%
mutate(fill=as.vector(itrp$z))
# str(out)
return(out)
}
StatTopo <- ggproto("StatTopo", Stat,
compute_group = cpt_grp,
required_aes = c("x","y","val")
)
stat_topo <- function(mapping = NULL, data = NULL, geom = "raster",
position = "identity", na.rm = FALSE, show.legend = NA,
inherit.aes = TRUE, ...) {
layer(
stat = StatTopo, data = data, mapping = mapping, geom = geom,
position = position, show.legend = show.legend, inherit.aes = inherit.aes,
params = list(na.rm = na.rm, ...)
)
}
set.seed(1)
nchan <- 30
d <- data.frame(val = rnorm(nchan), # some random values to be mapped to fill color
x = 1:nchan*cos(1:nchan), # the x and y position of the points to interpolate
y = 1:nchan*sin(1:nchan))
plot(d$x,d$y)
ggplot(d,aes(x=x,y=y,val=val)) +
stat_topo() +
geom_point()
When I run this, I get the following error:
Error: numerical color values must be >= 0, found -1
I understand that this is because somehow the scale of the fill aesthetic is set to discrete.
If I enter this:
ggplot(d,aes(x=x,y=y,val=val)) +
stat_topo() +
scale_fill_continuous() +
geom_point()
I get what I wanted: the expected raster with a continuous color scale, which I want the stat_ to do by default...
So I guess the question is:
How can I prevent ggplot from setting a discrete scale here, and ideally set a default scale within the call to my new stat_ function.
Apparently, when creating a new variable inside a stat_ function, one needs to explicitly associate it to the aesthetic it will be mapped to with the parameter default_aes = aes(fill = ..fill..) within the ggproto definition.
This is telling ggplot that it is a calculated aesthetic and it will pick a scale based on the data type.
So here we need to define the stat_ as follows:
cpt_grp <- function(data, scales) {
# interpolate data in 2D
itrp <- akima::interp(data$x,data$y,data$val,linear=F,extrap=T)
out <- expand.grid(x=itrp$x, y=itrp$y,KEEP.OUT.ATTRS = F)%>%
mutate(fill=as.vector(itrp$z))
# str(out)
return(out)
}
StatTopo <- ggproto("StatTopo", Stat,
compute_group = cpt_grp,
required_aes = c("x","y","val"),
default_aes = aes(fill = ..fill..)
)
stat_topo <- function(mapping = NULL, data = NULL, geom = "raster",
position = "identity", na.rm = FALSE, show.legend = NA,
inherit.aes = TRUE, ...) {
layer(
stat = StatTopo, data = data, mapping = mapping, geom = geom,
position = position, show.legend = show.legend, inherit.aes = inherit.aes,
params = list(na.rm = na.rm, ...)
)
}
Then the following code:
set.seed(1)
nchan <- 30
d <- data.frame(val = rnorm(nchan),
x = 1:nchan*cos(1:nchan),
y = 1:nchan*sin(1:nchan))
ggplot(d,aes(x=x,y=y,val=val)) +
stat_topo() +
geom_point()
Produces as expected:
Without the need to specify a scale_ manually, but leaving the possibility to adapt the scale easily as usual with e.g. scale_fill_gradient2(low = 'blue',mid='white',high='red')
I got this answer here: https://github.com/hadley/ggplot2/issues/1481
Okay, slept on it, and had an idea, and I think this might do what you want. In your stat_topo layer function instead of the ggproto I returned a list with it as the first element and then added to that list another ggproto with a call to scale_fill_continuous().
library(ggplot2)
library(dplyr)
library(akima)
cpt_grp <- function(data, scales) {
#interpolate data in 2D
itrp <- akima::interp(data$x,data$y,data$val,linear=F,extrap=T)
out <- expand.grid(x=itrp$x, y=itrp$y,KEEP.OUT.ATTRS = F)%>%
mutate(fill=as.vector(itrp$z))
return(out)
}
StatTopo <- ggproto("StatTopo", Stat,
compute_group = cpt_grp,
required_aes = c("x","y","val")
)
stat_topo <- function(mapping = NULL, data = NULL, geom = "raster",
position = "identity", na.rm = FALSE, show.legend = NA,
inherit.aes = TRUE, ...) {
list(
layer(
stat = StatTopo, data = data, mapping = mapping, geom = geom,
position = position, show.legend = show.legend, inherit.aes = inherit.aes,
params = list(na.rm = na.rm )
),
scale_fill_continuous()
)
}
set.seed(1)
nchan <- 30
d <- data.frame(val = rnorm(nchan), # some random values to be mapped to fill color
x = 1:nchan*cos(1:nchan), # the x and y position of interp points
y = 1:nchan*sin(1:nchan))
ggplot(d,aes(x=x,y=y,val=val)) +
stat_topo() +
geom_point()
yielding the same picture as above.