Skip to content
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

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

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

@clauswilke clauswilke commented Oct 31, 2018

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

@yutannihilation
Copy link
Member Author

@yutannihilation 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

@yutannihilation yutannihilation commented Nov 1, 2018

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

@yutannihilation yutannihilation commented Nov 1, 2018

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

@yutannihilation yutannihilation commented Nov 19, 2018

This also needs to wait for #3003.

@yutannihilation yutannihilation requested a review from clauswilke Nov 26, 2018
@yutannihilation
Copy link
Member Author

@yutannihilation yutannihilation commented Nov 26, 2018

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
4 checks passed
@yutannihilation yutannihilation deleted the fix-coord-sf-inf branch Nov 26, 2018
@lock
Copy link

@lock 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
Linked issues

Successfully merging this pull request may close these issues.

2 participants