coord_cartesian() currently provides two clear and useful clipping behaviours:
clip = "on" clips panel content exactly at the panel boundary.
clip = "off" allows panel content to extend beyond that boundary.
For some plots, however, ggplot2 users could benefit from an intermediate option: retaining panel clipping while allowing the clipping boundary to extend by a small, adjustable physical distance.
This would be particularly valuable when a geom is positioned exactly at, or very close to, a coordinate limit. In such cases, part of the rendered geom may extend beyond the panel and be removed when clip = "on". Setting clip = "off" preserves the complete geom, but also allows other panel content to extend freely into the surrounding plot area, which may be undesirable.
An adjustable clipping allowance—for example, half a line width—could preserve boundary geoms while retaining the protection provided by panel clipping.
Current behaviour with clip = "on"
The following example places a horizontal line at the upper y-limit and a vertical line at the upper x-limit. The line widths and tick lengths are exaggerated to make the clipping clearly visible.
library(ggplot2)
line_width <- 3
plot_clipped <- ggplot(
data.frame(
x = c(0, 1),
y = c(0, 1)
),
aes(x, y)
) +
geom_blank() +
geom_hline(yintercept = 1) +
geom_vline(xintercept = 1) +
coord_cartesian(
expand = FALSE,
clip = "on"
) +
theme_classic(
base_line_size = line_width
) +
theme(
axis.ticks.length = grid::unit(
line_width * 3,
"pt"
)
)
plot_clipped
The horizontal and vertical lines are centred on the upper coordinate limits. Half of each stroke therefore lies inside the panel and half outside it.
Because clip = "on" clips exactly at the panel boundary, the outer half of each stroke is removed. The boundary geoms consequently appear to have only half their intended line width.
This is particularly visible beside the axis lines and terminal tick marks, which retain their complete strokes. A line positioned at the same coordinate as the final break would more naturally have half its stroke inside the panel and half immediately outside it, just as a tick mark is centred on its break.
Current behaviour with clip = "off"
Disabling clipping preserves the complete boundary geoms:
plot_unclipped <- plot_clipped +
coord_cartesian(
expand = FALSE,
clip = "off"
)
plot_unclipped
The geoms now retain their intended line width and correspond visually with the axis lines and tick marks.
However, clip = "off" disables clipping for all panel content. Geoms can therefore extend into the axes, labels, legends, and plot margins.
This is a broader change than is needed when the goal is simply to preserve the outer half of a stroke at the panel boundary.
Proposed behaviour
Could coord_cartesian() support a small, adjustable amount of physical padding around the clipping boundary?
For example:
coord_cartesian(
expand = FALSE,
clip = "on",
clip_padding = grid::unit(0.2, "mm")
)
The coordinate limits, panel dimensions, axis positions, and data mapping would remain unchanged. Only the physical boundary at which panel content is clipped would move slightly beyond the panel.
This would provide an intermediate option:
- content within the small clipping allowance would remain visible;
- content extending farther outside the panel would still be clipped.
A natural allowance for line-based geoms would often be half a line width. For visual consistency, a line positioned at the final break should be able to retain half its stroke inside the panel and half immediately outside it, just as the tick mark is centred on that break.
The exact interface is not central to this request. The main feature would be the ability to control the physical clipping boundary independently of the coordinate range and panel dimensions.
Proof of concept
The desired behaviour can be produced by preserving the original viewport for coordinate mapping and wrapping it in a slightly larger viewport that controls clipping.
The following helper converts a ggplot2 line width to half of its physical stroke width:
half_linewidth_unit <- function(linewidth) {
lwd <- linewidth * ggplot2::.pt
# Convert the full stroke width to inches, then take half.
grid::unit(
(lwd / 96) / 2,
"inches"
)
}
A custom Cartesian coordinate system can then override the panel drawing method:
CoordCartesianPadded <- ggplot2::ggproto(
"CoordCartesianPadded",
ggplot2::CoordCartesian,
clip_padding = NULL,
draw_panel = function(
self,
panel,
params,
theme
) {
padding <- self$clip_padding
if (is.null(padding)) {
linewidth <- ggplot2::calc_element(
"geom",
theme
)$linewidth
padding <- half_linewidth_unit(linewidth)
}
if (
!grid::is.unit(padding) ||
length(padding) != 1L
) {
stop(
"`clip_padding` must be NULL or a single grid unit.",
call. = FALSE
)
}
parent <- ggplot2::ggproto_parent(
ggplot2::CoordCartesian,
self
)
inner_panel <- parent$draw_panel(
panel = panel,
params = params,
theme = theme
)
# Preserve the original panel dimensions and coordinate mapping.
inner_panel$vp <- grid::viewport(
width = grid::unit(1, "npc") - 2 * padding,
height = grid::unit(1, "npc") - 2 * padding,
clip = "inherit"
)
# Apply clipping at the enlarged physical boundary.
grid::gTree(
children = grid::gList(inner_panel),
vp = grid::viewport(
width = grid::unit(1, "npc") + 2 * padding,
height = grid::unit(1, "npc") + 2 * padding,
clip = self$clip
)
)
}
)
The corresponding constructor reuses the existing coord_cartesian() arguments and validation:
coord_cartesian_padded <- function(
xlim = NULL,
ylim = NULL,
expand = TRUE,
default = FALSE,
clip = "on",
reverse = "none",
ratio = NULL,
clip_padding = NULL
) {
coord <- ggplot2::coord_cartesian(
xlim = xlim,
ylim = ylim,
expand = expand,
default = default,
clip = clip,
reverse = reverse,
ratio = ratio
)
ggplot2::ggproto(
NULL,
CoordCartesianPadded,
limits = coord$limits,
reverse = coord$reverse,
expand = coord$expand,
default = coord$default,
clip = coord$clip,
ratio = coord$ratio,
clip_padding = clip_padding
)
}
The example can then be recreated with a clipping allowance equal to half the line width:
plot_padded <- ggplot(
data.frame(
x = c(0, 1),
y = c(0, 1)
),
aes(x, y)
) +
geom_blank() +
geom_hline(yintercept = 1) +
geom_vline(xintercept = 1) +
coord_cartesian_padded(
expand = FALSE,
clip = "on",
clip_padding = half_linewidth_unit(line_width)
) +
theme_classic(
base_line_size = line_width
) +
theme(
axis.ticks.length = grid::unit(
line_width * 3,
"pt"
)
)
plot_padded
The coordinate range and data mapping are unchanged. The boundary geoms retain their complete strokes, while content extending beyond the small allowance remains clipped.
This code is intended only as a proof of concept rather than a proposed implementation.
Why this would be valuable
Plots without axis expansion are common in publication figures. In these plots, geoms positioned at or near a panel boundary may be clipped and therefore rendered smaller than intended.
Current workarounds either change the coordinate mapping, modify the data, require low-level grob manipulation, or disable clipping entirely. A small clipping allowance would preserve the intended appearance of boundary geoms without changing the coordinate mapping or disabling panel clipping.
An explicit clip_padding argument would provide the essential functionality, potentially with half the resolved geom line width as a useful default.
coord_cartesian()currently provides two clear and useful clipping behaviours:clip = "on"clips panel content exactly at the panel boundary.clip = "off"allows panel content to extend beyond that boundary.For some plots, however, ggplot2 users could benefit from an intermediate option: retaining panel clipping while allowing the clipping boundary to extend by a small, adjustable physical distance.
This would be particularly valuable when a geom is positioned exactly at, or very close to, a coordinate limit. In such cases, part of the rendered geom may extend beyond the panel and be removed when
clip = "on". Settingclip = "off"preserves the complete geom, but also allows other panel content to extend freely into the surrounding plot area, which may be undesirable.An adjustable clipping allowance—for example, half a line width—could preserve boundary geoms while retaining the protection provided by panel clipping.
Current behaviour with
clip = "on"The following example places a horizontal line at the upper y-limit and a vertical line at the upper x-limit. The line widths and tick lengths are exaggerated to make the clipping clearly visible.
The horizontal and vertical lines are centred on the upper coordinate limits. Half of each stroke therefore lies inside the panel and half outside it.
Because
clip = "on"clips exactly at the panel boundary, the outer half of each stroke is removed. The boundary geoms consequently appear to have only half their intended line width.This is particularly visible beside the axis lines and terminal tick marks, which retain their complete strokes. A line positioned at the same coordinate as the final break would more naturally have half its stroke inside the panel and half immediately outside it, just as a tick mark is centred on its break.
Current behaviour with
clip = "off"Disabling clipping preserves the complete boundary geoms:
The geoms now retain their intended line width and correspond visually with the axis lines and tick marks.
However,
clip = "off"disables clipping for all panel content. Geoms can therefore extend into the axes, labels, legends, and plot margins.This is a broader change than is needed when the goal is simply to preserve the outer half of a stroke at the panel boundary.
Proposed behaviour
Could
coord_cartesian()support a small, adjustable amount of physical padding around the clipping boundary?For example:
The coordinate limits, panel dimensions, axis positions, and data mapping would remain unchanged. Only the physical boundary at which panel content is clipped would move slightly beyond the panel.
This would provide an intermediate option:
A natural allowance for line-based geoms would often be half a line width. For visual consistency, a line positioned at the final break should be able to retain half its stroke inside the panel and half immediately outside it, just as the tick mark is centred on that break.
The exact interface is not central to this request. The main feature would be the ability to control the physical clipping boundary independently of the coordinate range and panel dimensions.
Proof of concept
The desired behaviour can be produced by preserving the original viewport for coordinate mapping and wrapping it in a slightly larger viewport that controls clipping.
The following helper converts a ggplot2 line width to half of its physical stroke width:
A custom Cartesian coordinate system can then override the panel drawing method:
The corresponding constructor reuses the existing
coord_cartesian()arguments and validation:The example can then be recreated with a clipping allowance equal to half the line width:
The coordinate range and data mapping are unchanged. The boundary geoms retain their complete strokes, while content extending beyond the small allowance remains clipped.
This code is intended only as a proof of concept rather than a proposed implementation.
Why this would be valuable
Plots without axis expansion are common in publication figures. In these plots, geoms positioned at or near a panel boundary may be clipped and therefore rendered smaller than intended.
Current workarounds either change the coordinate mapping, modify the data, require low-level grob manipulation, or disable clipping entirely. A small clipping allowance would preserve the intended appearance of boundary geoms without changing the coordinate mapping or disabling panel clipping.
An explicit
clip_paddingargument would provide the essential functionality, potentially with half the resolved geom line width as a useful default.