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

Double coordinate transformations for geoms that draw with other geoms #2149

Closed
has2k1 opened this Issue Jun 2, 2017 · 5 comments

Comments

Projects
None yet
4 participants
@has2k1
Contributor

has2k1 commented Jun 2, 2017

When a geom queries the coordinate range and uses the result to create data that is then plotted by another geom, there is potentially a double transformation. i.e. coord.range() yields results in coordinate space and if followed by coord.transform(), the data may get transformed two times. Affects coords that transform the limits in coord$setup_panel_params and also do transforms in coord$transform. This includes coord_trans and coord_sf.

Affected geoms include geom_abline, geom_hline and geom_hline.

reprex

library(ggplot2)

df <- data.frame(x=1:6, y=10^(1:6))
ggplot(df, aes(x, y)) +
  geom_point() +
  geom_vline(xintercept=3, color='red') +
  coord_trans(y='log10')

The red line (at x=3) should be top to bottom.

@hadley

This comment has been minimized.

Member

hadley commented Oct 31, 2017

I don't see any obvious way to fix this. It'll have to wait until someone is deeper into coordinate systems.

@clauswilke

This comment has been minimized.

Member

clauswilke commented Aug 9, 2018

I think the correct way to fix this is to copy the line-drawing code from geom_segment():

ggplot2/R/geom-segment.r

Lines 106 to 141 in 4d2ca99

draw_panel = function(data, panel_params, coord, arrow = NULL, arrow.fill = NULL,
lineend = "butt", linejoin = "round", na.rm = FALSE) {
data <- remove_missing(data, na.rm = na.rm,
c("x", "y", "xend", "yend", "linetype", "size", "shape"),
name = "geom_segment")
if (empty(data)) return(zeroGrob())
if (coord$is_linear()) {
coord <- coord$transform(data, panel_params)
arrow.fill <- arrow.fill %||% coord$colour
return(segmentsGrob(coord$x, coord$y, coord$xend, coord$yend,
default.units = "native",
gp = gpar(
col = alpha(coord$colour, coord$alpha),
fill = alpha(arrow.fill, coord$alpha),
lwd = coord$size * .pt,
lty = coord$linetype,
lineend = lineend,
linejoin = linejoin
),
arrow = arrow
))
}
data$group <- 1:nrow(data)
starts <- subset(data, select = c(-xend, -yend))
ends <- plyr::rename(subset(data, select = c(-x, -y)), c("xend" = "x", "yend" = "y"),
warn_missing = FALSE)
pieces <- rbind(starts, ends)
pieces <- pieces[order(pieces$group),]
GeomPath$draw_panel(pieces, panel_params, coord, arrow = arrow,
lineend = lineend)
},

and modify appropriately for geom_abline() etc. It's the geom's responsibility to draw correctly given the data and the coord, and if a geom can't guarantee correct drawing when using another geom's drawing code then it needs to provide its own.

@clauswilke

This comment has been minimized.

Member

clauswilke commented Aug 9, 2018

I'd be happy to try to put together a PR. There's one important question, though: Is the intent that lines are straight in the data coordinate system, so they may become curved in the transformed space, or should they always be straight in the final plot?

@hadley

This comment has been minimized.

Member

hadley commented Aug 9, 2018

I think they have to be straight in the data coordinate system, otherwise I can’t imagine how you’d interpret the parameters in (eg) polar coordinates.

@clauswilke

This comment has been minimized.

Member

clauswilke commented Aug 10, 2018

Actually, I was too quick in blaming geom_abline() etc. The problem is with the coords, and specifically with the range() function. It looks like the code is inconsistent with respect to whether range() returns the range in the original data coordinates or in transformed coordinates. In several places in the code, it is assumed that range() returns original data coordinates. One is geom_abline() etc., and another location is coord_munch():

ggplot2/R/coord-munch.r

Lines 14 to 35 in 8778b48

coord_munch <- function(coord, data, range, segment_length = 0.01) {
if (coord$is_linear()) return(coord$transform(data, range))
# range has theta and r values; get corresponding x and y values
ranges <- coord$range(range)
# Convert any infinite locations into max/min
# Only need to work with x and y because for munching, those are the
# only position aesthetics that are transformed
data$x[data$x == -Inf] <- ranges$x[1]
data$x[data$x == Inf] <- ranges$x[2]
data$y[data$y == -Inf] <- ranges$y[1]
data$y[data$y == Inf] <- ranges$y[2]
# Calculate distances using coord distance metric
dist <- coord$distance(data$x, data$y, range)
dist[data$group[-1] != data$group[-nrow(data)]] <- NA
# Munch and then transform result
munched <- munch_data(data, dist, segment_length)
coord$transform(munched, range)
}

Thus, I can trigger a similar problem with geom_line():

library(ggplot2)
df <- data.frame(x = c(-Inf, Inf), y = c(1, 2))
p <- ggplot(df, aes(x, y)) + geom_line() + xlim(1, 2)
p

p + coord_flip()

p + coord_polar()

p + coord_trans(y = "log10")

p + coord_trans(x = "log10")
#> Warning in self$trans$x$transform(x): NaNs produced
#> Warning in trans$transform(value): NaNs produced
#> Warning: Transformation introduced infinite values in x-axis

Created on 2018-08-09 by the reprex package (v0.2.0).

Notice how coord_flip() and coord_polar() return the correct values but coord_trans() does not for x coordinates (it doesn't matter for y). One way to fix this would be to make sure that the range() function always returns the range in the original data coordinates. However, I'm not yet clear on whether there are other parts of the code that expect range() to return transformed coordinates. An alternative way to fix this would be to provide a second range function that always returns untransformed coordinates.

clauswilke added a commit to clauswilke/ggplot2 that referenced this issue Aug 10, 2018

clauswilke added a commit to clauswilke/ggplot2 that referenced this issue Aug 10, 2018

clauswilke added a commit to clauswilke/ggplot2 that referenced this issue Aug 11, 2018

clauswilke added a commit to clauswilke/ggplot2 that referenced this issue Aug 17, 2018

clauswilke added a commit to clauswilke/ggplot2 that referenced this issue Aug 22, 2018

clauswilke added a commit to clauswilke/ggplot2 that referenced this issue Aug 23, 2018

clauswilke added a commit that referenced this issue Aug 23, 2018

Fix back-transformation of ranges in coords, without API cleanup (#2832)
* rename range() function of coord to backtransform_range()

* update news. closes #2149, #2812

* add range backtransformation function. closes #2820.

* fix typo in coord code

* add backtransform_range() to coord_map

* update summarise_layout to use backtransform_range() instead of range().

* Update coord documentation

* undo API cleanup

* update documentation

* rebase

* fix remaining issue from rebase
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment