## -----------------------------------------------------------------------------
#| include: false
library(ggplot2)


## -----------------------------------------------------------------------------
#| label: ggproto-intro
A <- ggproto("A", NULL,
  x = 1,
  inc = function(self) {
    self$x <- self$x + 1
  }
)
A$x
A$inc()
A$x
A$inc()
A$inc()
A$x


## -----------------------------------------------------------------------------
#| label: chull
StatChull <- ggproto("StatChull", Stat,
  compute_group = function(data, scales) {
    data[chull(data$x, data$y), , drop = FALSE]
  },
  
  required_aes = c("x", "y")
)


## -----------------------------------------------------------------------------
stat_chull <- function(mapping = NULL, data = NULL, geom = "polygon",
                       position = "identity", na.rm = FALSE, show.legend = NA, 
                       inherit.aes = TRUE, ...) {
  layer(
    stat = StatChull, data = data, mapping = mapping, geom = geom, 
    position = position, show.legend = show.legend, inherit.aes = inherit.aes,
    params = list(na.rm = na.rm, ...)
  )
}


## -----------------------------------------------------------------------------
stat_chull <- make_constructor(StatChull, geom = "polygon")
print(stat_chull)


## -----------------------------------------------------------------------------
#| fig.alt: "Scatterplot of engine displacement versus highway miles per
#|  gallon, for 234 cars. The convex hull of all the points is marked by a 
#|  polygon with no fill."
ggplot(mpg, aes(displ, hwy)) + 
  geom_point() + 
  stat_chull(fill = NA, colour = "black")


## -----------------------------------------------------------------------------
#| fig.alt: "Scatterplot of engine displacement versus highway miles per
#|  gallon, for 234 cars. The convex hulls of points, grouped and coloured by 
#|  three types of drive train, are marked by polygons with no fill but the
#|  outline matches the colours of the points."
ggplot(mpg, aes(displ, hwy, colour = drv)) + 
  geom_point() + 
  stat_chull(fill = NA)


## -----------------------------------------------------------------------------
#| fig.alt: "Scatterplot of engine displacement versus highway miles per
#|  gallon, for 234 cars. The points that are part of the convex hull of all 
#|  points are marked with a red outline."
ggplot(mpg, aes(displ, hwy)) + 
  stat_chull(geom = "point", size = 4, colour = "red") +
  geom_point()


## -----------------------------------------------------------------------------
#| fig.alt: "Scatterplot of engine displacement versus highway miles per
#|  gallon, for 234 cars. A straight line with a negative slope passes through
#|  the cloud of points."
StatLm <- ggproto("StatLm", Stat, 
  required_aes = c("x", "y"),
  
  compute_group = function(data, scales) {
    rng <- range(data$x, na.rm = TRUE)
    grid <- data.frame(x = rng)
    
    mod <- lm(y ~ x, data = data)
    grid$y <- predict(mod, newdata = grid)
    
    grid
  }
)

stat_lm <- make_constructor(StatLm, geom = "line")

ggplot(mpg, aes(displ, hwy)) + 
  geom_point() + 
  stat_lm()


## -----------------------------------------------------------------------------
#| fig.alt: "Scatterplot of engine displacement versus highway miles per
#|  gallon, for 234 cars. A wobbly line follows the point cloud over the 
#|  horizontal direction. 20 points are placed on top of the line with constant
#|  horizontal intervals."
StatLm <- ggproto("StatLm", Stat, 
  required_aes = c("x", "y"),
  
  compute_group = function(data, scales, params = list(), n = 100, formula = y ~ x) {
    rng <- range(data$x, na.rm = TRUE)
    grid <- data.frame(x = seq(rng[1], rng[2], length = n))
    
    mod <- lm(formula, data = data)
    grid$y <- predict(mod, newdata = grid)
    
    grid
  }
)

stat_lm <- make_constructor(StatLm, geom = "line")

ggplot(mpg, aes(displ, hwy)) + 
  geom_point() + 
  stat_lm(formula = y ~ poly(x, 10)) + 
  stat_lm(formula = y ~ poly(x, 10), geom = "point", colour = "red", n = 20)


## -----------------------------------------------------------------------------
#' @export
#' @inheritParams ggplot2::stat_identity
#' @param formula The modelling formula passed to \code{lm}. Should only 
#'   involve \code{y} and \code{x}
#' @param n Number of points used for interpolation.
stat_lm <- function(mapping = NULL, data = NULL, geom = "line",
                    position = "identity", na.rm = FALSE, show.legend = NA, 
                    inherit.aes = TRUE, n = 50, formula = y ~ x, 
                    ...) {
  layer(
    stat = StatLm, data = data, mapping = mapping, geom = geom, 
    position = position, show.legend = show.legend, inherit.aes = inherit.aes,
    params = list(n = n, formula = formula, na.rm = na.rm, ...)
  )
}



## -----------------------------------------------------------------------------
#| fig.alt:
#| - "A line plot showing three kernel density estimates of engine displacement,
#|  coloured for three types of drive trains. The lines are a little bit 
#|  wobbly."
#| - "A line plot showing three kernel density estimates of engine displacement,
#|  coloured for three types of drive trains. The lines are fairly smooth."
StatDensityCommon <- ggproto("StatDensityCommon", Stat, 
  required_aes = "x",
  
  setup_params = function(data, params) {
    if (!is.null(params$bandwidth))
      return(params)
    
    xs <- split(data$x, data$group)
    bws <- vapply(xs, bw.nrd0, numeric(1))
    bw <- mean(bws)
    message("Picking bandwidth of ", signif(bw, 3))
    
    params$bandwidth <- bw
    params
  },
  
  compute_group = function(data, scales, bandwidth = 1) {
    d <- density(data$x, bw = bandwidth)
    data.frame(x = d$x, y = d$y)
  }  
)

stat_density_common <- make_constructor(StatDensityCommon, geom = "line")

ggplot(mpg, aes(displ, colour = drv)) + 
  stat_density_common()

ggplot(mpg, aes(displ, colour = drv)) + 
  stat_density_common(bandwidth = 0.5)


## -----------------------------------------------------------------------------
#| fig.alt: "A plot showing the engine displacement versus three types of drive 
#|  trains. Every drive train is represented by a series of densely packed 
#|  points that imitate a horizontal line, and their colour intensity indicates
#|  the kernel density estimate of the displacement."
StatDensityCommon <- ggproto("StatDensity2", Stat, 
  required_aes = "x",
  default_aes = aes(y = after_stat(density)),

  compute_group = function(data, scales, bandwidth = 1) {
    d <- density(data$x, bw = bandwidth)
    data.frame(x = d$x, density = d$y)
  }  
)

ggplot(mpg, aes(displ, drv, colour = after_stat(density))) + 
  stat_density_common(bandwidth = 1, geom = "point")


## -----------------------------------------------------------------------------
#| fig.alt: "An area plot showing the kernel density estimates of 
#|  engine displacement. Three areas are shown that indicate the estimates for
#|  three types of drive trains separately. All areas are floored to the x-axis
#|  and overlap one another."
ggplot(mpg, aes(displ, fill = drv)) + 
  stat_density_common(bandwidth = 1, geom = "area", position = "stack")


## -----------------------------------------------------------------------------
#| fig.alt:
#| - "A stacked area plot showing kernel density estimates of engine displacement.
#|  Three areas are shown that indicate the estimates for three types of drive
#|  trains separately. The areas are stacked on top of one another and show
#|  no overlap."
#| - "A heatmap showing the density of engine displacement for three types of 
#|  drive trains. The heatmap has three rows for the drive trains, but are
#|  continuous in the horizontal direction. The fill intensity of the heatmap
#|  shows the kernel density estimates."   
StatDensityCommon <- ggproto("StatDensityCommon", Stat, 
  required_aes = "x",
  default_aes = aes(y = after_stat(density)),

  setup_params = function(data, params) {
    min <- min(data$x) - 3 * params$bandwidth
    max <- max(data$x) + 3 * params$bandwidth
    
    list(
      bandwidth = params$bandwidth,
      min = min,
      max = max,
      na.rm = params$na.rm
    )
  },
  
  compute_group = function(data, scales, min, max, bandwidth = 1) {
    d <- density(data$x, bw = bandwidth, from = min, to = max)
    data.frame(x = d$x, density = d$y)
  }  
)

ggplot(mpg, aes(displ, fill = drv)) + 
  stat_density_common(bandwidth = 1, geom = "area", position = "stack")
ggplot(mpg, aes(displ, drv, fill = after_stat(density))) + 
  stat_density_common(bandwidth = 1, geom = "raster")


## -----------------------------------------------------------------------------
#| label: GeomSimplePoint
#| fig.alt: "Scatterplot of engine displacement versus highway miles per
#|  gallon, for 234 cars. The points are larger than the default."
GeomSimplePoint <- ggproto("GeomSimplePoint", Geom,
  required_aes = c("x", "y"),
  default_aes = aes(shape = 19, colour = "black"),
  draw_key = draw_key_point,

  draw_panel = function(data, panel_params, coord) {
    coords <- coord$transform(data, panel_params)
    grid::pointsGrob(
      coords$x, coords$y,
      pch = coords$shape,
      gp = grid::gpar(col = coords$colour)
    )
  }
)

# Default stat is `stat_identity()`, no need to specify
geom_simple_point <- make_constructor(GeomSimplePoint)

ggplot(mpg, aes(displ, hwy)) + 
  geom_simple_point()


## -----------------------------------------------------------------------------
#| fig.alt: "Scatterplot of engine displacement versus highway miles per
#|  gallon, for 234 cars. The convex hulls of points, grouped by 7 types of 
#|  cars, are displayed as multiple polygons with no fill, but the outer line is 
#|  coloured by the type."
GeomSimplePolygon <- ggproto("GeomPolygon", Geom,
  required_aes = c("x", "y"),
  
  default_aes = aes(
    colour = NA, fill = "grey20", linewidth = 0.5,
    linetype = 1, alpha = 1
  ),

  draw_key = draw_key_polygon,

  draw_group = function(data, panel_params, coord) {
    n <- nrow(data)
    if (n <= 2) return(grid::nullGrob())

    coords <- coord$transform(data, panel_params)
    # A polygon can only have a single colour, fill, etc, so take from first row
    first_row <- coords[1, , drop = FALSE]

    grid::polygonGrob(
      coords$x, coords$y, 
      default.units = "native",
      gp = grid::gpar(
        col = first_row$colour,
        fill = scales::alpha(first_row$fill, first_row$alpha),
        lwd = first_row$linewidth * .pt,
        lty = first_row$linetype
      )
    )
  }
)

geom_simple_polygon <- make_constructor(GeomSimplePolygon, stat = "chull")

ggplot(mpg, aes(displ, hwy)) + 
  geom_point() + 
  geom_simple_polygon(aes(colour = class), fill = NA)


## -----------------------------------------------------------------------------
#| fig.alt: "Scatterplot of engine displacement versus highway miles per
#|  gallon, for 234 cars. The convex hull of all the points is marked by a 
#|  polygon with no fill."
GeomPolygonHollow <- ggproto("GeomPolygonHollow", GeomPolygon,
  default_aes = aes(colour = "black", fill = NA, linewidth = 0.5, linetype = 1,
    alpha = NA)
  )

geom_chull <- make_constructor(GeomPolygonHollow, stat = "chull")

ggplot(mpg, aes(displ, hwy)) + 
  geom_point() + 
  geom_chull()


## -----------------------------------------------------------------------------
StatBoxplot$setup_params


## -----------------------------------------------------------------------------
StatBoxplot$setup_data


## -----------------------------------------------------------------------------
GeomBoxplot$setup_data


## -----------------------------------------------------------------------------
GeomBoxplot$required_aes


## -----------------------------------------------------------------------------
GeomLine$setup_params


