Skip to content
New issue

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

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

Already on GitHub? # to your account

Avoid tint color shift caused by the limits parameter in scale_*_hypso_tint_b and scale_*_hypso_tint_c #165

Merged
merged 1 commit into from
Jan 23, 2025

Conversation

Fan-iX
Copy link
Contributor

@Fan-iX Fan-iX commented Jan 23, 2025

Hi!

Thanks for your excellent package. I use this package to create elevation maps, its great integrity with ggplot has massively facilitated my work.

I noticed that for the scale_fill_hypso_tint_c function, the elevation tinting of the plot would become inaccurate if the parameter limits is supplied:

ggplot() +
  geom_spatraster(data = asia) +
  scale_fill_hypso_tint_c(
    palette = "gmt_globe",
    limits = c(-6000, 5000),
    # Further refinements
    breaks = c(-6000, -2000, 0, 2000, 5000),
    oob = scales::oob_squish,
    labels = c("<=-6000", "-2000", "0", "2000", ">=5000"),
    guide = guide_colorbar(reverse = TRUE)
  ) +
  labs(
    fill = "elevation (m)",
    title = "Hypsometric map of Asia"
  ) +
  theme(
    legend.position = "bottom",
    legend.title.position = "top",
    legend.key.width = rel(3),
    legend.ticks = element_line(colour = "black", linewidth = 0.3),
    legend.direction = "horizontal"
  )

image

And I expect the result to be

image

(Here the purple color has been truncated comparing with the original palette)

The image above is plotted with a "hotfix" for the function scale_fill_hypso_tint_c in my the R session. This PR is the changes I made to the function, but I didn't run package builds or tests so it may need some review.

Avoid tint color shift caused by the `limits` parameter
@dieghernan
Copy link
Owner

dieghernan commented Jan 23, 2025

Hi @Fan-iX

Thanks for the PR, but I am not really sure of this, if I understood correctly you expect geom_spatraster() to do two different things:

  1. terra::clamp() the values of the raster when producing the plot ( limits = c(-6000, 5000),oob = scales::oob_squish).
  2. Clamp the colour of the legend as well to the values of the clamped raster, being aware of the distribution of colors.

Regarding 2) I am not sure than ggplot2 provides a mechanism for modifying natively the colors of a given legend if already provided, and this modifies the expected behaviour of scales (AFAIK no other scale of ggplot does that, I may be wrong).

Additionally this would need further testing, not only for the failed checks (expected) but for other scales in the package (there are several of them) that uses the same approach, and I am not in a position right now of exploring the implications and corner cases.

I think you can still achieve the same result with some simple steps:

library(terra)
library(tidyterra)
library(ggplot2)

f_asia <- system.file("extdata/asia.tif", package = "tidyterra")
asia <- rast(f_asia) 

asia

Now use the colour database tidyterra::hypsometric_tints_db to adapt the colours to your desired range. This is quite safe, basically you are generating a new palette before ggplot runtime:

# Create custom cols using breaks
data("hypsometric_tints_db")
cols <- hypsometric_tints_db %>%
  filter(pal == "gmt_globe" &
    limit >= -6000 &
      limit <= 5000)

cols
#> # A tibble: 27 × 6
#>    pal       limit     r     g     b hex    
#>    <chr>     <dbl> <dbl> <dbl> <dbl> <chr>  
#>  1 gmt_globe -6000    51   102   255 #3366FF
#>  2 gmt_globe -5500    34   119   255 #2277FF
#>  3 gmt_globe -5000    17   136   255 #1188FF
#>  4 gmt_globe -4500     0   153   255 #0099FF
#>  5 gmt_globe -4000    27   164   255 #1BA4FF
#>  6 gmt_globe -3500    54   175   255 #36AFFF
#>  7 gmt_globe -3000    81   186   255 #51BAFF
#>  8 gmt_globe -2500   108   197   255 #6CC5FF
#>  9 gmt_globe -2000   134   208   255 #86D0FF
#> 10 gmt_globe -1500   161   219   255 #A1DBFF
#> # ℹ 17 more rows

Now use scale_fill_gradientn with parameters colours and values = scales::rescale(cols$limit):

ggplot() +
  geom_spatraster(data = asia) +
  # Use fill_gradientn with our new palette
  scale_fill_gradientn(
    colours = cols$hex, # New cols
    values = scales::rescale(cols$limit), # New values
    limits = c(-6000, 5000),
    # Further refinements
    breaks = c(-6000, -2000, 0, 2000, 5000),
    oob = scales::oob_squish,
    labels = c("<=-6000", "-2000", "0", "2000", ">=5000"),
    guide = guide_colorbar(reverse = TRUE)
  ) +
  labs(
    fill = "elevation (m)",
    title = "Hypsometric map of Asia"
  ) +
  theme(
    legend.position = "bottom",
    legend.title.position = "top",
    legend.key.width = rel(3),
    legend.ticks = element_line(colour = "black", linewidth = 0.3),
    legend.direction = "horizontal"
  )

Rplot

This is more flexible I think. The plot is clamped to your values and the legend is on the colours of the range

@dieghernan
Copy link
Owner

See this reprex, notice that limits does not truncate the colors of the scale, just set the minimum and maximum color level in the defined range:

library(ggplot2)


data("faithfuld")

range(faithfuld$density)
#> [1] 1.25925e-24 3.69878e-02

ggplot(faithfuld, aes(waiting, eruptions)) +
  geom_raster(aes(fill = density)) +
  scale_fill_viridis_c()

ggplot(faithfuld, aes(waiting, eruptions)) +
  geom_raster(aes(fill = density)) +
  scale_fill_viridis_c(
    limits = c(0.02, 0.0275),
    # Further refinements
    breaks = seq(0.015, 0.025, 0.001),
    oob = scales::oob_squish
    )

Created on 2025-01-23 with reprex v2.1.1

@Fan-iX
Copy link
Contributor Author

Fan-iX commented Jan 23, 2025

Thanks for your reply.

What I'm trying to accomplish is to adjust the range (limits) of the color (fill) scale while keeping the elevation tint accurate - I think that's what scale_fill_hypso_*tint*_c is for.

As you pointed out, I didn't find a easy way to clamp a existing color legend. I noticed that scale_fill_hypso_tint_c has a limits parameter, but simply passing a limits value to it function will gives an inaccurate (for elevation tint) color scale, which is unexpected for me.

I think it will be nice if scale_fill_hypso_tint_c could always give a correct color scale for tinting, otherwise its limits parameter is a little bit misleading.

There is a small flaw in the solution using tidyterra::hypsometric_tints_db - if the upper or lower limit value is not listed in the limit column (for example, a lower limit of -6250), the color scale is also inaccurate.

cols <- hypsometric_tints_db %>% filter(pal == "gmt_globe" & limit >= -6250 & limit <= 5000)

ggplot() +
  geom_spatraster(data = asia) +
  scale_fill_gradientn(
    colours = cols$hex,
    values = scales::rescale(cols$limit),
    limits = c(-6250, 5000),
    breaks = c(-6250, -2000, 0, 2000, 5000),
    oob = scales::oob_squish,
    labels = c("<=-6250", "-2000", "0", "2000", ">=5000"),
    guide = guide_colorbar(reverse = TRUE)
  ) +
  labs(
    fill = "elevation (m)",
    title = "Hypsometric map of Asia"
  ) +
  theme(
    legend.position = "bottom",
    legend.title.position = "top",
    legend.key.width = rel(3),
    legend.ticks = element_line(colour = "black", linewidth = 0.3),
    legend.direction = "horizontal"
  )

image

In this case, we need to scales::rescale the cols$limit column according to the upper and lower limits:

cols <- hypsometric_tints_db %>% filter(pal == "gmt_globe")
limits <- c(-6250, 5000)
ggplot() +
  geom_spatraster(data = asia) +
  scale_fill_gradientn(
    colours = cols$hex,
    values = scales::rescale(cols$limit, from=limits),
    limits = limits,
    breaks = c(-6250, -2000, 0, 2000, 5000),
    oob = scales::oob_squish,
    labels = c("<=-6250", "-2000", "0", "2000", ">=5000"),
    guide = guide_colorbar(reverse = TRUE)
  ) +
  labs(
    fill = "elevation (m)",
    title = "Hypsometric map of Asia"
  ) +
  theme(
    legend.position = "bottom",
    legend.title.position = "top",
    legend.key.width = rel(3),
    legend.ticks = element_line(colour = "black", linewidth = 0.3),
    legend.direction = "horizontal"
  )

and this is actually what I do in this PR.

@dieghernan dieghernan merged commit c487144 into dieghernan:main Jan 23, 2025
4 of 12 checks passed
dieghernan added a commit that referenced this pull request Jan 23, 2025
Expand PR #165 to other scales, tests and document
# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants