diff --git a/NEWS.md b/NEWS.md
index c0097b2de4..db00b34331 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -221,6 +221,8 @@
the new default in many scales (@teunbrand, #4696).
* `guide_axis()` no longer reserves space for blank ticks
(@teunbrand, #4722, #6069).
+* `geom_abline()` clips to the panel range in the vertical direction too
+ (@teunbrand, #6086).
# ggplot2 3.5.1
diff --git a/R/geom-abline.R b/R/geom-abline.R
index e9775e33fb..825d45faf8 100644
--- a/R/geom-abline.R
+++ b/R/geom-abline.R
@@ -132,12 +132,16 @@ GeomAbline <- ggproto("GeomAbline", Geom,
# Ensure the line extends well outside the panel to avoid visible line
# ending for thick lines
ranges$x <- ranges$x + c(-1, 1) * diff(ranges$x)
+ ranges$y <- ranges$y + c(-1, 1) * diff(ranges$y)
}
- data$x <- ranges$x[1]
- data$xend <- ranges$x[2]
- data$y <- ranges$x[1] * data$slope + data$intercept
- data$yend <- ranges$x[2] * data$slope + data$intercept
+ # Restrict 'x' to where 'y' is in range: x = (y - intercept) / slope
+ x <- sweep(outer(ranges$y, data$intercept, FUN = "-"), 2, data$slope, FUN = "/")
+
+ data$x <- pmax(ranges$x[1], pmin(x[1, ], x[2, ]))
+ data$xend <- pmin(ranges$x[2], pmax(x[1, ], x[2, ]))
+ data$y <- data$x * data$slope + data$intercept
+ data$yend <- data$xend * data$slope + data$intercept
GeomSegment$draw_panel(unique0(data), panel_params, coord, lineend = lineend)
},
diff --git a/tests/testthat/_snaps/geom-hline-vline-abline/cartesian-lines-intersect-mid-bars.svg b/tests/testthat/_snaps/geom-hline-vline-abline/cartesian-lines-intersect-mid-bars.svg
index f76c20a281..163e6973da 100644
--- a/tests/testthat/_snaps/geom-hline-vline-abline/cartesian-lines-intersect-mid-bars.svg
+++ b/tests/testthat/_snaps/geom-hline-vline-abline/cartesian-lines-intersect-mid-bars.svg
@@ -39,7 +39,7 @@
-
+
diff --git a/tests/testthat/_snaps/geom-hline-vline-abline/flipped-lines-intersect-mid-bars.svg b/tests/testthat/_snaps/geom-hline-vline-abline/flipped-lines-intersect-mid-bars.svg
index bdda8286d6..cd136306cc 100644
--- a/tests/testthat/_snaps/geom-hline-vline-abline/flipped-lines-intersect-mid-bars.svg
+++ b/tests/testthat/_snaps/geom-hline-vline-abline/flipped-lines-intersect-mid-bars.svg
@@ -39,7 +39,7 @@
-
+
diff --git a/tests/testthat/_snaps/geom-hline-vline-abline/polar-lines-intersect-mid-bars.svg b/tests/testthat/_snaps/geom-hline-vline-abline/polar-lines-intersect-mid-bars.svg
index c6f3b60763..2f67080988 100644
--- a/tests/testthat/_snaps/geom-hline-vline-abline/polar-lines-intersect-mid-bars.svg
+++ b/tests/testthat/_snaps/geom-hline-vline-abline/polar-lines-intersect-mid-bars.svg
@@ -48,7 +48,7 @@
-
+
A
B
C
diff --git a/tests/testthat/test-geom-hline-vline-abline.R b/tests/testthat/test-geom-hline-vline-abline.R
index b637cd0a2f..8a324dcf4c 100644
--- a/tests/testthat/test-geom-hline-vline-abline.R
+++ b/tests/testthat/test-geom-hline-vline-abline.R
@@ -43,6 +43,25 @@ test_that("curved lines in map projections", {
)
})
+test_that("geom_abline is clipped to x/y ranges", {
+
+ df <- data.frame(slope = c(-0.2, -1, -5, 5, 1, 0.2))
+
+ p <- ggplot(df) +
+ geom_abline(aes(slope = slope, intercept = 0)) +
+ scale_x_continuous(limits = c(-1, 1), expand = FALSE) +
+ scale_y_continuous(limits = c(-1, 1), expand = FALSE) +
+ coord_cartesian(clip = "off")
+
+ data <- layer_grob(p)[[1]]
+
+ x <- c(as.numeric(data$x0), as.numeric(data$x1))
+ expect_true(all(x >= 0 & x <= 1))
+
+ y <- c(as.numeric(data$y0), as.numeric(data$y1))
+ expect_true(all(y >= 0 & y <= 1))
+})
+
# Warning tests ------------------------------------------------------------
test_that("warnings are thrown when parameters cause mapping and data to be ignored", {