## -----------------------------------------------------------------------------
theme_grey()$legend.key

new_theme <- theme_grey() + theme(legend.key = element_rect(colour = "red"))
new_theme$legend.key


## -----------------------------------------------------------------------------
new_theme <- theme_grey() %+replace% theme(legend.key = element_rect(colour = "red"))
new_theme$legend.key


## -----------------------------------------------------------------------------
#| label: axis-line-ex
#| fig.alt:
#| - "Scatterplot of three observations arranged diagonally. The axis titles 'x' 
#|  and 'y' are coloured in black"
#| - "Scatterplot of three observations arranged diagonally. The axis titles 'x' 
#|  and 'y' are coloured in red"
df <- data.frame(x = 1:3, y = 1:3)
base <- ggplot(df, aes(x, y)) + 
  geom_point() + 
  theme_minimal()

base
base + theme(text = element_text(colour = "red"))


## -----------------------------------------------------------------------------
layout <- function(data, params) {
  data.frame(PANEL = c(1L, 2L), SCALE_X = 1L, SCALE_Y = 1L)
}


## -----------------------------------------------------------------------------
mapping <- function(data, layout, params) {
  if (is.null(data) || nrow(data) == 0) {
    return(cbind(data, PANEL = integer(0)))
  }
  rbind(
    cbind(data, PANEL = 1L),
    cbind(data, PANEL = 2L)
  )
}


## -----------------------------------------------------------------------------
render <- function(panels, layout, x_scales, y_scales, ranges, coord, data,
                   theme, params) {
  # Place panels according to settings
  if (params$horizontal) {
    # Put panels in matrix and convert to a gtable
    panels <- matrix(panels, ncol = 2)
    panel_table <- gtable::gtable_matrix("layout", panels, 
      widths = unit(c(1, 1), "null"), heights = unit(1, "null"), clip = "on")
    # Add spacing according to theme
    panel_spacing <- calc_element("panel.spacing.x", theme)
    panel_table <- gtable::gtable_add_col_space(panel_table, panel_spacing)
  } else {
    panels <- matrix(panels, ncol = 1)
    panel_table <- gtable::gtable_matrix("layout", panels, 
      widths = unit(1, "null"), heights = unit(c(1, 1), "null"), clip = "on")
    panel_spacing <- if (is.null(theme$panel.spacing.y)) {
      theme$panel.spacing
    } else {
      theme$panel.spacing.y
    }
    panel_table <- gtable::gtable_add_row_space(panel_table, panel_spacing)
  }
  # Name panel grobs so they can be found later
  panel_table$layout$name <- paste0("panel-", c(1, 2))
  
  # Construct the axes
  axes <- render_axes(ranges[1], ranges[1], coord, theme, 
    transpose = TRUE)

  # Add axes around each panel
  panel_pos_h <- panel_cols(panel_table)$l
  panel_pos_v <- panel_rows(panel_table)$t
  axis_width_l <- unit(grid::convertWidth(
    grid::grobWidth(axes$y$left[[1]]), "cm", TRUE), "cm")
  axis_width_r <- unit(grid::convertWidth(
    grid::grobWidth(axes$y$right[[1]]), "cm", TRUE), "cm")
  ## We do it reverse so we don't change the position of panels when we add axes
  for (i in rev(panel_pos_h)) {
    panel_table <- gtable::gtable_add_cols(panel_table, axis_width_r, i)
    panel_table <- gtable::gtable_add_grob(panel_table, 
      rep(axes$y$right, length(panel_pos_v)), t = panel_pos_v, l = i + 1, 
      clip = "off")
    panel_table <- gtable::gtable_add_cols(panel_table, axis_width_l, i - 1)
    panel_table <- gtable::gtable_add_grob(panel_table, 
      rep(axes$y$left, length(panel_pos_v)), t = panel_pos_v, l = i, 
      clip = "off")
  }
  ## Recalculate as gtable has changed
  panel_pos_h <- panel_cols(panel_table)$l
  panel_pos_v <- panel_rows(panel_table)$t
  axis_height_t <- unit(grid::convertHeight(
    grid::grobHeight(axes$x$top[[1]]), "cm", TRUE), "cm")
  axis_height_b <- unit(grid::convertHeight(
    grid::grobHeight(axes$x$bottom[[1]]), "cm", TRUE), "cm")
  for (i in rev(panel_pos_v)) {
    panel_table <- gtable::gtable_add_rows(panel_table, axis_height_b, i)
    panel_table <- gtable::gtable_add_grob(panel_table, 
      rep(axes$x$bottom, length(panel_pos_h)), t = i + 1, l = panel_pos_h, 
      clip = "off")
    panel_table <- gtable::gtable_add_rows(panel_table, axis_height_t, i - 1)
    panel_table <- gtable::gtable_add_grob(panel_table, 
      rep(axes$x$top, length(panel_pos_h)), t = i, l = panel_pos_h, 
      clip = "off")
  }
  panel_table
}


## -----------------------------------------------------------------------------
# Constructor: shrink is required to govern whether scales are trained on 
# Stat-transformed data or not.
facet_duplicate <- function(horizontal = TRUE, shrink = TRUE) {
  ggproto(NULL, FacetDuplicate,
    shrink = shrink,
    params = list(
      horizontal = horizontal
    )
  )
}

FacetDuplicate <- ggproto("FacetDuplicate", Facet,
  compute_layout = layout,
  map_data = mapping,
  draw_panels = render
)


## -----------------------------------------------------------------------------
#| fig.alt:
#| - "Scatterplot showing horsepower against miles per gallon for 32 cars."
#| - "Scatterplot with two panels showing horsepower against miles per gallon for 
#|  32 cars. The left and right panels are identical."
p <- ggplot(mtcars, aes(x = hp, y = mpg)) + geom_point()
p
p + facet_duplicate()


## -----------------------------------------------------------------------------
library(scales)

facet_trans <- function(trans, horizontal = TRUE, shrink = TRUE) {
  ggproto(NULL, FacetTrans,
    shrink = shrink,
    params = list(
      trans = scales::as.transform(trans),
      horizontal = horizontal
    )
  )
}

FacetTrans <- ggproto("FacetTrans", Facet,
  # Almost as before but we want different y-scales for each panel
  compute_layout = function(data, params) {
    data.frame(PANEL = c(1L, 2L), SCALE_X = 1L, SCALE_Y = c(1L, 2L))
  },
  # Same as before
  map_data = function(data, layout, params) {
    if (is.null(data) || nrow(data) == 0) {
      return(cbind(data, PANEL = integer(0)))
    }
    rbind(
      cbind(data, PANEL = 1L),
      cbind(data, PANEL = 2L)
    )
  },
  # This is new. We create a new scale with the defined transformation
  init_scales = function(layout, x_scale = NULL, y_scale = NULL, params) {
    scales <- list()
    if (!is.null(x_scale)) {
      scales$x <- lapply(seq_len(max(layout$SCALE_X)), function(i) x_scale$clone())
    }
    if (!is.null(y_scale)) {
      y_scale_orig <- y_scale$clone()
      y_scale_new <- y_scale$clone()
      y_scale_new$trans <- params$trans
      # Make sure that oob values are kept
      y_scale_new$oob <- function(x, ...) x
      scales$y <- list(y_scale_orig, y_scale_new)
    }
    scales
  },
  # We must make sure that the second scale is trained on transformed data
  train_scales = function(x_scales, y_scales, layout, data, params) {
    # Transform data for second panel prior to scale training
    if (!is.null(y_scales)) {
      data <- lapply(data, function(layer_data) {
        match_id <- match(layer_data$PANEL, layout$PANEL)
        y_vars <- intersect(y_scales[[1]]$aesthetics, names(layer_data))
        trans_scale <- layer_data$PANEL == 2L
        for (i in y_vars) {
          layer_data[trans_scale, i] <- y_scales[[2]]$transform(layer_data[trans_scale, i])
        }
        layer_data
      })
    }
    Facet$train_scales(x_scales, y_scales, layout, data, params)
  },
  # this is where we actually modify the data. It cannot be done in $map_data as that function
  # doesn't have access to the scales
  finish_data = function(data, layout, x_scales, y_scales, params) {
    match_id <- match(data$PANEL, layout$PANEL)
    y_vars <- intersect(y_scales[[1]]$aesthetics, names(data))
    trans_scale <- data$PANEL == 2L
    for (i in y_vars) {
      data[trans_scale, i] <- y_scales[[2]]$transform(data[trans_scale, i])
    }
    data
  },
  # A few changes from before to accommodate that axes are now not duplicate of each other
  # We also add a panel strip to annotate the different panels
  draw_panels = function(panels, layout, x_scales, y_scales, ranges, coord,
                         data, theme, params) {
    # Place panels according to settings
    if (params$horizontal) {
      # Put panels in matrix and convert to a gtable
      panels <- matrix(panels, ncol = 2)
      panel_table <- gtable::gtable_matrix("layout", panels, 
        widths = unit(c(1, 1), "null"), heights = unit(1, "null"), clip = "on")
      # Add spacing according to theme
      panel_spacing <- calc_element("panel.spacing.x", theme)
      panel_table <- gtable::gtable_add_col_space(panel_table, panel_spacing)
    } else {
      panels <- matrix(panels, ncol = 1)
      panel_table <- gtable::gtable_matrix("layout", panels, 
        widths = unit(1, "null"), heights = unit(c(1, 1), "null"), clip = "on")
      panel_spacing <- calc_element("panel.spacing.y", theme)
      panel_table <- gtable::gtable_add_row_space(panel_table, panel_spacing)
    }
    # Name panel grobs so they can be found later
    panel_table$layout$name <- paste0("panel-", c(1, 2))
    
    # Construct the axes
    axes <- render_axes(ranges[1], ranges, coord, theme, 
      transpose = TRUE)
  
    # Add axes around each panel
    grobWidths <- function(x) {
      unit(vapply(x, function(x) {
        grid::convertWidth(
          grid::grobWidth(x), "cm", TRUE)
      }, numeric(1)), "cm")
    }
    panel_pos_h <- panel_cols(panel_table)$l
    panel_pos_v <- panel_rows(panel_table)$t
    axis_width_l <- grobWidths(axes$y$left)
    axis_width_r <- grobWidths(axes$y$right)
    ## We do it reverse so we don't change the position of panels when we add axes
    if (params$horizontal) {
      for (i in rev(seq_along(panel_pos_h))) {
        panel_table <- gtable::gtable_add_cols(panel_table, axis_width_r[i], panel_pos_h[i])
        panel_table <- gtable::gtable_add_grob(panel_table,
          axes$y$right[i], t = panel_pos_v, l = panel_pos_h[i] + 1,
          clip = "off")

        panel_table <- gtable::gtable_add_cols(panel_table, axis_width_l[i], panel_pos_h[i] - 1)
        panel_table <- gtable::gtable_add_grob(panel_table,
          axes$y$left[i], t = panel_pos_v, l = panel_pos_h[i],
          clip = "off")
      }
    } else {
        panel_table <- gtable::gtable_add_cols(panel_table, axis_width_r[1], panel_pos_h)
        panel_table <- gtable::gtable_add_grob(panel_table,
          axes$y$right, t = panel_pos_v, l = panel_pos_h + 1,
          clip = "off")
        panel_table <- gtable::gtable_add_cols(panel_table, axis_width_l[1], panel_pos_h - 1)
        panel_table <- gtable::gtable_add_grob(panel_table,
          axes$y$left, t = panel_pos_v, l = panel_pos_h,
          clip = "off")
      }

    ## Recalculate as gtable has changed
    panel_pos_h <- panel_cols(panel_table)$l
    panel_pos_v <- panel_rows(panel_table)$t
    axis_height_t <- unit(grid::convertHeight(
      grid::grobHeight(axes$x$top[[1]]), "cm", TRUE), "cm")
    axis_height_b <- unit(grid::convertHeight(
      grid::grobHeight(axes$x$bottom[[1]]), "cm", TRUE), "cm")
    for (i in rev(panel_pos_v)) {
      panel_table <- gtable::gtable_add_rows(panel_table, axis_height_b, i)
      panel_table <- gtable::gtable_add_grob(panel_table, 
        rep(axes$x$bottom, length(panel_pos_h)), t = i + 1, l = panel_pos_h, 
        clip = "off")
      panel_table <- gtable::gtable_add_rows(panel_table, axis_height_t, i - 1)
      panel_table <- gtable::gtable_add_grob(panel_table, 
        rep(axes$x$top, length(panel_pos_h)), t = i, l = panel_pos_h, 
        clip = "off")
    }
    
    # Add strips
    strips <- render_strips(
      x = data.frame(name = c("Original", paste0("Transformed (", params$trans$name, ")"))),
      labeller = label_value, theme = theme)
    
    panel_pos_h <- panel_cols(panel_table)$l
    panel_pos_v <- panel_rows(panel_table)$t
    strip_height <- unit(grid::convertHeight(
      grid::grobHeight(strips$x$top[[1]]), "cm", TRUE), "cm")
    for (i in rev(seq_along(panel_pos_v))) {
      panel_table <- gtable::gtable_add_rows(panel_table, strip_height, panel_pos_v[i] - 1)
      if (params$horizontal) {
        panel_table <- gtable::gtable_add_grob(panel_table, strips$x$top, 
          t = panel_pos_v[i], l = panel_pos_h, clip = "off")
      } else {
        panel_table <- gtable::gtable_add_grob(panel_table, strips$x$top[i], 
          t = panel_pos_v[i], l = panel_pos_h, clip = "off")
      }
    }
    
    
    panel_table
  }
)


