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,
7172# '
7273# ' @export
7374plot.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
283286swap_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 ))
0 commit comments