简体   繁体   English

如何使用 geom_contour_filled 制作离散渐变色条?

[英]How to make discrete gradient color bar with geom_contour_filled?

I plot a map based on a piece of code like this:我根据这样的一段代码绘制了一张地图:

ggplot(faithfuld, aes(y=eruptions, x=waiting, z=100*density)) +
geom_contour_filled(breaks = c(-Inf,-2., -1.5, -1., -0.5, 0, 0.5, 1, 1.5, 2, 3, 4, 5, 7, 9, 11,Inf))+
theme(plot.title = element_text(size = 10,hjust = 0.5))

This is my plot currently looks like:这是我的情节目前看起来像: 在此处输入图像描述 But my boss asks me to make the legend like this:但是我的老板让我把这个传说变成这样: 在此处输入图像描述 or like this:或像这样: 在此处输入图像描述 Arguments from this link ( https://ggplot2.tidyverse.org/reference/theme.html ) just provide minor changes for the legend.此链接 ( https://ggplot2.tidyverse.org/reference/theme.html ) 中的参数只是为图例提供了微小的更改。 And I can't find any arguments that can achieve this, is it doable with ggplot?而且我找不到任何可以实现这一目标的论点,ggplot 是否可行? or I have to use other plotting package?还是我必须使用其他绘图包?

Create discrete color bar with varying interval widths and no spacing between legend levels This question (answer No. 4) provides a method that can create a color bar like my boss required, however, I'm using geom_contour_filled(breaks = c(-Inf,-2., -1.5, -1., -0.5, 0, 0.5, 1, 1.5, 2, 3, 4, 5, 7, 9, 11,Inf)) this argument so the legend always appears with a lot of text: 创建具有不同间隔宽度且图例级别之间没有间距的离散颜色条此问题(答案 4)提供了一种可以像我的老板所需的那样创建颜色条的方法,但是,我正在使用geom_contour_filled(breaks = c(-Inf,-2., -1.5, -1., -0.5, 0, 0.5, 1, 1.5, 2, 3, 4, 5, 7, 9, 11,Inf))这个论点所以传说总是出现很多文字: 在此处输入图像描述 Are there any solutions?有什么解决办法吗?

edit编辑

I recommend not to use this answer - my second answer in this thread is much more appropriate, but I have answered this here in ignorance of the new functions.我建议不要使用这个答案——我在这个线程中的第二个答案更合适,但我在这里回答了这个问题,不知道新功能。 I still think it may be useful in very specific situations, so I leave it for future readers.我仍然认为它在非常特定的情况下可能很有用,所以我把它留给未来的读者。 The functions are taken and modified taken from Claus Wilke's comment in this github issue .这些函数取自Claus Wilke 在此 github 问题中的评论并对其进行了修改。

I'd also like to again recommend to consider user AF7's function to create a fake legend, because you have much more freedom how to style your legend.我还想再次建议考虑使用 AF7 用户创建假图例的功能,因为您可以更自由地设置图例的样式。

geom_contour_filled discretizes your dimension of interest and then the inherently continuous scale_fill_discrete_gradient fails. geom_contour_filled离散化您感兴趣的维度,然后固有的连续scale_fill_discrete_gradient失败。 It seems that metR::geom_contour_fill does not produce discrete data, but keeps it continous...似乎metR::geom_contour_fill不会产生离散数据,而是保持连续......

In order to make this solution work, you need to cut your variable to bins and then use the factor levels for setting breaks and limits.为了使该解决方案发挥作用,您需要将变量切割成箱,然后使用因子水平来设置中断和限制。 It's a bit hacky...这有点hacky...

library(RColorBrewer)
library(metR)
library(ggplot2)

mybreaks <- c(seq(-2,2,0.5), 3:5, seq(7,11,2))
mycols <- rev(colorRampPalette(brewer.pal(11, "Spectral"))(length(mybreaks)-1))
faithfuld$cut_dens <- cut(100*faithfuld$density, mybreaks)

ggplot(faithfuld, aes(eruptions, waiting)) +
  geom_contour_fill(aes(z = as.integer(cut_dens))) +
  scale_fill_discrete_gradient(
    colours = mycols,
    breaks = seq(1, 15, 1), # breaks and limits based on factor levels! 
    limits = c(1,15),
    bins = length(mybreaks)-1,
    labels = mybreaks,
    guide = guide_colourbar(frame.colour = "black", 
                            ticks.colour = "black", # you can also remove the ticks with NA
                            barwidth=20)
  ) +
  theme(legend.position = "bottom")

functions功能

## very mildly modified from Claus Wilke
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_fill_discrete_gradient <- 
  function(..., colours, bins = 5, 
           na.value = "grey50", 
           guide = "colourbar", 
           aesthetics = "fill", colors)  {
    colours <- if (missing(colours)) 
      colors
    else colours
    continuous_scale(
      aesthetics,
      "discrete_gradient",
      discrete_gradient_pal(colours, bins),
      na.value = na.value,
      guide = guide,
      ...
    )
  } 

I believe this is different enough to my previous answer to justify a second one.我相信这与我之前的答案足以证明第二个答案是正确的。 I answered the latter in complete denial of the new scale functions that came with ggplot2 3.3.0, and now here we go, they make it much easier.我回答后者完全否认了 ggplot2 3.3.0 附带的新比例函数,现在我们开始了,它们让它变得更容易。 I'd still keep the other solution because it might help for ... well very specific requirements.我仍然会保留其他解决方案,因为它可能有助于......非常具体的要求。

We still need to use metR because the problem with the continuous/discrete contour persists, and metR::geom_contour_fill handles this well.我们仍然需要使用metR,因为连续/离散轮廓的问题仍然存在,而metR::geom_contour_fill 可以很好地处理这个问题。

I am modifying the scale_fill_fermenter function which is the good function to use here because it works with a binned scale.我正在修改scale_fill_fermenter函数,这是在这里使用的好函数,因为它适用于分箱比例。 I have slightly enhanced the underlying brewer_pal function, so that it gives more than the original brewer colors, if n > max(palette_colors) .如果n > max(palette_colors) ,我稍微增强了底层的brewer_pal函数,以便它提供比原始 brewer 颜色更多的功能。

update You should use guide_colorsteps to change the colorbar.更新您应该使用guide_colorsteps来更改颜色条。 And see this related discussion regarding the longer breaks at start and end of the bar.查看有关在小节开始和结束时更长的休息时间的相关讨论

library(ggplot2)
library(metR)

mybreaks <- c(seq(-2,2,0.5), 3:5, seq(7,11,2))

ggplot(faithfuld, aes(eruptions, waiting)) +
  metR::geom_contour_fill(aes(z = 100*density)) +
  scale_fill_craftfermenter(
    breaks = mybreaks, 
    palette = "Spectral", 
    limits = c(-2,11),
    guide = guide_colorsteps(
      frame.colour = "black", 
      ticks.colour = "black", # you can also remove the ticks with NA
      barwidth=20)
  ) +
  theme(legend.position = "bottom")
#> Warning: 14 colours used, but Spectral has only 11 - New palette created based
#> on all colors of Spectral

## with uneven steps, better representing the scale 
ggplot(faithfuld, aes(eruptions, waiting)) +
  metR::geom_contour_fill(aes(z = 100*density)) +
  scale_fill_craftfermenter(
    breaks = mybreaks, 
    palette = "Spectral", 
    limits = c(-2,11),
    guide = guide_colorsteps(
      even.steps = FALSE,
      frame.colour = "black", 
      ticks.colour = "black", # you can also remove the ticks with NA
      barwidth=20, )
  ) +
  theme(legend.position = "bottom")
#> Warning: 14 colours used, but Spectral has only 11 - New palette created based
#> on all colors of Spectral

Function modifications功能修改

craftbrewer_pal <- function (type = "seq", palette = 1, direction = 1) 
{
  pal <- scales:::pal_name(palette, type)
  force(direction)
  function(n) {
    n_max_palette <- RColorBrewer:::maxcolors[names(RColorBrewer:::maxcolors) == palette]
    
    if (n < 3) {
      pal <- suppressWarnings(RColorBrewer::brewer.pal(n, pal))
    } else if (n > n_max_palette){
      rlang::warn(paste(n, "colours used, but", palette, "has only",
                    n_max_palette, "- New palette created based on all colors of", 
                    palette))
      n_palette <- RColorBrewer::brewer.pal(n_max_palette, palette)
      colfunc <- grDevices::colorRampPalette(n_palette)
      pal <- colfunc(n)
    }
    else {
      pal <- RColorBrewer::brewer.pal(n, pal)
    }
    pal <- pal[seq_len(n)]
    if (direction == -1) {
      pal <- rev(pal)
    }
    pal
  }
}

scale_fill_craftfermenter <- function(..., type = "seq", palette = 1, direction = -1, na.value = "grey50", guide = "coloursteps", aesthetics = "fill") {
  type <- match.arg(type, c("seq", "div", "qual"))
  if (type == "qual") {
    warn("Using a discrete colour palette in a binned scale.\n  Consider using type = \"seq\" or type = \"div\" instead")
  }
  binned_scale(aesthetics, "fermenter", ggplot2:::binned_pal(craftbrewer_pal(type, palette, direction)), na.value = na.value, guide = guide, ...)
}
["

Another option is to make use of guide_bins<\/code> .<\/i>另一种选择是使用guide_bins<\/code> 。<\/b><\/p>

To get nice labels you can probably make use of the labels<\/code> argument to cut<\/code> as I do in my approach.<\/i>要获得漂亮的标签,您可能可以像我在方法中那样使用labels<\/code>参数进行cut<\/code> 。<\/b><\/p>

Unfortunately I could not figure out a way to remove the spacing between the legend keys or to have a black frame around the keys.<\/i>不幸的是,我想不出一种方法来消除图例键之间的间距或在键周围设置黑框。<\/b><\/p>

Also, without a glance at your data and color palette I'm not sure whether this approach could be easily adpated to your case.<\/i>此外,如果不看一下您的数据和调色板,我不确定这种方法是否可以轻松地应用于您的案例。<\/b><\/p>

set.seed(42)

d <- data.frame(
  x = runif(1000, -20, 20)
)

d$y <- cut(d$x, breaks = c(-Inf, seq(-2, 11, 1), Inf), labels = c(seq(-2, 11, 1), ""))

library(ggplot2)

ggplot(d, aes(y, fill = as.numeric(y))) +
  geom_bar() +
  scale_fill_viridis_b(name = "\u00B0C", limits = c(-2, 11), breaks = seq(-2, 11, 1),
                       guide = guide_bins(axis = FALSE, title.position = "right",
                                          axis.colour = "black",
                                          keywidth = unit(1, "cm"), 
                                          keyheight = unit(1, "cm"))) + 
  theme(legend.position = "bottom")

This is an old answer, but the metR package might solve this issue with the new discretised scale (disclaimer, I'm the author :) ).这是一个旧答案,但metR包可能会用新的离散量表解决这个问题(免责声明,我是作者 :))。 Use ggplot2::geom_contour_filled() (or metR::geom_contour_fill(aes(fill = stat(level))) ) and then use metR::scale_fill_discretised()使用ggplot2::geom_contour_filled() (或metR::geom_contour_fill(aes(fill = stat(level))) )然后使用metR::scale_fill_discretised()

library(ggplot2)

breaks <-  c(-Inf,-2., -1.5, -1., -0.5, 0, 0.5, 1, 1.5, 2, 3, 4, 5, 7, 9, 11,Inf)
ggplot(faithfuld, aes(y=eruptions, x=waiting, z=100*density)) +
  geom_contour_filled(breaks = breaks) +
  metR::scale_fill_discretised()

This will treat discretised values (such as the computed level variable from geom_contour_filled() ) as if they were continuous.这会将离散值(例如来自geom_contour_filled()的计算level变量)视为连续值。 Notice that now the colour scale correctly reflects the unequal spacing of the breaks.请注意,现在色标正确地反映了中断的不等间距。 That is, not only the breaks are unequally spaced in the guide, but also the colours are unequally spaced in the colour scale.也就是说,不仅中断在指南中的间距不均匀,而且颜色在色标中的间距也不均匀。

If you want to use a colour palette similar to the ones on your screenshot, you can use ggplot2::scale_fill_gradientn() but convert it to a discretised scale with the super argument.如果您想使用类似于屏幕截图中的调色板,您可以使用ggplot2::scale_fill_gradientn()但使用super参数将其转换为离散比例。

ggplot(faithfuld, aes(y=eruptions, x=waiting, z=100*density)) +
  geom_contour_filled(breaks = breaks) +
  scale_fill_gradientn(colours = c("#0A2864", "#CCD9FF", "#FFF9CF", "#FEBF00", "#E6281E", "#6C0000"),
    super = metR::ScaleDiscretised)

Or any other continuous scale.或任何其他连续规模。

ggplot(faithfuld, aes(y=eruptions, x=waiting, z=100*density)) +
  geom_contour_filled(breaks = breaks) +
  scale_fill_distiller(super = metR::ScaleDiscretised, palette = "Spectral")

And from that, just continue with any adjustments as you see fit.然后,继续进行您认为合适的任何调整。

(This feature is a bit new and it might have errors on cases I didn't considered. If you use it and find any problem, please do open an issue in the github repository . I'd be glad to solve it.) (这个功能有点新,在我没有考虑过的情况下可能会出现错误。如果您使用它并发现任何问题,请在 github 存储库中打开一个问题。我很乐意解决它。)

Created on 2020-11-26 by the reprex package (v0.3.0)reprex 包于 2020-11-26 创建 (v0.3.0)

If I'm not mistaken, the ggplot version 3.3.0 might solve all your problems (as shown in part here ).如果我没记错的话,ggplot 3.3.0 版可能会解决您的所有问题(如这里所示)。 metR package seems to be no longer needed.似乎不再需要metR 包。 Although the question is old, this might help future readers.虽然这个问题很老,但这可能会对未来的读者有所帮助。 You can use the guide_colorsteps function to change from the "interval" labels to the labels we are familiar with.您可以使用guide_colorsteps函数将“间隔”标签更改为我们熟悉的标签。 I used the RColorBrewer package as I think it's the best option if you want to use the "Spectral" palette (as it doesn't contain enough colors for the example), but it's not necessary depending on your palette needs (as explained bellow).我使用了 RColorBrewer 包,因为我认为如果您想使用“光谱”调色板(因为它没有包含足够的示例颜色),它是最好的选择,但根据您的调色板需求,它不是必需的(如下所述) .

Spectral.colors <- colorRampPalette(RColorBrewer::brewer.pal(11, "Spectral"))
ggplot(faithfuld, aes(y=eruptions, x=waiting, z=100*density)) +
  geom_contour_filled(breaks = c(-Inf,-2., -1.5, -1., -0.5, 0, 0.5, 1, 1.5, 2, 3, 4, 5, 7, 9, 11,Inf)) + 
  theme(plot.title = element_text(size = 10,hjust = 0.5)) +
  scale_fill_manual(values = rev(Spectral.colors(16)),drop=FALSE) +
  guides(fill = guide_colorsteps(direction = "horizontal",
                                 barwidth = unit(par("pin")[1], "in"))) +
  theme(legend.position = "bottom")

询问情节

In the above example, scale_fill_manual have two inputs, values and drop .在上面的例子中, scale_fill_manual有两个输入, valuesdrop values is a vector with all used colors (with length 16 as breaks have length 17) and drop=FALSE ensures all your breaks will be shown (as explained here ). values是一个包含所有已使用颜色的向量(长度为 16,因为中断的长度为 17),并且drop=FALSE确保将显示所有中断(如此所述)。 Then guides and theme are adjustments to make a nice bottom horizontal legend.然后调整guidestheme以制作漂亮的底部水平图例。 In particular, barwidth is the width of your legend.特别是, barwidth是图例的宽度。 If not given, its width might be too small.如果没有给出,它的宽度可能太小了。 par("pin")[1] will take the current plot width in inches, but you can specify a specific value if you want. par("pin")[1]将以英寸为单位采用当前绘图宽度,但您可以根据需要指定特定值。

If the length of your breaks is small enough, you can replace scale_fill_manual(values = rev(Spectral.colors(16)),drop=FALSE) by scale_fill_brewer(palette = "Spectral",drop=FALSE) .如果您的休息时间足够小,您可以将scale_fill_manual(values = rev(Spectral.colors(16)),drop=FALSE)替换为scale_fill_brewer(palette = "Spectral",drop=FALSE) If you don't need to use "Spectral" and want to use the original geom_contour_filled palette ("viridis"), the same chunk can be replaced by scale_fill_viridis_d(drop = FALSE) .如果您不需要使用“Spectral”并想使用原始的geom_contour_filled调色板(“viridis”),可以用scale_fill_viridis_d(drop = FALSE)替换相同的块。 And finally, if you want to change the palette to any other except "viridis" and "Spectral", you can try the R build-in palette creator functions (eg, heat.colors ).最后,如果您想将调色板更改为“viridis”和“Spectral”以外的任何其他调色板,您可以尝试 R 内置调色板创建功能(例如heat.colors )。 The same chunk can be replaced by scale_fill_manual(values = heat.colors(16),drop=FALSE) .相同的块可以替换为scale_fill_manual(values = heat.colors(16),drop=FALSE)

If you need the legend to be in its the original place (vertical), your code should be (here I used heat.colors just for illustration):如果您需要图例位于其原始位置(垂直),您的代码应该是(这里我使用heat.colors只是为了说明):

ggplot(faithfuld, aes(y=eruptions, x=waiting, z=100*density)) +
  geom_contour_filled(breaks = c(-Inf,-2., -1.5, -1., -0.5, 0, 0.5, 1, 1.5, 2, 3, 4, 5, 7, 9, 11,Inf)) + 
  theme(plot.title = element_text(size = 10,hjust = 0.5)) +
  scale_fill_manual(values = heat.colors(16),drop=FALSE) +
  guides(fill = guide_colorsteps(barheight = unit(par("pin")[2], "in")))

vertical_legend_alternative

Where barwidth was replaced by barheight and par("pin")[1] was replaced by par("pin")[2] . barwidthbarheight取代, par("pin")[1]par("pin")[2]取代。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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