## -----------------------------------------------------------------------------
#| fig.alt: "Scatterplot with two panels showing horsepower against miles per 
#|  gallon for 32 cars. Both panels show the same datapoints. The left panel is 
#|  titled 'original' and the right panel is titled 'transformed (sqrt)'. On the 
#|  right panel, the miles per gallon are displayed on a square root 
#|  transformed scale."
ggplot(mtcars, aes(x = hp, y = mpg)) + geom_point() + facet_trans('sqrt')


## -----------------------------------------------------------------------------
#| fig.alt: "Scatterplot with three-by-three panels showing the weight versus
#|  the price of about 10.000 diamonds in every panel. The panels are titled 1
#|  to 9 and show different points, but are visually similar."

facet_bootstrap <- function(n = 9, prop = 0.2, nrow = NULL, ncol = NULL, 
  scales = "fixed", shrink = TRUE, strip.position = "top") {
  
  facet <- facet_wrap(~.bootstrap, nrow = nrow, ncol = ncol, scales = scales, 
    shrink = shrink, strip.position = strip.position)
  facet$params$n <- n
  facet$params$prop <- prop
  ggproto(NULL, FacetBootstrap,
    shrink = shrink,
    params = facet$params
  )
}

FacetBootstrap <- ggproto("FacetBootstrap", FacetWrap,
  compute_layout = function(data, params) {
    id <- seq_len(params$n)

    dims <- wrap_dims(params$n, params$nrow, params$ncol)
    layout <- data.frame(PANEL = factor(id))

    layout$ROW <- as.integer((id - 1L) %/% dims[2] + 1L)
    layout$COL <- as.integer((id - 1L) %% dims[2] + 1L)

    layout <- layout[order(layout$PANEL), , drop = FALSE]
    rownames(layout) <- NULL

    # Add scale identification
    layout$SCALE_X <- if (params$free$x) id else 1L
    layout$SCALE_Y <- if (params$free$y) id else 1L

    cbind(layout, .bootstrap = id)
  },
  map_data = function(data, layout, params) {
    if (is.null(data) || nrow(data) == 0) {
      return(cbind(data, PANEL = integer(0)))
    }
    n_samples <- round(nrow(data) * params$prop)
    new_data <- lapply(seq_len(params$n), function(i) {
      cbind(data[sample(nrow(data), n_samples), , drop = FALSE], PANEL = i)
    })
    do.call(rbind, new_data)
  }
)

