Skip to content

Provide a guide that has discrete colors, but labels shifted between them to highlight intervals #2673

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
adrfantini opened this issue May 30, 2018 · 26 comments
Assignees
Labels
feature a feature request or enhancement guides 📏

Comments

@adrfantini
Copy link

This is a feature request.
ggplot2 does not provide a way to use discrete color scales in which the labels are staggered compared to the colors. In my opinion, this kind of color bars is more intuitive than color bars where each color is assigned an explicit interval (usually in a form like [2,5)).

Using only base ggplot, this is the best that I could come up with (SO question link), but it's very hackish, involving shifting the labels and using a dummy empty label at the end:

While this works, it is a bit hackish, the labels often are a bit shifted depending on plot size, and the white spaces between the color boxes do not look very good.

Most other plotting systems allow for this kind of color bar: here are some examples of the kind of color bar that I'd like in ggplot, implemented in other plotting systems:

I have written a small function (SO link with examples) to be able to plot this kind of color bar as a ggplot object, to be added to other objects using patchwork or similar. It's not a great solution however: a built-in guide would be much much better.
Example result:

Note that in this case the last "bin" in the color bar indicates that the last color represents values going up to infinity; this is a useful feature in many cases.

Could something like this be implemented as a built-in guide?

@thomasp85
Copy link
Member

Can you clarify whether merely the guide should be discrete while the scale itself is continuous such as in your lattice example, or if it is intended for discrete scales?

It is possible to write your own guides - ggraph does so - so it is not necessary that this has to be put into ggplot2 proper, but could also reside in an extension package. If @hadley don’t want it in ggplot2 you’re welcome to open an issue in ggforce

@adrfantini
Copy link
Author

Thanks for the reply. I think this should be implemented for discrete data, for continuous data imho it is factually incorrect to have a scale that does not represent all the colors in the plot. Sorry if that example was a bit misleading.

@clauswilke
Copy link
Member

I think the exact desired behavior needs to be thought through more carefully. It seems to me that what is wanted is a function that takes a continuous range, splits it into bins, and then colors the data points within each bin uniformly. To do this properly, you'd need both a new scale and an accompanying guide.

If it's just about using discrete colors for already discrete data, I don't see why the existing guide_legend() is insufficient.

@adrfantini
Copy link
Author

Not knowing the internal design of ggplot I'm not sure I can comment as to what would be more appropriate.
But yes, the idea is to take continuous data and split them into bins, as you said. If taking categorical data as input, then it's unclear to me what the "intervals" (and thus the labels) could be, since not all categorical data is the result of a cut() operation on numerics (with labels=NULL). But if the scale was clever enough to figure this out and act accordingly (throwing an error if the data is factor but not the result of a cut() of numerics), that would of course be even better.

@thomasp85
Copy link
Member

I generally think there is a case to be made for a ScaleBin family of scales - @hadley would you be interested in this in ggplot2 or should it go in ggforce?

@hadley
Copy link
Member

hadley commented May 30, 2018

I think this would be fine in ggplot2

@thomasp85
Copy link
Member

Ok - I’ll look into it after this release

@thomasp85 thomasp85 self-assigned this May 30, 2018
@adrfantini
Copy link
Author

Awesome, thanks!

@m-saenger
Copy link

m-saenger commented Jun 6, 2018

Hi all,

I would very much welcome the feature proposed by Adriano. Atmospheric and earth scientists tend to discretise continuous variables for a more readable plot display. I built a framework to plot meteorological variables on a map. Currently, I use Adriano's workaround to produce an appropriate scale. I added two plots to illustrate how useful such a feature is. The plots are based on ggplot (plus: geom_quiver, geom_polypath and a set of functions to produce (filled/labelled) contours with holes)

eur continent character 0 coord -3 5_21 5_39 5_54 5 vars d _vars u_wind _vars z

ch east surface coord 8 21_9 59_46 81_47 59 vars hs _vars pp _vars pp _300_dpi

Best regards, Matt

@adrfantini
Copy link
Author

@m-saenger those are some awesome plots. Would you be able to share the source code?

@m-saenger
Copy link

@adrfantini Thanks. Will do so. Need to tidy up things first.

@hadley
Copy link
Member

hadley commented Jul 5, 2018

FWIW vega calls these discrete gradient scales.

@clauswilke
Copy link
Member

And the simplest way to implement them is to write a palette function that discretizes the colors and then draw the tick marks at the bin boundaries. This approach can reuse nearly all of the existing functionality of continuous color scales. (That's how I did the discrete value-suppressing scales.)

@clauswilke
Copy link
Member

Example:

library(ggplot2)

discrete_gradient_pal <- function(colours, bins = 5) {
  ramp <- scales::colour_ramp(colours)
  
  function(x) {
    if (length(x) == 0) return(character())
    
    i <- floor(x * bins)
    i <- ifelse(i > bins-1, bins-1, i)
    ramp(i/(bins-1))
  }
}

scale_colour_discrete_gradient <- function(..., colours, bins = 5, na.value = "grey50", guide = "colourbar", aesthetics = "colour", colors)  {
  colours <- if (missing(colours)) 
    colors
  else colours
  continuous_scale(
    aesthetics,
    "discrete_gradient",
    discrete_gradient_pal(colours, bins),
    na.value = na.value,
    guide = guide,
    ...
  )
}

ggplot(mtcars, aes(disp, hp, colour = mpg)) +
  geom_point() +
  scale_colour_discrete_gradient(
    colours = viridisLite::viridis(5, begin = 0.2, end = 0.9),
    limits = c(10, 35),
    breaks = 5*(2:7),
    guide = guide_colourbar(nbin = 100, raster = FALSE, frame.colour = "black", ticks.colour = NA)
  )

Created on 2018-07-05 by the reprex package (v0.2.0).

@hadley
Copy link
Member

hadley commented Jul 6, 2018

Nice! We should definitely try and get that into 3.1.

@thomasp85
Copy link
Member

I think we should aim a bit higher and provide a ScaleBin class at the same level as ScaleContinuous and ScaleDiscrete. There are other areas than colour where binning might make sense, even with positional scales, so I think this should have deep support

@adrfantini
Copy link
Author

@clauswilke , is there any WIP code we can look at / test out?

@clauswilke
Copy link
Member

I thought I provided that in my comment from July 5.
#2673 (comment)

@adrfantini
Copy link
Author

adrfantini commented Sep 17, 2018

Whoopsie. I thought that required some development ggplot version. Great. Is that final, then?

@clauswilke
Copy link
Member

We don't have a pending PR if that's the question. I was just demonstrating that a basic implementation can be done easily with existing functionality.

@thomasp85
Copy link
Member

I still want to do something at a fundamental level for this when I get the time

@adrfantini
Copy link
Author

@thomasp85 have you managed to take a look at this? I cannot code, but I am more than willing to help with testing if that can be of any use.

@adrfantini
Copy link
Author

By the way, the awesome package metR includes a scale_fill_divergent function that does something similar to what's requested here:

@paleolimbot paleolimbot added feature a feature request or enhancement guides 📏 labels Jul 7, 2019
@tjebo
Copy link

tjebo commented Jul 11, 2019

Note that the herein discussed discrete color bar would still not give the requested result from this SO question - which is the varying interval widths represented in the discrete guide. It would be nice if an implementation of a discrete color bar would also allow for that

@tjebo
Copy link

tjebo commented Jun 24, 2020

should this issue now be closed, considering the functionality from scale_..._binned, or scale_..._fermenter with guide_colorbar(even.steps = FALSE)?

For future readers, have also a look at this SO post which shows how to extend the small colorbar ticks to full separators

@adrfantini
Copy link
Author

Yep, I think it can be closed indeed. scale_..._binned works well.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature a feature request or enhancement guides 📏
Projects
None yet
Development

No branches or pull requests

7 participants