Marching squares over a matrix, chained into polylines. Use it for density
isolines over a datashade() surface, level curves of a fitted model, or any
other gridded field.
Arguments
- z
A numeric matrix, with rows indexing x and columns indexing y —
dim(z) == c(length(x), length(y)), the same convention asgraphics::image(),graphics::contour()andgraphics::persp(), and the shapeouter(xs, ys, f)produces.- levels
Contour levels. Defaults to 5 levels spanning the finite range of
z, excluding the extremes (a contour at the minimum or maximum is either empty or the whole boundary).- xlim, ylim
The coordinate range the grid spans. Cell centres are placed at the ends of these ranges, matching
image().- contours
The data frame returned by
vl_contour().- close
Draw closed contours as closed rings.
TRUE(the default) joins the last point back to the first for contoursvl_contour()markedclosed; open contours are never closed.- gp, name, vp, role
Passed to each
lines_grob()the contours become.
Value
A data frame with one row per point: level, id (a distinct
contour line), x, y, and closed. Draw it with
lines_grob(x, y, id = id).
Details
Segments are chained into continuous lines rather than returned loose. That matters beyond tidiness: an unchained contour restarts its dash pattern in every grid cell, cannot be simplified or measured, and cannot be filled. Closed rings come back closed.
Cells with a non-finite corner are skipped, so a contour breaks around missing data rather than being drawn through it.
Examples
# A ring contour around a Gaussian bump.
# rows index x, columns index y -- exactly what `outer(xs, ys, f)` gives.
g <- outer(seq(-3, 3, length.out = 60), seq(-3, 3, length.out = 60),
function(x, y) exp(-(x^2 + y^2) / 2))
head(vl_contour(g, levels = c(0.2, 0.5, 0.8)))
#> level id x y closed
#> 1 0.2 1 0.4745763 0.2019822 TRUE
#> 2 0.2 1 0.4628971 0.2033898 TRUE
#> 3 0.2 1 0.4576271 0.2039628 TRUE
#> 4 0.2 1 0.4406780 0.2067596 TRUE
#> 5 0.2 1 0.4237288 0.2106261 TRUE
#> 6 0.2 1 0.4067797 0.2156898 TRUE
# Draw them: one grob per contour, because each is its own polyline.
# rows index x, columns index y -- exactly what `outer(xs, ys, f)` gives.
g <- outer(seq(-3, 3, length.out = 60), seq(-3, 3, length.out = 60),
function(x, y) exp(-(x^2 + y^2) / 2))
vl_scene(3, 3, dpi = 96, bg = "white") |>
push(vl_viewport(xscale = c(-3, 3), yscale = c(-3, 3))) |>
draw(contour_grob(vl_contour(g, levels = c(0.2, 0.5, 0.8)),
gp = vl_gpar(col = "steelblue"))) |>
pop() |>
(\(s) s)()