ggplot(diamonds, aes(carat, price)) + 
  geom_point(alpha = 0.1) + 
  facet_bootstrap(n = 9, prop = 0.05)


## -----------------------------------------------------------------------------
p <- ggplot(mpg, aes(displ, hwy, colour = drv)) +
  geom_point() +
  scale_colour_discrete(
    labels = c("4-wheel drive", "front wheel drive", "rear wheel drive")
  )

get_guide_data(p, "colour")


## -----------------------------------------------------------------------------
GuideKey <- ggproto(
  "Guide", GuideAxis,
  
  # Some parameters are required, so it is easiest to copy the base Guide's
  # parameters into our new parameters.
  # We add a new 'key' parameter for our own guide.
  params = c(GuideAxis$params, list(key = NULL)),
  
  # It is important for guides to have a mapped aesthetic with the correct name
  extract_key = function(scale, aesthetic, key, ...) {
    key$aesthetic <- scale$map(key$aesthetic)
    names(key)[names(key) == "aesthetic"] <- aesthetic
    key
  }
)


## -----------------------------------------------------------------------------
guide_key <- function(
  aesthetic, value = aesthetic, label = as.character(aesthetic),
  ...,
  # Standard guide arguments
  theme = NULL, title = waiver(), order = 0, position = waiver()
) {
  
  key <- data.frame(aesthetic, .value = value, .label = label, ...)
  
  new_guide(
    # Arguments passed on to the GuideKey$params field
    key = key, theme = theme, title = title, order = order, position = position,
    # Declare which aesthetics are supported
    available_aes = c("x", "y"),
    # Set the guide class
    super = GuideKey
  )
}


