Skip to content

Squish infinite values in coord_sf(), coord_map(), and coord_polar() #2972

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

Merged
merged 12 commits into from
Nov 26, 2018

Conversation

yutannihilation
Copy link
Member

@yutannihilation yutannihilation commented Oct 31, 2018

Fixes #2971 (and r-spatial/sf#879)

It seems CoordSf, CoordMap, and CoordPolar don't follow the convention of treating Inf as the edge of the range. This PR fixes it.

CoordSf

It seems CoordSf$transform() just forget to squish infinite values to range as coord_cartesian() does.

CoordMap

In CoordMap, Inf can be converted to NA by mapproj:: mapproject(). So, just applying squish_infinite() is not enough; we need to restore the Inf from the original data.

CoordPolar

For angle (theta), Inf and -Inf are squashed into 0, which is both the start and the end of the circle. For radius (r), Inf is squashed to 0.4, the hardcoded value in r_rescale(), and -Inf is squashed to 0.

Results

library(ggplot2)

ggplot(sf::st_point(c(0, 0))) +
  geom_sf() +
  annotate("text", -Inf, Inf, label = "Top-left", hjust = 0, vjust = 1)

ggplot(data.frame(x = 0, y = 0)) +
  geom_point(aes(x,y)) +
  annotate("text", -Inf, Inf, label = "Top-left", hjust = 0, vjust = 1) +
  coord_map()

ggplot(data.frame(x = "a", y = 1), aes(x, y)) +
  geom_col(fill = "white") +
  coord_polar() +
  annotate("text", Inf, Inf, label = "Top-Center") +
  annotate("text", -Inf, -Inf, label = "Center-Center")

Created on 2018-11-26 by the reprex package (v0.2.1)

@clauswilke
Copy link
Member

This seems reasonable to me. Are there other coords that have the same issue?

@yutannihilation
Copy link
Member Author

yutannihilation commented Nov 1, 2018

Thanks, here's a table of transform()s. To me,

  • CoordCartesian and CoordTrans are fine.
  • CoordFlip is fine since it uses CoordCartesian$transform() inside.
  • CoordFixed and CoordQuickmap are fine since they inherit from CoordCartesian.
  • CoordSf needs to be fixed.
  • CoordMap seems to have the same issue, so I'll fix this as well.
  • CoordPolar seems to have the same issue, but I'm yet to find what it means to "squash infinite values to range" in this coordinate system...
library(ggplot2)
library(purrr)

e <- as.environment("package:ggplot2")
coords_names <- ls(e, pattern = "^Coord")
coords <- map(set_names(coords_names), get, envir = e)

transforms <- coords %>%
  map("transform") %>%
  map(rlang::expr_text) %>%
  map(sprintf, fmt = '<div class="highlight highlight-source-r"><pre>%s</pre></div>')

knitr::kable(
  tibble::enframe(transforms),
  format = "html",
  escape = FALSE
)

name

value

Coord

function (data, range) 
NULL

CoordCartesian

function (data, panel_params) 
{
    rescale_x <- function(data) rescale(data, from = panel_params$x.range)
    rescale_y <- function(data) rescale(data, from = panel_params$y.range)
    data <- transform_position(data, rescale_x, rescale_y)
    transform_position(data, squish_infinite, squish_infinite)
}

CoordFixed

NULL

CoordFlip

function (data, panel_params) 
{
    data <- flip_labels(data)
    CoordCartesian$transform(data, panel_params)
}

CoordMap

function (self, data, panel_params) 
{
    trans <- mproject(self, data$x, data$y, panel_params$orientation)
    out <- cunion(trans[c("x", "y")], data)
    out$x <- rescale(out$x, 0:1, panel_params$x.proj)
    out$y <- rescale(out$y, 0:1, panel_params$y.proj)
    out
}

CoordPolar

