Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ggplot2: How to get a Geom Class to affect ScaleContinuous Class

I have begun to extend ggplot2 and I'm still getting a feel for how the package calls all of its internal functions.

I have a new ggproto class that extends one of the current Geom environments. The current model of the class will plot something along the discrete x axis, ideally touching the x axis ticks. This model works well when the y axis is already on a discrete scale, because the default expansion values only adds .6 padding. However on a continuous y scale, the default padding can make these new plotted objects seem far. In summary, how can I make my Geom class override the default expansion without just adding either scale_y_continuous(expand = c(0,0,.05,0) or scale_y_discrete(expand = c(0, 0, .6,0) to my layer function...

Consider the following reproducible example

library(dplyr)
library(tidyr)
library(ggplot2)
library(stringr)
mtcars0 <- as_tibble(mtcars, rownames = "CarNames") %>%
  gather(key = "qualities", value = "value", -CarNames) %>%
  group_by(qualities) %>%
  mutate(scaledValue = scale(value)) %>%
  ungroup() %>%
  mutate(carCase = case_when(str_detect(CarNames, "^[A-M]") ~ "A-M",
                             TRUE ~ "N-Z"))
"%||%" <- function(a, b) {
  if (!is.null(a)) a else b
}

MyText <- ggproto("GeomMyText",
                  GeomText,
                  extra_params = c("na.rm","padDist"),
                  setup_data = function(data, params){
                    #find bottom of plot with sufficent space
                    minpadding <- params$padDist %||% diff(range(data$y))*.05
                    data$y <- min(data$y) - minpadding
                    data 
                  })

geom_mytext <- function (mapping = NULL, data = NULL, stat = "identity", position = "identity", 
                         ..., parse = FALSE, nudge_x = 0, nudge_y = 0, check_overlap = FALSE, 
                         na.rm = FALSE, show.legend = NA, inherit.aes = TRUE, padDist = NULL) 
{ 
  if (!missing(nudge_x) || !missing(nudge_y)) {
    if (!missing(position)) {
      abort("You must specify either `position` or `nudge_x`/`nudge_y`.")
    }
    position <- position_nudge(nudge_x, nudge_y)
  }
  layer(data = data, mapping = mapping, stat = stat, geom = MyText, 
        position = position, show.legend = show.legend, inherit.aes = inherit.aes, 
        params = list(parse = parse, check_overlap = check_overlap, 
                      na.rm = na.rm, padDist = padDist, ...))
}


result <- ggplot(mtcars0, aes(x = CarNames, value)) +
  geom_point() +
  geom_mytext(aes(label = carCase)) +
  theme(axis.text.x = element_text(angle=90))
#Default
result 
#Desired Result without having to call scale_y_continuous
result + scale_y_continuous(expand = c(0,0,0.05,0))

I'm assuming I need to extend the ScaleContinuous environment but I have no idea how to connect the MyText environment with it.

Any suggestions?

---- EDIT ----

Thanks for the quick replies! A few things -

  1. I am aware there is over plotting and clipping of labels, this isn't my actual Geom environment, just something I could put together that demonstrates my question.
  2. As I was afraid, it seems that everyone so far is providing the solution that was raised for this question. While supplying my own scale is less than ideal - because I have to put logic in to discern if they associated y axis is discrete/continuous, when ggplot2 already knows this, I figured that there might be a trick I was missing. For now I will continue development with the suggestions given. Thanks!

---- EDIT 2 ----

I took another look at the solution given here. The exact parameters I need to modify is

panel_params$y$continuous_range[1] <- panel_params$y$limits[1]

And I need to do this somewhere in draw_panel. It seems like the associated scales are contained there and the coord$transform(data, panel_params) is responsible for including the padding on the rescaled axis depending on what is set for panel_params$y$limits and panel_params$y$continuous_range.

Thanks again to everyone who contributed!

like image 882
Justin Landis Avatar asked Dec 02 '25 04:12

Justin Landis


1 Answers

Nice question - thanks for posting.

This is easier than you'd think. You simply bundle the desired scale object with the layer object that is returned from your geom_mytext function by concatenating them with c. In this example I have also bundled a coord_cartesian object so that I could turn clipping off to show the text properly. I have also changed the default check.overlap to TRUE because your labels are being overplotted.

Note I haven't changed your ggplot call at all

geom_mytext <- function (mapping = NULL, data = NULL, stat = "identity", position = "identity", 
                         ..., parse = FALSE, nudge_x = 0, nudge_y = 0, check_overlap = FALSE, 
                         na.rm = FALSE, show.legend = NA, inherit.aes = TRUE, padDist = NULL) 
{ 
  if (!missing(nudge_x) || !missing(nudge_y)) {
    if (!missing(position)) {
      abort("You must specify either `position` or `nudge_x`/`nudge_y`.")
    }
    position <- position_nudge(nudge_x, nudge_y)
  }
  c(layer(data = data, mapping = mapping, stat = stat, geom = MyText, 
        position = position, show.legend = show.legend, inherit.aes = inherit.aes, 
        params = list(parse = parse, check_overlap = check_overlap, 
                      na.rm = na.rm, padDist = padDist, ...)), 
    scale_y_continuous(expand = c(0,0,0.05,0)),
    coord_cartesian(clip = "off"))
}


result <- ggplot(mtcars0, aes(x = CarNames, value)) +
  geom_point() +
  geom_mytext(label = "test") +
  theme(axis.text.x = element_text(angle=90))

result 

enter image description here

Now come the caveats. Because you are supplying your own scale_y_continuous object, users won't like that ggplot complains when they try to add their own y scale. You will also need some logic to choose between adding a continuous or discrete y scale. I don't think these are insurmountable problems though.

like image 199
Allan Cameron Avatar answered Dec 04 '25 21:12

Allan Cameron