## -----------------------------------------------------------------------------
#| label: key_example
#| fig.alt: >
#|  Scatterplot of engine displacement versus highway miles per 
#|  gallon. The x-axis axis ticks are at 2.5, 3.5, 4.5, 5.5 and 6.5.

ggplot(mpg, aes(displ, hwy)) +
  geom_point() +
  scale_x_continuous(
    guide = guide_key(aesthetic = 2:6 + 0.5)
  )


## -----------------------------------------------------------------------------
#| label: key_ggproto_edit
# Same as before
GuideKey <- ggproto(
  "Guide", GuideAxis,
  params = c(GuideAxis$params, list(key = NULL)),
  extract_key = function(scale, aesthetic, key, ...) {
    key$aesthetic <- scale$map(key$aesthetic)
    names(key)[names(key) == "aesthetic"] <- aesthetic
    key
  },
  
  # New method to draw labels
  build_labels = function(key, elements, params) {
    position <- params$position
    # Downstream code expects a list of labels
    list(element_grob(
      elements$text,
      label = key$.label,
      x = switch(position, left = 1, right = 0, key$x),
      y = switch(position, top = 0, bottom = 1, key$y),
      margin_x = position %in% c("left", "right"),
      margin_y = position %in% c("top", "bottom"),
      colour = key$colour
    ))
  }
)


## -----------------------------------------------------------------------------
#| label: key_example_2
#| fig.alt: >
#|  Scatterplot of engine displacement versus highway miles per 
#|  gallon. There are two x-axes at the bottom and top of the plot. The bottom
#|  has labels alternating in red and gray, and the top has red, green and blue
#|  labels.

ggplot(mpg, aes(displ, hwy)) +
  geom_point() +
  guides(
    x = guide_key(
      aesthetic = 2:6 + 0.5,
      colour = c("red", "grey", "red", "grey", "red")
    ),
    x.sec = guide_key(
      aesthetic = c(2, 4, 6), 
      colour = c("tomato", "limegreen", "dodgerblue")
    )
  )

