I am trying to understand the connection between scale_fill_brewer
and scale_fill_manual
of package ggplot2
.
First, generate a ggplot with filled colors:
library(ggplot2)
p <- ggplot(data = mtcars, aes(x = mpg, y = wt,
group = cyl, fill = factor(cyl))) +
geom_area(position = 'stack')
# apply ready-made palette with scale_fill_brewer from ggplot2
p + scale_fill_brewer(palette = "Blues")
Now, replicate with scale_fill_manual
library(RColorBrewer)
p + scale_fill_manual(values = brewer.pal(3, "Blues"))
where 3 is the number of fill-colors in the data. For convenience, I have used the brewer.pal
function of package RColorBrewer
.
As far as I understand, the convenience of scale_fill_brewer
is that it automatically computes the number of unique levels in the data (3 in this example). Here is my attempt at replicating:
p + scale_fill_manual(values = brewer.pal(length(levels(factor(mtcars$cyl))), "Blues"))
My question is: how does scale_fill_brewer
compute the number of levels in the data?
I'm interested in understanding what else fill_color_brewer
might be doing under the hood. Might I run into any difficulty if I replace the more user friendly fill_color_brewer
with a more contorted implementation of scale_fill_manual
like the one above.
Perusing the source code:
scale_fill_brewer
function (..., type = "seq", palette = 1) {
discrete_scale("fill", "brewer", brewer_pal(type, palette), ...)
}
I couldn't see through this how scale_fill_brewer
computes the number of unique levels in the data. Perhaps hidden in the ...
?
Edit: Where does the function scale_fill_brewer
receive instructions to compute the number of levels in the data? Is it in "seq"
or in ...
or elsewhere?
The discrete_scale
function is intricate and I'm lost. Here are its arguments:
discrete_scale <- function(aesthetics, scale_name, palette, name = NULL,
breaks = waiver(), labels = waiver(), legend = NULL, limits = NULL,
expand = waiver(), na.value = NA, drop = TRUE, guide="legend") {
Does any of this compute the number of levels?
The easiest way is to trace it is to think in terms of (1) setting up the plot data structure, and (2) resolving the aesthetics. It uses S3 so the branching is implicit
The setup call sequence
[scale-brewer.R] scale_fill_brewer(type="seq", palette="Blues")
[scale-.R] discrete_scale(...)
- return an object representing the scale
structure(list(
call = match.call(),
aesthetics = aesthetics,
scale_name = scale_name,
palette = palette,
range = DiscreteRange$new(), ## this is scales::DiscreteRange
...), , class = c(scale_name, "discrete", "scale"))
The resolve call sequence
ggplot_build(plot)
- for non-position scales, apply scales_train_df # Train and map non-position scales
npscales <- scales$non_position_scales() ## scales is plot$scales, S4 type Scales
if (npscales$n() > 0) {
lapply(data, scales_train_df, scales = npscales)
data <- lapply(data, scales_map_df, scales = npscales)
}
[scales-.r] scales_train_df(...)
- iterate again over scales$scales (list)
[scale-.r] scale_train_df(...)
- iterate again
[scale-.r] scale_train(...)
- S3 generic function
[scale-.r] scale_train.discrete(...)
- almost there...
scale$range$train(x, drop = scale$drop)
(scales::DiscreteRange$new())$train
, which overwrites scale$range! range <<- train_discrete(x, range, drop)
scales:::train_discrete(...)
- again, almost there...
scales:::discrete_range(...)
- still not there..
scales:::clevels(...)
- there it is!
As of this point, scale$range
has been overwritten by the levels of the factor. Unwinding the call stack to #1, we now call scales_map_df
ggplot_build(plot)
- for non-position scales, apply scales_train_df # Train and map non-position scales
npscales <- scales$non_position_scales() ## scales is plot$scales, S4 type Scales
if (npscales$n() > 0) {
lapply(data, scales_train_df, scales = npscales)
data <- lapply(data, scales_map_df, scales = npscales)
}
[scales-.r] scale_maps_df(...)
- iterate
[scale-.r] scale_map_df(...)
- iterate
[scale-.r] scale_map.discrete
- fill up the palette (non-position scale!)
scale_map.discrete <- function(scale, x, limits = scale_limits(scale)) { n <- sum(!is.na(limits)) pal <- scale$palette(n) ... }
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.