Skip to content

Commit b74570c

Browse files
authored
coord_sf() scale function breaks (#5442)
* Helper function for breaks * Use new breaks * NULL breaks discard gridlines * Fix typo * Add tests * fallback for problematic crs * Add news bullet * Fix `breaks = NULL` case * replace removed `len0_null()` * remove duplicated news entry
1 parent 7f6ca67 commit b74570c

File tree

4 files changed

+148
-2
lines changed

4 files changed

+148
-2
lines changed

NEWS.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# ggplot2 (development version)
22

3+
* Position scales combined with `coord_sf()` can now use functions in the
4+
`breaks` argument. In addition, `n.breaks` works as intended and
5+
`breaks = NULL` removes grid lines and axes (@teunbrand, #4622).
36
* (Internal) Applying defaults in `geom_sf()` has moved from the internal
47
`sf_grob()` to `GeomSf$use_defaults()` (@teunbrand).
58
* `facet_wrap()` has new options for the `dir` argument to more precisely

R/coord-sf.R

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -222,16 +222,25 @@ CoordSf <- ggproto("CoordSf", CoordCartesian,
222222
x_range[2], y_range[2]
223223
)
224224

225+
breaks <- sf_breaks(scale_x, scale_y, bbox, params$crs)
226+
225227
# Generate graticule and rescale to plot coords
226228
graticule <- sf::st_graticule(
227229
bbox,
228230
crs = params$crs,
229-
lat = scale_y$breaks %|W|% NULL,
230-
lon = scale_x$breaks %|W|% NULL,
231+
lat = breaks$y %|W|% NULL,
232+
lon = breaks$x %|W|% NULL,
231233
datum = self$datum,
232234
ndiscr = self$ndiscr
233235
)
234236

237+
if (is.null(breaks$x)) {
238+
graticule <- vec_slice(graticule, graticule$type != "E")
239+
}
240+
if (is.null(breaks$y)) {
241+
graticule <- vec_slice(graticule, graticule$type != "N")
242+
}
243+
235244
# override graticule labels provided by sf::st_graticule() if necessary
236245
graticule <- self$fixup_graticule_labels(graticule, scale_x, scale_y, params)
237246

@@ -580,6 +589,53 @@ parse_axes_labeling <- function(x) {
580589
list(top = labs[1], right = labs[2], bottom = labs[3], left = labs[4])
581590
}
582591

592+
# This function does two things differently from standard breaks:
593+
# 1. It does not resolve `waiver()`, unless `n.breaks` is given. In the case
594+
# that breaks are `waiver()`, we use the default graticule breaks.
595+
# 2. It discards non-finite breaks because they are invalid input to the
596+
# graticule. This may cause atomic `labels` to be out-of-sync.
597+
sf_breaks <- function(scale_x, scale_y, bbox, crs) {
598+
599+
has_x <- !is.null(scale_x$breaks) || !is.null(scale_x$n.breaks)
600+
has_y <- !is.null(scale_y$breaks) || !is.null(scale_y$n.breaks)
601+
602+
x_breaks <- if (has_x) waiver() else NULL
603+
y_breaks <- if (has_y) waiver() else NULL
604+
605+
606+
if (has_x || has_y) {
607+
if (!is.null(crs)) {
608+
# Atomic breaks input are assumed to be in long/lat coordinates.
609+
# To preserve that assumption for function breaks, the bounding box
610+
# needs to be translated to long/lat coordinates.
611+
if (!is_named(bbox)) {
612+
names(bbox) <- c("xmin", "ymin", "xmax", "ymax")
613+
}
614+
# Convert bounding box to long/lat coordinates
615+
bbox <- sf::st_as_sfc(sf::st_bbox(bbox, crs = crs))
616+
bbox <- sf::st_bbox(sf::st_transform(bbox, 4326))
617+
bbox <- as.numeric(bbox)
618+
619+
# If any bbox is NA the transformation has probably failed.
620+
# (.e.g from IGH to long/lat). In this case, just provide full long/lat.
621+
bbox[is.na(bbox)] <- c(-180, -90, 180, 90)[is.na(bbox)]
622+
}
623+
624+
if (!(is.waive(scale_x$breaks) && is.null(scale_x$n.breaks))) {
625+
x_breaks <- scale_x$get_breaks(limits = bbox[c(1, 3)])
626+
finite <- is.finite(x_breaks)
627+
x_breaks <- if (any(finite)) x_breaks[finite] else NULL
628+
}
629+
630+
if (!(is.waive(scale_y$breaks) && is.null(scale_y$n.breaks))) {
631+
y_breaks <- scale_y$get_breaks(limits = bbox[c(2, 4)])
632+
finite <- is.finite(y_breaks)
633+
y_breaks <- if (any(finite)) y_breaks[finite] else NULL
634+
}
635+
}
636+
637+
list(x = x_breaks, y = y_breaks)
638+
}
583639

584640
#' ViewScale from graticule
585641
#'
Lines changed: 48 additions & 0 deletions
Loading

tests/testthat/test-coord_sf.R

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,20 @@ test_that("graticule lines can be removed via theme", {
3030
expect_doppelganger("no panel grid", plot)
3131
})
3232

33+
test_that("graticule lines and axes can be removed via scales", {
34+
skip_if_not_installed("sf")
35+
36+
df <- data_frame(x = c(1, 2, 3), y = c(1, 2, 3))
37+
plot <- ggplot(df, aes(x, y)) +
38+
geom_point() +
39+
coord_sf() +
40+
theme_gray() +
41+
scale_x_continuous(breaks = NULL) +
42+
scale_y_continuous(breaks = NULL)
43+
44+
expect_doppelganger("no breaks", plot)
45+
})
46+
3347
test_that("axis labels are correct for manual breaks", {
3448
skip_if_not_installed("sf")
3549

@@ -300,6 +314,31 @@ test_that("sf_transform_xy() works", {
300314

301315
})
302316

317+
test_that("coord_sf() can use function breaks and n.breaks", {
318+
319+
polygon <- sf::st_sfc(
320+
sf::st_polygon(list(matrix(c(-80, -76, -76, -80, -80, 35, 35, 40, 40, 35), ncol = 2))),
321+
crs = 4326 # basic long-lat crs
322+
)
323+
polygon <- sf::st_transform(polygon, crs = 3347)
324+
325+
p <- ggplot(polygon) + geom_sf(fill = NA) +
326+
scale_x_continuous(breaks = breaks_width(0.5)) +
327+
scale_y_continuous(n.breaks = 4)
328+
329+
b <- ggplot_build(p)
330+
grat <- b$layout$panel_params[[1]]$graticule
331+
332+
expect_equal(
333+
vec_slice(grat$degree, grat$type == "E"),
334+
seq(-81, -74.5, by = 0.5)
335+
)
336+
expect_equal(
337+
vec_slice(grat$degree, grat$type == "N"),
338+
seq(34, 40, by = 2)
339+
)
340+
})
341+
303342
test_that("coord_sf() uses the guide system", {
304343
skip_if_not_installed("sf")
305344
polygon <- sf::st_sfc(

0 commit comments

Comments
 (0)