function (self, data, panel_params) 
{
    data <- rename_data(self, data)
    data$r <- r_rescale(self, data$r, panel_params)
    data$theta <- theta_rescale(self, data$theta, panel_params)
    data$x <- data$r * sin(data$theta) + 0.5
    data$y <- data$r * cos(data$theta) + 0.5
    data
}

CoordQuickmap

NULL

CoordSf

function (self, data, panel_params) 
{
    data[[geom_column(data)]] <- sf_rescale01(data[[geom_column(data)]], 
        panel_params$x_range, panel_params$y_range)
    data <- transform_position(data, function(x) sf_rescale01_x(x, 
        panel_params$x_range), function(x) sf_rescale01_x(x, 
        panel_params$y_range))
    data
}

CoordTrans

function (self, data, panel_params) 
{
    trans_x <- function(data) transform_value(self$trans$x, data, 
        panel_params$x.range)
    trans_y <- function(data) transform_value(self$trans$y, data, 
        panel_params$y.range)
    new_data <- transform_position(data, trans_x, trans_y)
    warn_new_infinites(data$x, new_data$x, "x")
    warn_new_infinites(data$y, new_data$y, "y")
    transform_position(new_data, squish_infinite, squish_infinite)
}

Created on 2018-11-01 by the reprex package (v0.2.1)

@yutannihilation
Copy link
Member Author

Hmm, coord_map() does have the same problem, but it seems the same fix is not enough...

library(ggplot2)

p <- ggplot(data.frame(x = 0, y = 0)) +
  geom_point(aes(x,y)) +
  annotate("text", -Inf, Inf, label = "Top-left", hjust = 0, vjust = 1)

patchwork::wrap_plots(
  p + coord_map(),
  p + coord_quickmap()
)

Created on 2018-11-01 by the reprex package (v0.2.1)

The problem is that CoordMap$transform() converts Inf to NA because underlying mapproj::mapproject() does so. But, mapproj::mapproject() originally perservers Inf. After building a plot, the same function returns different result. What happened...?

library(ggplot2)

mapproj::mapproject(data.frame(x = c(-Inf, 0, 1), y = c(0, 1, Inf)))
#> $x
#> [1] -Inf    0    1
#> 
#> $y
#> [1]   0   1 Inf

p <- ggplot(data.frame(x = 0, y = 0)) +
  geom_point(aes(x,y)) +
  annotate("text", -Inf, Inf, label = "Top-left", hjust = 0, vjust = 1) +
  coord_map()

b <- ggplot_build(p)

mapproj::mapproject(data.frame(x = c(-Inf, 0, 1), y = c(0, 1, Inf)))
#> $x
#> [1] NA  0 NA
#> 
#> $y
#> [1]         NA 0.01745418         NA
#> 
#> $range
#> [1] 0.00000000 0.00000000 0.01745418 0.01745418
#> 
#> $error
#> [1] 1

Created on 2018-11-01 by the reprex package (v0.2.1)

@yutannihilation
Copy link
Member Author

Maybe mapproj::.Last.projection() matters? Anyway, we need some tweaks to restore NAs to Infs...

@yutannihilation yutannihilation changed the title Let coord_sf() to squish infinite values to range Squish infinite values in coord_sf(), coord_map(), and coord_polar() Nov 19, 2018
@yutannihilation
Copy link
Member Author

This also needs to wait for #3003.

@yutannihilation
Copy link
Member Author

Ah, sorry, I forgot to add a NEWS bullete... Will add one and merge.

@yutannihilation yutannihilation merged commit 23a23cd into tidyverse:master Nov 26, 2018
@yutannihilation yutannihilation deleted the fix-coord-sf-inf branch November 26, 2018 23:40
@lock
Copy link

lock bot commented May 26, 2019

This old issue has been automatically locked. If you believe you have found a related problem, please file a new issue (with reprex) and link to this issue. https://reprex.tidyverse.org/

@lock lock bot locked and limited conversation to collaborators May 26, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Unable to add text annotation to geom_sf plot
2 participants