简体   繁体   中英

recreate scale_fill_brewer with scale_fill_manual

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

  1. [scale-brewer.R] scale_fill_brewer(type="seq", palette="Blues")

  2. [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

  1. [plot-build.R] 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)
    }
  1. [scales-.r] scales_train_df(...) - iterate again over scales$scales (list)

  2. [scale-.r] scale_train_df(...) - iterate again

  3. [scale-.r] scale_train(...) - S3 generic function

  4. [scale-.r] scale_train.discrete(...) - almost there...

    scale$range$train(x, drop = scale$drop)
  1. but scale$range is a DiscreteRange instance, so it calls (scales::DiscreteRange$new())$train , which overwrites scale$range!
    range <<- train_discrete(x, range, drop)
  1. scales:::train_discrete(...) - again, almost there...

  2. scales:::discrete_range(...) - still not there..

  3. 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

  1. [plot-build.R] 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)
    }
  1. [scales-.r] scale_maps_df(...) - iterate

  2. [scale-.r] scale_map_df(...) - iterate

  3. [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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM