Nothing
#' Line patterned grobs
#'
#' `grid.pattern_line()` draws a line pattern onto the graphic device.
#' Unlike [grid.pattern_stripe()] which fills bands with solid colour,
#' this pattern draws stroked lines using the device's native line rendering,
#' enabling all of R's built-in `linetype` values (including `"dotdash"`,
#' `"twodash"`, and custom line types specified as hex strings per `?par`).
#'
#' @inheritParams grid.pattern_circle
#' @inheritParams alphaMaskGrob
#' @param lineend Line end style, one of `"round"` (default), `"butt"`, or `"square"`.
#' @param stagger If `TRUE`, alternate lines are shifted by half the dash period so that
#' dashes of adjacent lines interleave.
#' Computed from `linetype` and `linewidth` per `?par`. Default `FALSE`.
#' @return A grid grob object invisibly. If `draw` is `TRUE` then also draws to the graphic device as a side effect.
#' @examples
#' x_hex <- 0.5 + 0.5 * cos(seq(2 * pi / 4, by = 2 * pi / 6, length.out = 6))
#' y_hex <- 0.5 + 0.5 * sin(seq(2 * pi / 4, by = 2 * pi / 6, length.out = 6))
#' if (capabilities("png") || guess_has_R4.1_features("masks")) {
#' grid::grid.newpage()
#' grid.pattern_line(x_hex, y_hex, colour = "black", linetype = "dotdash",
#' angle = 45, spacing = 0.1, stagger = TRUE)
#' }
#'
#' # more intricate dashed lines are possible with hex strings
#' if (capabilities("png") || guess_has_R4.1_features("masks")) {
#' grid::grid.newpage()
#' grid.pattern_line(x_hex, y_hex, gp = grid::gpar(col = "darkred", lty = "23632E"))
#' }
#' @seealso [grid.pattern_stripe()] for filled bands, [grid.pattern_wave()] for wavy bands, [grid.pattern_crosshatch()] for two sets of lines.
#' @export
grid.pattern_line <- function(
x = c(0, 0, 1, 1),
y = c(1, 0, 0, 1),
id = 1L,
...,
colour = gp$col %||% "grey20",
angle = 30,
spacing = 0.05,
xoffset = 0,
yoffset = 0,
units = "snpc",
alpha = gp$alpha %||% NA_real_,
lineend = gp$lineend %||% "round",
linetype = gp$lty %||% 1,
linewidth = size %||% gp$lwd %||% 1,
size = NULL,
stagger = FALSE,
use_R4.1_masks = getOption(
"ggpattern_use_R4.1_masks",
getOption("ggpattern_use_R4.1_features")
),
png_device = NULL,
res = getOption("ggpattern_res", 72),
default.units = "npc",
name = NULL,
gp = gpar(),
draw = TRUE,
vp = NULL
) {
if (missing(colour) && hasName(l <- list(...), "color")) {
colour <- l$color
}
grid.pattern(
"line",
x,
y,
id,
colour = colour,
angle = angle,
spacing = spacing,
xoffset = xoffset,
yoffset = yoffset,
units = units,
alpha = alpha,
linetype = linetype,
linewidth = linewidth,
lineend = lineend,
stagger = stagger,
use_R4.1_masks = use_R4.1_masks,
png_device = png_device,
res = res,
default.units = default.units,
name = name,
gp = gp,
draw = draw,
vp = vp
)
}
# blank or solid lines return NULL
lty_pattern_str <- function(lty) {
if (is.numeric(lty)) {
lty <- c("blank", "solid", "dashed", "dotted", "dotdash", "longdash", "twodash")[lty + 1L]
}
switch(
lty,
blank = ,
solid = NULL,
dashed = "44",
dotted = "13",
dotdash = "1343",
longdash = "73",
twodash = "2262",
lty
)
}
# blank or solid lines return 0L
lty_period_sum <- function(lty) {
pat <- lty_pattern_str(lty)
if (is.null(pat)) {
return(0L)
}
sum(strtoi(strsplit(pat, "")[[1L]], base = 16L))
}
# Expand one row of a staggered pattern into explicit solid on-segments.
# phase_offset_lwd: phase in lwd units (may be fractional, e.g. 5.5 for dotdash half-period).
# Returns list(x0, x1, y) — equal-length vectors.
expand_lty_row <- function(lty, lwd, x_min, x_max, y_row, phase_offset_lwd) {
pat <- lty_pattern_str(lty)
if (is.null(pat)) {
if (lty_period_sum(lty) == 0L) {
return(list(x0 = x_min, x1 = x_max, y = y_row))
}
return(list(x0 = numeric(0L), x1 = numeric(0L), y = numeric(0L)))
}
units_lwd <- strtoi(strsplit(pat, "")[[1L]], base = 16L)
n <- length(units_lwd)
period_lwd <- sum(units_lwd)
phase <- phase_offset_lwd %% period_lwd
cumul_lwd <- c(0, cumsum(units_lwd))
seg_idx <- findInterval(phase, cumul_lwd[-length(cumul_lwd)])
offset_in_seg <- phase - cumul_lwd[seg_idx]
x0s <- numeric(0L)
x1s <- numeric(0L)
x_cur <- x_min
first <- TRUE
i <- seg_idx
while (x_cur < x_max) {
rem_lwd <- if (first) {
first <- FALSE
units_lwd[i] - offset_in_seg
} else {
units_lwd[i]
}
if (rem_lwd > 0) {
x_end <- min(x_cur + rem_lwd * lwd, x_max)
if (i %% 2L == 1L) {
x0s <- c(x0s, x_cur)
x1s <- c(x1s, x_end)
}
x_cur <- x_end
}
i <- i %% n + 1L
}
list(x0 = x0s, x1 = x1s, y = rep(y_row, length(x0s)))
}
# Build the segments grob for one set of lines (angle/lty/stagger already set in params).
# params must already be unit-converted (via convert_params_units).
# vpm must be from get_vp_measurements(default.units).
create_line_maskee <- function(params, vpm, default.units = "bigpts") {
grid_xy <- get_xy_grid(params, vpm)
col <- update_alpha(params$pattern_colour, params$pattern_alpha)
lwd <- params$pattern_linewidth * .pt
lty <- params$pattern_linetype
lineend <- params$pattern_lineend
gp <- gpar(col = col, lwd = lwd, lty = lty, lineend = lineend)
x0 <- rep(grid_xy$x_min, length(grid_xy$y))
x1 <- rep(grid_xy$x_max, length(grid_xy$y))
seg_y <- grid_xy$y
# Stagger alternating lines by half the dash period so that marks of
# adjacent lines interleave (heraldic convention). Explicit solid
# sub-segments are used so the phase is device-independent (relying on a
# segment start outside the viewport breaks dash phase on some devices).
if (isTRUE(params$pattern_stagger)) {
half_period_lwd <- lty_period_sum(lty) / 2
if (half_period_lwd > 0) {
segs <- lapply(seq_along(grid_xy$y), function(i) {
phase <- if (i %% 2L == 0L) half_period_lwd else 0
expand_lty_row(lty, lwd, grid_xy$x_min, grid_xy$x_max, grid_xy$y[i], phase)
})
x0_list <- lapply(segs, `[[`, "x0")
x0 <- unlist(x0_list, use.names = FALSE)
x1 <- unlist(lapply(segs, `[[`, "x1"), use.names = FALSE)
seg_y <- unlist(lapply(segs, `[[`, "y"), use.names = FALSE)
# Expand per-line gp values to per-sub-segment so recycling
# matches the non-stagger path (one gp value per line, not per dash).
n_sub <- lengths(x0_list)
line_idx <- rep(seq_along(grid_xy$y), n_sub)
recycle_to_lines <- function(v) v[(line_idx - 1L) %% length(v) + 1L]
gp <- gpar(
col = recycle_to_lines(col),
lwd = recycle_to_lines(lwd),
lineend = recycle_to_lines(lineend),
lty = "solid"
)
}
}
xy0 <- rotate_xy(x0, seg_y, params$pattern_angle, vpm$x, vpm$y)
xy1 <- rotate_xy(x1, seg_y, params$pattern_angle, vpm$x, vpm$y)
segmentsGrob(xy0$x, xy0$y, xy1$x, xy1$y, default.units = default.units, gp = gp)
}
create_pattern_line <- function(params, boundary_df, aspect_ratio, legend = FALSE) {
default.units <- "bigpts"
boundary_df <- convert_polygon_df_units(boundary_df, default.units)
params <- convert_params_units(params, default.units)
vpm <- get_vp_measurements(default.units)
maskee <- create_line_maskee(params, vpm, default.units)
masker <- convert_polygon_df_to_polygon_grob(
boundary_df,
default.units = default.units,
gp = gpar(fill = "white", col = NA, lwd = 0)
)
alphaMaskGrob(
maskee,
masker,
use_R4.1_masks = params$pattern_use_R4.1_masks,
png_device = params$pattern_png_device,
res = params$pattern_res,
name = "line"
)
}
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.