Skip to content

Commit 519e2d4

Browse files
committed
tidy multi-plot layout; add compact argument
1 parent a88e29c commit 519e2d4

File tree

4 files changed

+77
-52
lines changed

4 files changed

+77
-52
lines changed

NEWS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# version 1.0-15
22

3-
* `st_transform()` properly responds to in-session changes to `sf_proj_network()`; #2166
3+
* `st_transform()` responds to in-session changes to `sf_proj_network()`; #2166
44

55
* `plot.sf()`: `key.width` is sensitive to pointsize graphics parameter `par("ps")`; keys with factor levels suggest a proper size if they won't fit.
66

R/plot.R

Lines changed: 65 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#' @param extent object with an \code{st_bbox} method to define plot extent; defaults to \code{x}
2929
#' @param xlim numeric; x-axis limits; overrides \code{extent}
3030
#' @param ylim numeric; y-axis limits; overrides \code{extent}
31+
#' @param compact logical; compact sub-plots over plotting space?
3132
#' @method plot sf
3233
#' @name plot
3334
#' @details \code{plot.sf} maximally plots \code{max.plot} maps with colors following from attribute columns,
@@ -71,10 +72,10 @@
7172
#'
7273
#' @export
7374
plot.sf <- function(x, y, ..., main, pal = NULL, nbreaks = 10, breaks = "pretty",
74-
max.plot = if(is.null(n <- getOption("sf_max.plot"))) 9 else n,
75+
max.plot = getOption("sf_max.plot", default = 9),
7576
key.pos = get_key_pos(x, ...), key.length = .618, key.width = lcm(1.8 * par("ps")/12),
7677
reset = TRUE, logz = FALSE, extent = x, xlim = st_bbox(extent)[c(1,3)],
77-
ylim = st_bbox(extent)[c(2,4)]) {
78+
ylim = st_bbox(extent)[c(2,4)], compact = FALSE) {
7879

7980
stopifnot(missing(y))
8081
nbreaks.missing = missing(nbreaks)
@@ -83,16 +84,17 @@ plot.sf <- function(x, y, ..., main, pal = NULL, nbreaks = 10, breaks = "pretty"
8384
dots = list(...)
8485
col_missing = is.null(dots$col)
8586
breaks_numeric = is.numeric(breaks)
87+
reset_layout_needed = reset
8688

8789
x = swap_axes_if_needed(x)
8890

89-
opar = par()
91+
opar = par(no.readonly = TRUE)
9092
if (ncol(x) > 2 && !isTRUE(dots$add)) { # multiple maps to plot...
9193
cols = setdiff(names(x), attr(x, "sf_column"))
9294
lt = .get_layout(st_bbox(x), min(max.plot, length(cols)), par("din"), key.pos[1], key.width)
9395
if (key.pos.missing || key.pos == -1)
9496
key.pos = lt$key.pos
95-
layout(lt$m, widths = lt$widths, heights = lt$heights, respect = FALSE)
97+
layout(lt$m, widths = lt$widths, heights = lt$heights, respect = compact)
9698

9799
if (isTRUE(dots$axes))
98100
par(mar = c(2.1, 2.1, 1.2, 0))
@@ -157,11 +159,10 @@ plot.sf <- function(x, y, ..., main, pal = NULL, nbreaks = 10, breaks = "pretty"
157159
}
158160

159161
} else { # single map, or dots$add == TRUE:
160-
if (!isTRUE(dots$add) && reset)
161-
layout(matrix(1)) # reset
162-
if (ncol(x) == 1) # no attributes to choose colors from: plot geometry
162+
if (ncol(x) == 1) { # no attributes to choose colors from: plot geometry
163163
plot(st_geometry(x), xlim = xlim, ylim = ylim, ...)
164-
else { # generate plot with colors and possibly key
164+
reset_layout_needed = FALSE
165+
} else { # generate plot with colors and possibly key
165166
if (ncol(x) > 2) { # add = TRUE
166167
warning("ignoring all but the first attribute")
167168
x = x[,1]
@@ -247,7 +248,8 @@ plot.sf <- function(x, y, ..., main, pal = NULL, nbreaks = 10, breaks = "pretty"
247248
} else
248249
.image_scale(values, colors, breaks = breaks, key.pos = key.pos,
249250
key.length = key.length, logz = logz, ...)
250-
}
251+
} else
252+
reset_layout_needed = FALSE # as we didn't call layout()
251253
# plot the map:
252254
if (!isTRUE(dots$add)) {
253255
mar = c(1, 1, 1.2, 1)
@@ -273,11 +275,12 @@ plot.sf <- function(x, y, ..., main, pal = NULL, nbreaks = 10, breaks = "pretty"
273275
localTitle(main, ...)
274276
}
275277
}
276-
if (!isTRUE(dots$add) && reset && ncol(x) > 1) { # reset device:
277-
layout(matrix(1))
278-
desel = which(names(opar) %in% c("cin", "cra", "csi", "cxy", "din", "page", "fig"))
279-
par(opar[-desel])
280-
}
278+
if (!isTRUE(dots$add) && reset) { # reset device:
279+
if (reset_layout_needed)
280+
layout(matrix(1))
281+
par(opar)
282+
}
283+
invisible()
281284
}
282285

283286
swap_axes_if_needed = function(x) {
@@ -678,67 +681,84 @@ sf.colors = function (n = 10, cutoff.tails = c(0.35, 0.2), alpha = 1, categorica
678681
}
679682
}
680683

684+
# get the aspect ratio of a bounding box, for geodetic coords true scale at mid latitude:
685+
get_asp = function(bb) {
686+
asp = diff(bb[c(2,4)])/diff(bb[c(1,3)])
687+
if (!is.finite(asp)) # 0/0
688+
asp = 1
689+
if (isTRUE(st_is_longlat(bb)))
690+
asp = asp / cos(mean(bb[c(2,4)]) * pi /180)
691+
asp
692+
}
693+
694+
681695
#' @export
682696
#' @name stars
683697
#' @param bb ignore
684698
#' @param n ignore
685699
#' @param total_size ignore
700+
#' @param key.width ignore
686701
#' @param key.length ignore
687702
#' @param mfrow length-2 integer vector with number of rows, columns
688-
.get_layout = function(bb, n, total_size, key.pos, key.length, mfrow = NULL) {
703+
#' @param main main or sub title
704+
.get_layout = function(bb, n, total_size, key.pos, key.width, mfrow = NULL, main = NULL) {
689705
# return list with "m" matrix, "key.pos", "widths" and "heights" fields
690-
# if key.pos = -1, it will be a return value, "optimally" placed
691-
asp = diff(bb[c(2,4)])/diff(bb[c(1,3)])
692-
if (!is.finite(asp)) # 0/0
693-
asp = 1
694-
if (isTRUE(st_is_longlat(bb)))
695-
asp = asp / cos(mean(bb[c(2,4)]) * pi /180)
696-
if (is.null(mfrow)) {
697-
size = function(nrow, n, asp) {
698-
ncol = ceiling(n / nrow)
699-
xsize = total_size[1] / ncol
700-
ysize = xsize * asp
701-
if (xsize * ysize * n > prod(total_size)) {
702-
ysize = total_size[2] / nrow
703-
xsize = ysize / asp
704-
}
705-
xsize * ysize
706+
# if key.pos = -1 on input, it will be a return value, "optimally" placed
707+
asp = get_asp(bb)
708+
strip = if (is.character(main))
709+
# strheight(main, "inches")
710+
par("cin")[2]
711+
else
712+
0.0
713+
size = function(nrow, n, asp, strip = 0) { # given nrow n asp, what size does a single tile occupy?
714+
ncol = ceiling(n / nrow)
715+
xsize = total_size[1] / ncol
716+
ysize = xsize * asp + strip
717+
if (xsize * ysize * n > prod(total_size)) {
718+
ysize = total_size[2] / nrow - strip
719+
xsize = ysize / asp
706720
}
707-
sz = vapply(1:n, function(x) size(x, n, asp), 0.0)
708-
nrow = which.max(sz)
721+
c(xsize, ysize)
722+
}
723+
sz = vapply(1:n, function(nrow) size(nrow, n, asp, strip), c(0.0, 0.0))
724+
if (is.null(mfrow)) {
725+
nrow = which.max(apply(sz, 2, prod))
709726
ncol = ceiling(n / nrow)
710727
} else {
711728
stopifnot(is.numeric(mfrow), length(mfrow) == 2)
712729
nrow = mfrow[1]
713730
ncol = mfrow[2]
714731
}
732+
xsize = sz[1, nrow]
733+
ysize = sz[2, nrow]
734+
asp = ysize / xsize
715735

716736
ret = list()
717737
ret$mfrow = c(nrow, ncol)
718738

719739
# the following is right now only used by stars; FIXME:
720740
# nocov start
721741
ret$key.pos = if (!is.null(key.pos) && key.pos == -1L) { # figure out here: right or bottom?
722-
newasp = asp * ncol / nrow # of the composition
723-
dispasp = total_size[1] / total_size[2]
724-
ifelse(newasp > dispasp, 1, 4) # > or < ? oh dear,
742+
newasp = asp * nrow / ncol # of the composition
743+
dispasp = total_size[2] / total_size[1]
744+
ifelse(newasp > dispasp, 4, 1)
725745
} else
726746
key.pos
727747

728748
m = matrix(seq_len(nrow * ncol), nrow, ncol, byrow = TRUE)
729-
if (!is.null(ret$key.pos) && ret$key.pos != 0) {
730-
k = key.length
749+
if (!is.null(ret$key.pos) && ret$key.pos != 0) { # add key row or column:
750+
k = key.width
731751
n = nrow * ncol + 1
732752
switch(ret$key.pos,
733-
{ ret$m = rbind(m, n); ret$widths = c(rep(1, ncol)); ret$heights = c(rep(1, nrow), k) },
734-
{ ret$m = cbind(n, m); ret$widths = c(k, rep(1, ncol)); ret$heights = c(rep(1, nrow)) },
735-
{ ret$m = rbind(n, m); ret$widths = c(rep(1, ncol)); ret$heights = c(k, rep(1, nrow)) },
736-
{ ret$m = cbind(m, n); ret$widths = c(rep(1, ncol), k); ret$heights = c(rep(1, nrow)) }
753+
{ ret$m = rbind(m, n); ret$widths = c(rep(1, ncol)); ret$heights = c(rep(asp, nrow), k) },
754+
{ ret$m = cbind(n, m); ret$widths = c(k, rep(1, ncol)); ret$heights = c(rep(asp, nrow)) },
755+
{ ret$m = rbind(n, m); ret$widths = c(rep(1, ncol)); ret$heights = c(k, rep(asp, nrow)) },
756+
{ ret$m = cbind(m, n); ret$widths = c(rep(1, ncol), k); ret$heights = c(rep(asp, nrow)) }
737757
)
738758
} else {
739759
ret$m = m
740760
ret$widths = rep(1, ncol)
741-
ret$heights = rep(1, nrow)
761+
ret$heights = rep(asp, nrow)
742762
}
743763
# nocov end
744764
ret
@@ -816,7 +836,7 @@ xy_from_r = function(r, l, o) {
816836
cex.axis = par("cex.axis")) {
817837
if (!is.null(breaks) && length(breaks) != (length(col) + 1))
818838
stop("must have one more break than colour")
819-
stopifnot(is.character(lab) || is.expression(lab))
839+
stopifnot(is.null(lab) || is.character(lab) || is.expression(lab))
820840
lab_set = (is.character(lab) && lab != "") || is.expression(lab)
821841
zlim = range(z, na.rm = TRUE)
822842
if (is.null(breaks))
@@ -854,7 +874,7 @@ xy_from_r = function(r, l, o) {
854874

855875
plot(1, 1, t = "n", ylim = ylim, xlim = xlim, axes = FALSE,
856876
xlab = "", ylab = "", xaxs = "i", yaxs = "i")
857-
if (lab != "")
877+
if (!is.null(lab) && lab != "")
858878
mtext(lab, side = key.pos, line = 2.5, cex = .8)
859879
poly = vector(mode="list", length(col))
860880
for (i in seq(poly))

man/plot.Rd

Lines changed: 5 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/stars.Rd

Lines changed: 6 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)