From b4faa4f82ca8fef11b80b1ad5d67e9ad43ada605 Mon Sep 17 00:00:00 2001 From: mtennekes Date: Tue, 9 Apr 2024 17:25:20 +0200 Subject: [PATCH] improved continuous scaling, cartograms, and panels --- NAMESPACE | 3 + R/misc_other.R | 2 +- R/process_meta.R | 3 +- R/step2_helper_data.R | 16 ++-- R/step4_plot.R | 2 +- R/tm_layers_cartogram.R | 6 ++ R/tmapGridComp_leg_landscape.R | 16 ++-- R/tmapGridComp_leg_portrait.R | 18 ++-- R/tmapGridInit.R | 16 ++-- R/tmapGridWrap.R | 15 ++-- R/tmapGrid_misc.R | 2 +- R/tmapScaleContinuous.R | 148 +++++++++++++++++++++------------ R/tmapScaleRank.R | 6 +- R/tmapScale_defaults.R | 102 +++++++++++++++-------- R/tmap_options.R | 7 +- examples/tm_cartogram.R | 9 +- man/tm_cartogram.Rd | 36 ++++++-- man/tm_facets.Rd | 2 +- man/tm_shape.Rd | 4 + man/tm_text.Rd | 1 - 20 files changed, 267 insertions(+), 147 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index 7e184895..51cfe678 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -121,6 +121,9 @@ export(get_fact_levels_na) export(make_by_vars) export(marker_icon) export(opt_tm_bubbles) +export(opt_tm_cartogram) +export(opt_tm_cartogram_dorling) +export(opt_tm_cartogram_ncont) export(opt_tm_dots) export(opt_tm_labels) export(opt_tm_lines) diff --git a/R/misc_other.R b/R/misc_other.R index d543cc82..fa31e5b5 100644 --- a/R/misc_other.R +++ b/R/misc_other.R @@ -72,7 +72,7 @@ completeDT2 = function(DT, cols, defs = NULL){ } cont_breaks = function(breaks, n=101) { - x = round(seq(1, 101, length.out=length(breaks))) + x = round(seq(1, n, length.out=length(breaks))) unlist(lapply(1L:(length(breaks)-1L), function(i) { y = seq(breaks[i], breaks[i+1], length.out=x[i+1]-x[i]+1) diff --git a/R/process_meta.R b/R/process_meta.R index 979a066e..e40a9b46 100644 --- a/R/process_meta.R +++ b/R/process_meta.R @@ -40,8 +40,7 @@ preprocess_meta = function(o, cdt) { isdef = !sapply(fl, is.null) n = prod(nby) - - if (is.na(panel.type)) panel.type = ifelse(((type == "page" || n == 1) && is.na(panel.labels[[1]])) || ((type %in% c("wrap", "stack")) && !isdef[1]) || (!(type %in% c("wrap", "stack")) && !isdef[1] && !isdef[2]), "none", + if (is.na(panel.type)) panel.type = ifelse(((type == "page" || n == 1) && is.na(panel.labels[[1]])) || ((type %in% c("wrap", "stack")) && !isdef[1]) || (!(type %in% c("wrap", "stack")) && !isdef[1] && !isdef[2]) || !o$panel.show, "none", ifelse((type %in% c("wrap", "stack")) || (n == 1), "wrap", "xtab")) inner.margins = get_option_class(inner.margins, class = main_class) diff --git a/R/step2_helper_data.R b/R/step2_helper_data.R index effff2c5..083238e7 100644 --- a/R/step2_helper_data.R +++ b/R/step2_helper_data.R @@ -88,16 +88,16 @@ getdts = function(aes, unm, p, q, o, dt, shpvars, layer, mfun, args, plot.order) # active grouping variables (to keep) grp_bv = by123__[sort(c({if (nvars > 1) v else integer(0)}, b))] - sfun = paste0("tmapValuesScale_", unm) - cfun = paste0("tmapValuesColorize_", unm) + sfun = paste0("tmapValuesScale_", nm) + cfun = paste0("tmapValuesColorize_", nm) #print(vars) if (!aes$data_vars && !aes$geo_vars) { - # cat("step2_grp_lyr_aes_const", unm," \n") + #cat("step2_grp_lyr_aes_const", unm," \n") # constant values (take first value (of possible tm_mv per facet) if (any(nvari) > 1) warning("Aesthetic values considered as direct visual variables, which cannot be used with tm_mv", call. = FALSE) val1 = sapply(vars, "[[", 1, USE.NAMES = FALSE) - check_fun = paste0("tmapValuesCheck_", unm) + check_fun = paste0("tmapValuesCheck_", nm) check = do.call(check_fun, list(x = val1)) if (!check) { # to do: add "layer" name e.g. tm_fill is still "polygons" and not "fill" @@ -138,7 +138,7 @@ getdts = function(aes, unm, p, q, o, dt, shpvars, layer, mfun, args, plot.order) if (any(!dtl$sel__) || !q$drop.units) { # also needed for drop.units later on cls = data_class(dtl[[nm]]) - value.null = getAesOption("value.null", o, unm, layer, cls = cls) + value.null = getAesOption("value.null", o, nm, layer, cls = cls) value.null = do.call(sfun, list(x = value.null, scale = o$scale)) value.null = do.call(cfun, list(x = value.null, pc = o$pc)) @@ -162,6 +162,8 @@ getdts = function(aes, unm, p, q, o, dt, shpvars, layer, mfun, args, plot.order) dtl_leg = dtl[, .SD[1], by = c(grp_bv)][, tmapID__ := NULL][, legnr := (vapply(get(..nm), function(s) legend_save(list(vneutral = s)), FUN.VALUE = integer(1)))][, crtnr := (vapply(get(..nm), function(s) chart_save(list()), FUN.VALUE = integer(1)))][, (nm) := NULL] } else { + #cat("step2_grp_lyr_aes_var", nm," \n") + relevant_vars = c("tmapID__", "sel__" , vars, by123__[b]) dtl = copy(dt[, relevant_vars, with = FALSE]) @@ -271,12 +273,12 @@ getdts = function(aes, unm, p, q, o, dt, shpvars, layer, mfun, args, plot.order) #aesname = aes$aes value.null = if ("value.null" %in% names(s)) s$value.null else { - vn = getAesOption("value.null", o, unm, layer, cls = cls) + vn = getAesOption("value.null", o, nm, layer, cls = cls) vn = do.call(sfun, list(x = vn, scale = o$scale)) do.call(cfun, list(x = vn, pc = o$pc)) } - arglist = list(scale = s, legend = l, chart = crt, o = o, aes = unm, + arglist = list(scale = s, legend = l, chart = crt, o = o, aes = nm, layer = layer, layer_args = args, sortRev = sortRev, diff --git a/R/step4_plot.R b/R/step4_plot.R index 2ab8b2af..43f385e8 100644 --- a/R/step4_plot.R +++ b/R/step4_plot.R @@ -470,7 +470,7 @@ step4_plot = function(tm, vp, return.asp, show, knit, args) { # determine row and col ids - if (o$panel.type == "xtab") { + if (o$type == "grid") { d[, row := as.integer((i - 1) %% o$nrows + 1)] d[, col := as.integer((((i - 1) %/% o$nrows + 1) - 1) %% o$ncols + 1)] } else { diff --git a/R/tm_layers_cartogram.R b/R/tm_layers_cartogram.R index 91412795..e830b77d 100644 --- a/R/tm_layers_cartogram.R +++ b/R/tm_layers_cartogram.R @@ -1,5 +1,8 @@ #' @rdname tm_cartogram #' @name opt_tm_cartogram +#' @param type cartogram type, one of: "cont" for contiguous cartogram, "ncont" for non-contiguous cartogram and "dorling" for Dorling cartograms +#' @param itermax, maximum number of iterations (see [cartogram::cartogram_cont()]) +#' @param ... arguments passed on to [cartogram::cartogram_cont()] #' @export opt_tm_cartogram = function(type = "cont", itermax = 15, @@ -11,6 +14,8 @@ opt_tm_cartogram = function(type = "cont", #' @rdname tm_cartogram #' @name opt_tm_cartogram +#' @param expansion factor expansion, see [cartogram::cartogram_ncont()] (argument `k`) +#' @param inplace should each polygon be modified in its original place? (`TRUE` by default) #' @export opt_tm_cartogram_ncont = function(type = "ncont", expansion = 1, @@ -25,6 +30,7 @@ opt_tm_cartogram_ncont = function(type = "ncont", #' @rdname tm_cartogram #' @name opt_tm_cartogram +#' @param share share of the bounding box filled with the larger circle (see [cartogram::cartogram_dorling()] argument `k`) #' @export opt_tm_cartogram_dorling = function(type = "dorling", share = 5, diff --git a/R/tmapGridComp_leg_landscape.R b/R/tmapGridComp_leg_landscape.R index 70b3ba1a..f519ec64 100644 --- a/R/tmapGridComp_leg_landscape.R +++ b/R/tmapGridComp_leg_landscape.R @@ -256,15 +256,15 @@ tmapGridLegPlot.tm_legend_standard_landscape = function(comp, o, fH, fW) { id1 = which(!is.na(fill_list[[1]]))[1] id2 = tail(which(!is.na(fill_list[[nlev2]])), 1) - x1 = ((id1-1) / 10) / nlev2 - x2 = (id2 / 10) / nlev2 + ((nlev2-1)/nlev2) + x1 = ((id1-1) / o$nvv) / nlev2 + x2 = (id2 / o$nvv) / nlev2 + ((nlev2-1)/nlev2) w = x2 - x1 if (vary_fill) { - cols = unlist(fill_list)[id1:(10*(nlev2-1) + id2)] + cols = unlist(fill_list)[id1:(o$nvv*(nlev2-1) + id2)] cols_alph = paste0(cols, num_to_hex(gp$fill_alpha[1] * 255)) } else { - alph = unlist(alpha_list)[id1:(10*(nlev2-1) + id2)] + alph = unlist(alpha_list)[id1:(o$nvv*(nlev2-1) + id2)] cols_alph = paste0(col2hex(gp$fill[1]), num_to_hex(alph * 255)) } grItems1 = list(gridCell(6, comp$item_ids[lvs], grid::rectGrob(x = x1 + 0.5*w, width= w, gp=gpar(fill = grid::linearGradient(colours = cols_alph), col = NA)))) @@ -279,11 +279,11 @@ tmapGridLegPlot.tm_legend_standard_landscape = function(comp, o, fH, fW) { } if (vary_fill) { - x1 = (sum(is.na(fill_list[[1]])) * .1) / nlev2 - x2 = (sum(is.na(fill_list[[nlev2]])) * .1) / nlev2 + x1 = (sum(is.na(fill_list[[1]])) / o$nvv) / nlev2 + x2 = (sum(is.na(fill_list[[nlev2]])) / o$nvv) / nlev2 } else { - x1 = (sum(is.na(alpha_list[[1]])) * .1) / nlev2 - x2 = (sum(is.na(alpha_list[[nlev2]])) * .1) / nlev2 + x1 = (sum(is.na(alpha_list[[1]])) / o$nvv) / nlev2 + x2 = (sum(is.na(alpha_list[[nlev2]])) / o$nvv) / nlev2 } diff --git a/R/tmapGridComp_leg_portrait.R b/R/tmapGridComp_leg_portrait.R index 556e499a..2051e95c 100644 --- a/R/tmapGridComp_leg_portrait.R +++ b/R/tmapGridComp_leg_portrait.R @@ -7,7 +7,7 @@ tmapGridCompPrepare.tm_legend_standard_portrait = function(comp, o) { type = if ("biv" %in% names(attributes(gp$fill))) { "bivariate" - } else if (!is.na(gp$fill[1]) && any(nchar(gp$fill) > 50) || !is.na(gp$fill_alpha[1]) && any(nchar(gp$fill_alpha) > 50)) { + } else if (!is.na(gp$fill[1]) && any(nchar(gp$fill) > 20) || !is.na(gp$fill_alpha[1]) && any(nchar(gp$fill_alpha) > 20)) { "gradient" } else if (!is.na(gp$shape[1])) { "symbols" @@ -423,15 +423,15 @@ tmapGridLegPlot.tm_legend_standard_portrait = function(comp, o, fH, fW) { id1 = which(!is.na(fill_list[[1]]))[1] id2 = tail(which(!is.na(fill_list[[nlev2]])), 1) - y1 = 1 - ((id1-1) / 10) / nlev2 - y2 = 1 - ((id2 / 10) / nlev2 + ((nlev2-1)/nlev2)) + y1 = 1 - ((id1-1) / o$nvv) / nlev2 + y2 = 1 - ((id2 / o$nvv) / nlev2 + ((nlev2-1)/nlev2)) h = y1 - y2 if (vary_fill) { - cols = unlist(fill_list)[id1:(10*(nlev2-1) + id2)] + cols = unlist(fill_list)[id1:(o$nvv*(nlev2-1) + id2)] cols_alph = paste0(cols, num_to_hex(gp$fill_alpha[1] * 255)) } else { - alph = unlist(alpha_list)[id1:(10*(nlev2-1) + id2)] + alph = unlist(alpha_list)[id1:(o$nvv*(nlev2-1) + id2)] cols_alph = paste0(col2hex(gp$fill[1]), num_to_hex(alph * 255)) } grItems1 = list(gridCell(comp$item_ids[lvs], 3, grid::rectGrob(y = y2 + 0.5*h, height= h, gp=gpar(fill = grid::linearGradient(colours = rev(cols_alph)), col = NA)))) @@ -448,11 +448,11 @@ tmapGridLegPlot.tm_legend_standard_portrait = function(comp, o, fH, fW) { if (vary_fill) { - y1 = (sum(is.na(fill_list[[1]])) * .1) / nlev2 - y2 = (sum(is.na(fill_list[[nlev2]])) * .1) / nlev2 + y1 = (sum(is.na(fill_list[[1]])) /o$nvv) / nlev2 + y2 = (sum(is.na(fill_list[[nlev2]])) /o$nvv) / nlev2 } else { - y1 = (sum(is.na(alpha_list[[1]])) * .1) / nlev2 - y2 = (sum(is.na(alpha_list[[nlev2]])) * .1) / nlev2 + y1 = (sum(is.na(alpha_list[[1]])) /o$nvv) / nlev2 + y2 = (sum(is.na(alpha_list[[nlev2]])) /o$nvv) / nlev2 } grItems2 = list(gridCell(comp$item_ids[lvs], 3, rndrectGrob(y = grid::unit(y2, "npc"), just = c("center", "bottom"), height = grid::unit(1-(y1+y2), "npc"), gp = gpars, r = comp$item.r))) diff --git a/R/tmapGridInit.R b/R/tmapGridInit.R index 05a0b24c..b2d0db89 100644 --- a/R/tmapGridInit.R +++ b/R/tmapGridInit.R @@ -12,8 +12,8 @@ tmapGridInit = function(o, return.asp = FALSE, vp) { grid.buffers.top = grid.buffers[3], grid.margins.top = grid.margins[3], - {if (o$nrows > 1) rep(c(panel.wrap.size[3], 0, panel.wrap.size[1], between.marginH), o$nrows -1) else NULL}, - panel.wrap.size[3], 0, panel.wrap.size[1], + {if (o$nrows > 1) rep(c(panel.wrap.size[3], panel.wrap.margin[3], 0, panel.wrap.margin[1], panel.wrap.size[1], between.marginH), o$nrows -1) else NULL}, + panel.wrap.size[3], panel.wrap.margin[3], 0, panel.wrap.margin[1], panel.wrap.size[1], grid.margins.bottom = grid.margins[1], grid.buffers.bottom = grid.buffers[1], @@ -43,8 +43,8 @@ tmapGridInit = function(o, return.asp = FALSE, vp) { grid.buffers.left = grid.buffers[2], grid.margins.left = grid.margins[2], - {if (o$ncols > 1) rep(c(panel.wrap.size[2], 0, panel.wrap.size[4], between.marginW), o$ncols -1) else NULL}, - panel.wrap.size[2], 0, panel.wrap.size[4], + {if (o$ncols > 1) rep(c(panel.wrap.size[2], panel.wrap.margin[2], 0, panel.wrap.margin[4], panel.wrap.size[4], between.marginW), o$ncols -1) else NULL}, + panel.wrap.size[2], panel.wrap.margin[2], 0, panel.wrap.margin[4], panel.wrap.size[4], grid.margins.left = grid.margins[4], grid.buffers.left = grid.buffers[4], @@ -65,8 +65,8 @@ tmapGridInit = function(o, return.asp = FALSE, vp) { nr = length(rows) nc = length(cols) - cols_facet_ids = 1:o$ncols * 4 + 7 - rows_facet_ids = 1:o$nrows * 4 + 7 + cols_facet_ids = 1:o$ncols * 6 + 6 + rows_facet_ids = 1:o$nrows * 6 + 6 #if (o$panel.type == "xtab") { cols_panel_col_ids = cols_facet_ids @@ -75,8 +75,8 @@ tmapGridInit = function(o, return.asp = FALSE, vp) { rows_panel_row_ids = rows_facet_ids rows_panel_col_id = ifelse(o$panel.xtab.pos[1] == "left", 6, nc - 5) #} else if (o$panel.type == "wrap") { - cols_panel_ids = cols_facet_ids + ifelse(o$panel.wrap.pos == "left", -1, ifelse(o$panel.wrap.pos == "right", 1, 0)) - rows_panel_ids = rows_facet_ids + ifelse(o$panel.wrap.pos == "top", -1, ifelse(o$panel.wrap.pos == "bottom", 1, 0)) + cols_panel_ids = cols_facet_ids + ifelse(o$panel.wrap.pos == "left", -2, ifelse(o$panel.wrap.pos == "right", 2, 0)) + rows_panel_ids = rows_facet_ids + ifelse(o$panel.wrap.pos == "top", -2, ifelse(o$panel.wrap.pos == "bottom", 2, 0)) panel_col_rot = 0 panel_row_rot = ifelse(o$panel.xtab.pos[1] == "left", 90, 270) diff --git a/R/tmapGridWrap.R b/R/tmapGridWrap.R index 352be3a1..84642b96 100644 --- a/R/tmapGridWrap.R +++ b/R/tmapGridWrap.R @@ -8,21 +8,26 @@ tmapGridWrap = function(label, facet_row, facet_col, facet_page, o) { row = g$rows_panel_ids[facet_row] col = g$cols_panel_ids[facet_col] - frame.col = if (isFALSE(o$frame)) o$attr.color else if (isTRUE(o$frame)) o$attr.color else o$frame + frame.col = if (isFALSE(o$panel.label.frame)) o$attr.color else if (isTRUE(o$panel.label.frame)) o$attr.color else o$panel.label.frame + + frame.show = !isFALSE(o$panel.label.frame) #scale = o$scale * o$scale_down - gpar_rect = grid::gpar(fill = o$panel.label.bg.color, lwd=o$frame.lwd * o$scale, col = o$frame) + if (frame.show) { + gpar_rect = grid::gpar(fill = o$panel.label.bg.color, lwd=o$panel.label.frame.lwd * o$scale, col = frame.col) + } gpar_text = rescale_gp(grid::gpar(cex = o$panel.label.size * o$scale, col = o$panel.label.color, fontfamily = o$panel.label.fontfamily, fontface = o$panel.label.fontface), o$scale_down) # resize due to not fitting gpar_text$cex = determine_scale(label = label, rot = rot, row = row, col = col, g = g, scale = gpar_text$cex) grb = grid::grobTree( - rndrectGrob(gp = gpar_rect, r = o$frame.r * o$scale), + if (frame.show) { + rndrectGrob(gp = gpar_rect, r = o$panel.label.frame.r * o$scale) + } else NULL, grid::textGrob(label = label, rot = rot, gp = gpar_text) ) - - + gt = add_to_gt(gt, grb, row = row, col = col) gts[[facet_page]] = gt diff --git a/R/tmapGrid_misc.R b/R/tmapGrid_misc.R index e5efb028..ebb6836a 100644 --- a/R/tmapGrid_misc.R +++ b/R/tmapGrid_misc.R @@ -74,7 +74,7 @@ gp_to_gpar = function(gp, id = NULL, sel = "all", split_to_n = NULL, pick_middle if (pick_middle) { x = sapply(x, function(i) { if (all(is.na(i))) NA else { - sq = c(5,6,4,7,3,8,2,9,1,10) # priority for middle values + sq = (o$nvv/2) + (rep(0:5,each=2) * c(1,-1))[-1] # priority for middle values i[sq[which(!is.na(i)[sq])[1]]] } }) diff --git a/R/tmapScaleContinuous.R b/R/tmapScaleContinuous.R index ceab4ec7..8b78bfc1 100644 --- a/R/tmapScaleContinuous.R +++ b/R/tmapScaleContinuous.R @@ -36,12 +36,18 @@ tmapScaleContinuous = function(x1, scale, legend, chart, o, aes, layer, layer_ar warning(maincls, " is supposed to be applied to numerical data", call. = FALSE) } + + + x1 = without_units(x1) if (aes %in% c("lty", "shape", "pattern")) stop("tm_scale_continuous cannot be used for layer ", layer, ", aesthetic ", aes, call. = FALSE) scale = get_scale_defaults(scale, o, aes, layer, cls) + vnum = (is.numeric(scale$values) || inherits(scale$values, "tmapSeq")) + #vnum=FALSE + show.messages <- o$show.messages show.warnings <- o$show.warnings @@ -108,25 +114,28 @@ tmapScaleContinuous = function(x1, scale, legend, chart, o, aes, layer, layer_ar limits_t = tr$fun(limits) domain_t = tr$fun(tr$domain) - breaks = cont_breaks(limits_t, n=101) - - if (is.null(labels)) { - ncont = n - } else { - if (ticks.specified && length(labels) != n+1) { - if (show.warnings) warning("The length of legend labels is ", length(labels), ", which differs from the length of the breaks (", (n+1), "). Therefore, legend labels will be ignored", call.=FALSE) - labels = NULL + if (!vnum) { + breaks = cont_breaks(limits_t, n=o$precision) + if (is.null(labels)) { + ncont = n } else { - ncont = length(labels) + if (ticks.specified && length(labels) != n+1) { + if (show.warnings) warning("The length of legend labels is ", length(labels), ", which differs from the length of the breaks (", (n+1), "). Therefore, legend labels will be ignored", call.=FALSE) + labels = NULL + } else { + ncont = length(labels) + } } + q = num2breaks(x = x_t, n = o$precision, style = "fixed", breaks=breaks, approx=TRUE, interval.closure = "left", var=paste(layer, aes, sep = "-"), args = list()) + + breaks = q$brks + nbrks = length(breaks) + n2 = nbrks - 1 + } - q = num2breaks(x = x_t, n = 101, style = "fixed", breaks=breaks, approx=TRUE, interval.closure = "left", var=paste(layer, aes, sep = "-"), args = list()) - breaks = q$brks - nbrks = length(breaks) - n2 = nbrks - 1 - int.closure <- attr(q, "intervalClosure") + # update range if NA (automatic) if (is.na(values.range[1])) { @@ -150,28 +159,49 @@ tmapScaleContinuous = function(x1, scale, legend, chart, o, aes, layer, layer_ar if (show.messages) message("Variable(s) \"", paste(aes, collapse = "\", \""), "\" contains positive and negative values, so midpoint is set to 0. Set midpoint = NA to show the full range of visual values.") midpoint <- 0 } else { - if ((n2 %% 2) == 1) { - # number of classes is odd, so take middle class (average of those breaks) - midpoint <- mean.default(breaks[c((n2+1) / 2, (n2+3) / 2)]) + if (vnum) { + midpoint = mean(limits_t) } else { - midpoint <- breaks[(n2+2) / 2] + if ((n2 %% 2) == 1) { + # number of classes is odd, so take middle class (average of those breaks) + midpoint <- mean.default(breaks[c((n2+1) / 2, (n2+3) / 2)]) + } else { + midpoint <- breaks[(n2+2) / 2] + } } } } fun_getVV = paste0("tmapValuesVV_", aes) - VV = do.call(fun_getVV, list(x = values, value.na = value.na, isdiv = isdiv, n = n2, dvalues = breaks, midpoint = midpoint, range = values.range, scale = values.scale * o$scale, are_breaks = TRUE, rep = values.repeat, o = o)) - - vvalues = VV$vvalues - value.na = VV$value.na - - sfun = paste0("tmapValuesScale_", aes) - cfun = paste0("tmapValuesColorize_", aes) - if (is.na(value.neutral)) value.neutral = VV$value.neutral else value.neutral = do.call(sfun, list(x = do.call(cfun, list(x = value.neutral, pc = o$pc)), scale = values.scale)) + if (!vnum) { + #### discretisize + + # number of visual values in legend item (belonging to one label) + nvv = o$nvv + + + VV = do.call(fun_getVV, list(x = values, value.na = value.na, isdiv = isdiv, n = n2, dvalues = breaks, midpoint = midpoint, range = values.range, scale = values.scale * o$scale, are_breaks = TRUE, rep = values.repeat, o = o)) + + vv = VV$vvalues + value.na = VV$value.na + + sfun = paste0("tmapValuesScale_", aes) + cfun = paste0("tmapValuesColorize_", aes) + if (is.na(value.neutral)) value.neutral = VV$value.neutral else value.neutral = do.call(sfun, list(x = do.call(cfun, list(x = value.neutral, pc = o$pc)), scale = values.scale)) + + ids = classInt::findCols(q) + vals = vv[ids] + } else { + if (is.numeric(values)) { + values = tmap_seq(values[1], values[length(values)], power = "lin") + } + VV = transform_values(x_t, limits_t, values.range, values$power, values.scale * o$scale) + + vals = VV$x + value.neutral = VV$neutral + } - ids = classInt::findCols(q) - vals = vvalues[ids] isna = is.na(vals) anyNA = any(isna) @@ -182,7 +212,13 @@ tmapScaleContinuous = function(x1, scale, legend, chart, o, aes, layer, layer_ar } else if (is.na(sortRev)) { ids[] = 1L } else if (sortRev) { - ids = (as.integer(n2) + 1L) - ids + if (vnum) { + ids = rank(-vals) + } else { + ids = (as.integer(n2) + 1L) - ids + } + } else if (vnum) { + ids = rank(vals) } if (anyNA) { @@ -194,49 +230,55 @@ tmapScaleContinuous = function(x1, scale, legend, chart, o, aes, layer, layer_ar b_t = ticks_t b = tr$rev(b_t) } else { -# tr$rev(pretty(limits_t, n = 10)) - # TODO - #pretty() - #tr$fun(round(tr$rev(pretty(limits_t)),1)) b = prettyTicks(tr$rev(seq(limits_t[1], limits_t[2], length.out = n))) if (!(aes %in% c("col", "fill"))) b = b[b!=0] - b_t = tr$fun(b) } sel = if (length(b_t) == 2) TRUE else (b_t>=limits_t[1] & b_t<=limits_t[2]) b_t = b_t[sel] b = b[sel] + + nbrks_cont <- length(b_t) - id = as.integer(cut(b_t, breaks=breaks, include.lowest = TRUE)) - id_step = id[-1] - head(id, -1) - id_step = c(id_step[1], id_step, id_step[length(id_step)]) - id_lst = mapply(function(i, s1, s2){ - #res = round(seq(i-floor(id_step/2), i+ceiling(id_step/2), length.out=11))[1:10] - res1 = round(seq(i-floor(s1/2), i, length.out=6)) - res2 = round(seq(i, i+ceiling(s2/2), length.out=6))[2:5] - res = c(res1, res2) - res[res<1 | res>101] = NA - res - }, id, head(id_step, -1), id_step[-1], SIMPLIFY = FALSE) - vvalues = lapply(id_lst, function(i) { - if (legend$reverse) rev(vvalues[i]) else vvalues[i] - }) + if (vnum) { + vvalues = transform_values(b_t, limits_t, values.range, values$power, values.scale * o$scale, include.neutral = FALSE) + } else { + id = as.integer(cut(b_t, breaks=breaks, include.lowest = TRUE)) + id_step = id[-1] - head(id, -1) + id_step = c(id_step[1], id_step, id_step[length(id_step)]) + id_lst = mapply(function(i, s1, s2){ + #res = round(seq(i-floor(id_step/2), i+ceiling(id_step/2), length.out=11))[1:10] + res1 = round(seq(i-floor(s1/2), i, length.out=(nvv/2)+1L)) + res2 = round(seq(i, i+ceiling(s2/2), length.out=(nvv/2)+1L))[2:(nvv/2)] + res = c(res1, res2) + res[res<1 | res>o$precision] = NA + res + }, id, head(id_step, -1), id_step[-1], SIMPLIFY = FALSE) + vvalues = lapply(id_lst, function(i) { + if (legend$reverse) rev(vv[i]) else vv[i] + }) + } + + + + + if (legend$reverse) vvalues = rev(vvalues) if (na.show) vvalues = c(vvalues, value.na) # temporarily stack gradient values - vvalues = cont_collapse(vvalues) + if (!vnum) vvalues = cont_collapse(vvalues) # create legend values values = b # create legend labels for continuous cases if (is.null(labels)) { - labels = do.call("fancy_breaks", c(list(vec=b, as.count = FALSE, intervals=FALSE, interval.closure=int.closure), label.format)) + labels = do.call("fancy_breaks", c(list(vec=b, as.count = FALSE, intervals=FALSE, interval.closure="left"), label.format)) } else { labels = rep(labels, length.out=nbrks_cont) attr(labels, "align") <- label.format$text.align @@ -254,7 +296,6 @@ tmapScaleContinuous = function(x1, scale, legend, chart, o, aes, layer, layer_ar } - legend = within(legend, { nitems = length(labels) labels = labels @@ -267,9 +308,8 @@ tmapScaleContinuous = function(x1, scale, legend, chart, o, aes, layer, layer_ar limits = limits }) # NOTE: tr and limits are included in the output to facilitate the transformation of the leaflet continuous legend ticks (https://github.com/rstudio/leaflet/issues/665) - - vvalues_mids = sapply(cont_split(vvalues), "[", 5) - vvalues_mids[vvalues_mids == "NA"] = NA + #vvalues_mids = sapply(cont_split(vvalues), "[", nvv/2) + #vvalues_mids[vvalues_mids == "NA"] = NA chartFun = paste0("tmapChart", toTitleCase(chart$summary)) diff --git a/R/tmapScaleRank.R b/R/tmapScaleRank.R index a6eaba90..9a293a38 100644 --- a/R/tmapScaleRank.R +++ b/R/tmapScaleRank.R @@ -32,7 +32,7 @@ tmapScaleRank = function(x1, scale, legend, chart, o, aes, layer, layer_args, so } limits = range(x1) - breaks = cont_breaks(limits, n=101) + breaks = cont_breaks(limits, n=o$precision) if (is.null(labels)) { ncont = n @@ -44,7 +44,7 @@ tmapScaleRank = function(x1, scale, legend, chart, o, aes, layer, layer_args, so ncont = length(labels) } } - q = num2breaks(x = x1, n = 101, style = "fixed", breaks=breaks, approx=TRUE, interval.closure = "left", var=paste(layer, aes, sep = "-"), args = list()) + q = num2breaks(x = x1, n = o$precision, style = "fixed", breaks=breaks, approx=TRUE, interval.closure = "left", var=paste(layer, aes, sep = "-"), args = list()) breaks = q$brks nbrks = length(breaks) @@ -115,7 +115,7 @@ tmapScaleRank = function(x1, scale, legend, chart, o, aes, layer, layer_args, so res1 = round(seq(i-floor(s1/2), i, length.out=6)) res2 = round(seq(i, i+ceiling(s2/2), length.out=6))[2:5] res = c(res1, res2) - res[res<1 | res>101] = NA + res[res<1 | res>o$precision] = NA res }, id, head(id_step, -1), id_step[-1], SIMPLIFY = FALSE) vvalues = lapply(id_lst, function(i) { diff --git a/R/tmapScale_defaults.R b/R/tmapScale_defaults.R index 00968fcf..733caeec 100644 --- a/R/tmapScale_defaults.R +++ b/R/tmapScale_defaults.R @@ -31,6 +31,10 @@ tmapValuesCheck_size = function(x) { inherits(x, "tmapSeq") || (is.numeric(x) && (all(x>=0) || all(x<=0))) } +tmapValuesCheck_area = function(x) { + tmapValuesCheck_size(x) +} + tmapValuesCheck_lwd = function(x) { tmapValuesCheck_size(x) } @@ -99,6 +103,10 @@ tmapValuesIsDiv_size = function(x) { inherits(x, "tmapSeq") && (x$from < 0) && (x$to > 1) || (is.numeric(x) && (any(x < 0) && any(x> 0))) } +tmapValuesIsDiv_area = function(x) { + tmapValuesIsDiv_size(x) +} + tmapValuesIsDiv_lwd = function(x) { tmapValuesIsDiv_size(x) } @@ -173,9 +181,15 @@ tmapValuesRange_lty = function(x, n, isdiv) { tmapValuesRange_size = function(x, n, isdiv) { #print(c(.5/n, 1 - .5/n)) - c(.5/n, 1 - .5/n) +# c(.5/n, 1 - .5/n) + c(0, 1) +} + +tmapValuesRange_area = function(x, n, isdiv) { + tmapValuesRange_size(x, n, isdiv) } + tmapValuesRange_lwd = function(x, n, isdiv) { tmapValuesRange_size(x, n, isdiv) } @@ -264,7 +278,7 @@ tmapValuesVV_fill = function(x, value.na, isdiv, n, dvalues, are_breaks, midpoin } else { ids_after_range = map_ids(ids_scaled[c(1L, ntot)], range, ntot) } - vvalues = grDevices::colorRampPalette(x)(101)[ids_after_range] + vvalues = grDevices::colorRampPalette(x)(o$precision)[ids_after_range] } else { vvalues = grDevices::colorRampPalette(x)(ntot)[ids] } @@ -305,10 +319,6 @@ tmapValuesVV_lty = function(x, value.na, isdiv, n, dvalues, are_breaks, midpoint tmapValuesVV_size = function(x, value.na, isdiv, n, dvalues, are_breaks, midpoint, range, scale, rep, o) { - #break_mids = breaks[-(n+1)] + (breaks[-1] - breaks[-(n+1)]) / 2 - - - #vvalues = seq(x[1], x[2]) vvalues = if (is.numeric(x) && length(x) == n) { if (range[1] !=0 || range[2] != 1) { warning("values.range not used because the individual values have been specified (instead of a sequence)", call. = FALSE) @@ -320,35 +330,25 @@ tmapValuesVV_size = function(x, value.na, isdiv, n, dvalues, are_breaks, midpoin } if (range[1] !=0 || range[2] != 1) { - range_curved = tmapSeq(tmap_seq(from = range[1], to = range[2], power = x$power), n = 2, rescale = FALSE) - x$from = x$from + range_curved[1] * (x$to - x$from) - x$to = x$from + range_curved[2] * (x$to - x$from) + p = if (is.numeric(x$power)) x$power else switch(x$power, lin = 1, sqrt = 0.5, sqrt_perceptual = 0.5716, quadratic = 2) + x$from = range[1] ^ (1/p) + x$to = range[2] ^ (1/p) } tmapSeq(x, n) } - - # (inherits(x, "tmapSeq")) { - # tmapSeq(x, n) - # } else if (length(x) == n) { - # x - # } else { - # seq(x[1], x[n], length.out = n) - # } - # - # if (isdiv) { - # colpal = seq(x[1], x[2], length.out = 1001)[map2divscaleID(breaks - midpoint, n=1001, range=range)] - # } else { - # #colpal = seq(values[1], values[2], length.out = n) #seq(palette[1], palette[2], length.out = 1001)[map2seqscaleID(breaks, n=1001, range=range, breaks.specified=breaks.specified, show.warnings = show.warnings)] - # colpal = seq(x[1], x[2], length.out = 1001)[map2seqscaleID(breaks, n = 1001, range = range)] - # } - # vvalues = colpal value.neutral = vvalues[round((n+1)/2)] - - list(vvalues = vvalues * scale, value.neutral = value.neutral * scale, value.na = value.na * scale) } + + + +tmapValuesVV_area = function(...) { + do.call(tmapValuesVV_size, args = list(...)) +} + + tmapValuesVV_lwd = function(...) { do.call(tmapValuesVV_size, args = list(...)) } @@ -390,6 +390,7 @@ tmapValuesSubmit_col = function(x, args) x tmapValuesSubmit_fill = function(x, args) x tmapValuesSubmit_bgcol = function(x, args) x tmapValuesSubmit_size = function(x, args) x +tmapValuesSubmit_area = function(x, args) x tmapValuesSubmit_xmod = function(x, args) x tmapValuesSubmit_ymod = function(x, args) x tmapValuesSubmit_angle = function(x, args) x @@ -432,6 +433,7 @@ tmapValuesScale_col = function(x, scale) x tmapValuesScale_fill = function(x, scale) x tmapValuesScale_bgcol = function(x, scale) x tmapValuesScale_size = function(x, scale) x * scale +tmapValuesScale_area = function(x, scale) x tmapValuesScale_lwd = function(x, scale) x * scale tmapValuesScale_lty = function(x, scale) x tmapValuesScale_shape = function(x, scale) x @@ -447,6 +449,7 @@ tmapValuesColorize_col = function(x, pc) do.call(process_color, c(list(col = x), tmapValuesColorize_fill = function(x, pc) do.call(process_color, c(list(col = x), pc)) tmapValuesColorize_bgcol = function(x, pc) do.call(process_color, c(list(col = x), pc)) tmapValuesColorize_size = function(x, pc) x +tmapValuesColorize_area = function(x, pc) x tmapValuesColorize_lwd = function(x, pc) x tmapValuesColorize_lty = function(x, pc) x tmapValuesColorize_shape = function(x, pc) x @@ -463,20 +466,47 @@ tmap_seq = function(from = 0, to = 1, power = c("lin", "sqrt", "sqrt_perceptual" structure(as.list(environment()), class = "tmapSeq") } -tmapSeq = function(s, n, rescale = TRUE) { +# x is vector, rng is its range +norm_vector = function(x, rng) { + (x - rng[1]) / diff(rng) +} + +scale_vector = function(x, new_rng) { + (x + new_rng[1]) * diff(new_rng) +} + + +tmapSeq = function(s, n = NULL) { + if (is.null(n) && is.null(s$values)) stop("One of n or s$values should be provided") + if (is.null(s$values)) s["values"] = list(NULL) with(s, { p = if (is.numeric(power)) power else switch(power, lin = 1, sqrt = 0.5, sqrt_perceptual = 0.5716, quadratic = 2) - #if (is.null(p)) p = as.numeric() r = seq(from = from, to = to, length.out = n) ^ p - if (rescale) { - (r - (r[1])) / (r[n] - r[1]) * (to - from) + from - } else { - r - } }) } +transform_values = function(x, lim, rng, power, scale, include.neutral = TRUE) { + p = if (is.numeric(power)) power else switch(power, lin = 1, sqrt = 0.5, sqrt_perceptual = 0.5716, quadratic = 2) + if (p != 1) rng = rng ^ (1/p) + + x2 = norm_vector(x, lim) + x3 = if (rng[1] != 0 || rng[2] != 1) scale_vector(x2, rng) else x2 + if (include.neutral) neutral = mean(x3) + if (p != 1) { + x3 = x3 ^ p + if (include.neutral) neutral = neutral ^ p + } + x4 = x3 * scale + if (include.neutral) neutral = neutral * scale + + if (include.neutral) { + list(x = x4, + neutral = neutral) + } else { + x4 + } +} @@ -532,6 +562,10 @@ tmapValuesCVV_size = function(x, value.na, n, range, scale, rep, o) { tmapValuesVV_size(x = x, value.na = value.na, isdiv = FALSE, n = n, dvalues = NA, are_breaks = FALSE, midpoint = NA, range = range, scale = scale, rep = rep) } +tmapValuesCVV_area = function(x, value.na, n, range, scale, rep, o) { + tmapValuesVV_area(x = x, value.na = value.na, isdiv = FALSE, n = n, dvalues = NA, are_breaks = FALSE, midpoint = NA, range = range, scale = scale, rep = rep) +} + tmapValuesCVV_lwd = function(x, value.na, n, range, scale, rep, o) { tmapValuesVV_lwd(x = x, value.na = value.na, isdiv = FALSE, n = n, dvalues = NA, are_breaks = FALSE, midpoint = NA, range = range, scale = scale, rep = rep) } diff --git a/R/tmap_options.R b/R/tmap_options.R index 45c80b2f..a8f063cd 100644 --- a/R/tmap_options.R +++ b/R/tmap_options.R @@ -174,6 +174,8 @@ unit = "rank")), # NA means take data range, 0 means include 0 + nvv = 50, # the number of continuous legend breaks within one 'unit' (label). Should be even + precision = 101, # the number of classes of a continuous scale. Should be oddÃ’ # labels label.format = list( @@ -524,13 +526,16 @@ mouse_coordinates.position = tm_pos_in(pos.h = "right", pos.v = "bottom", align.h = "left", align.v = "top", just.h = "left", just.v = "bottom"), mouse_coordinates.show = FALSE, - panel.show = NA, + panel.show = TRUE, panel.labels = NA, panel.label.size = 1, panel.label.color = "black", panel.label.fontface = NULL, panel.label.fontfamily = NULL, panel.label.bg.color = "grey80", + panel.label.frame = TRUE, + panel.label.frame.lwd = 1, + panel.label.frame.r = 2, panel.label.height = 1, panel.label.rot = c(90, 0), diff --git a/examples/tm_cartogram.R b/examples/tm_cartogram.R index 2cea63d7..852863ba 100644 --- a/examples/tm_cartogram.R +++ b/examples/tm_cartogram.R @@ -14,10 +14,15 @@ tm_shape(World, crs = "+proj=robin") + tm_polygons() + tm_cartogram_ncont(size = "pop_est", fill = "yellow") - +library(cartogram) # to do: make output like this: -W = cartogram_ncont(World |> st_transform("+proj=robin"), weight = "pop_est") +W = cartogram_ncont(World |> st_transform("+proj=robin"), weight = "pop_est", k = 1) tm_shape(World, crs = "+proj=robin") + tm_polygons() + tm_shape(W) + tm_polygons(fill = "yellow") + + +tm_shape(metro) + tm_bubbles(size = "pop2020", size.scale = tm_scale_continuous(values.range = c(0.4,1))) + +# tmapSeq() extend $values. To do: check if they need to be within [from, to] \ No newline at end of file diff --git a/man/tm_cartogram.Rd b/man/tm_cartogram.Rd index 9d28842d..a896f180 100644 --- a/man/tm_cartogram.Rd +++ b/man/tm_cartogram.Rd @@ -1,9 +1,18 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/tm_layers_cartogram.R -\name{tm_cartogram} +\name{opt_tm_cartogram} +\alias{opt_tm_cartogram} +\alias{opt_tm_cartogram_ncont} +\alias{opt_tm_cartogram_dorling} \alias{tm_cartogram} \title{Map layer: cartogram} \usage{ +opt_tm_cartogram(type = "cont", itermax = 15, ...) + +opt_tm_cartogram_ncont(type = "ncont", expansion = 1, inplace = FALSE, ...) + +opt_tm_cartogram_dorling(type = "dorling", share = 5, itermax = 1000, ...) + tm_cartogram( size = 1, size.scale = tm_scale(), @@ -11,18 +20,14 @@ tm_cartogram( size.chart = tm_chart_none(), size.free = NA, plot.order = tm_plot_order("size", reverse = FALSE), - trans.args = list(type = "cont", itermax = 15), + options = opt_tm_cartogram(), ... ) } \arguments{ -\item{size, size.scale, size.legend, size.chart, size.free}{Transformation variable that -determines the size of the polygons.} +\item{type}{cartogram type, one of: "cont" for contiguous cartogram, "ncont" for non-contiguous cartogram and "dorling" for Dorling cartograms} -\item{plot.order}{Specification in which order the spatial features are drawn. -See \code{\link[=tm_plot_order]{tm_plot_order()}} for details.} - -\item{trans.args}{lists that are passed on to internal transformation function} +\item{itermax, }{maximum number of iterations (see \code{\link[cartogram:cartogram_cont]{cartogram::cartogram_cont()}})} \item{...}{ Arguments passed on to \code{\link[=tm_polygons]{tm_polygons}} @@ -46,7 +51,6 @@ can be switched on and off. Options: \code{"radio"} for radio buttons (meaning only one group can be shown), \code{"check"} for check boxes (so multiple groups can be shown), and \code{"none"} for no control (the group cannot be (de)selected).} - \item{\code{options}}{options passed on to the corresponding \verb{opt_} function} \item{\code{popup.vars}}{names of data variables that are shown in the popups in \code{"view"} mode. Set popup.vars to \code{TRUE} to show all variables in the shape object. Set popup.vars to \code{FALSE} to disable popups. Set \code{popup.vars} @@ -63,6 +67,20 @@ is applied to the named variable.} \item{\code{id}}{name of the data variable that specifies the indices of the spatial features. Only used for \code{"view"} mode.} }} + +\item{expansion}{factor expansion, see \code{\link[cartogram:cartogram_ncont]{cartogram::cartogram_ncont()}} (argument \code{k})} + +\item{inplace}{should each polygon be modified in its original place? (\code{TRUE} by default)} + +\item{share}{share of the bounding box filled with the larger circle (see \code{\link[cartogram:cartogram_dorling]{cartogram::cartogram_dorling()}} argument \code{k})} + +\item{size, size.scale, size.legend, size.chart, size.free}{Transformation variable that +determines the size of the polygons.} + +\item{plot.order}{Specification in which order the spatial features are drawn. +See \code{\link[=tm_plot_order]{tm_plot_order()}} for details.} + +\item{options}{options passed on to the corresponding \verb{opt_} function} } \description{ Map layer that draws a cartogram diff --git a/man/tm_facets.Rd b/man/tm_facets.Rd index ae60975e..da403082 100644 --- a/man/tm_facets.Rd +++ b/man/tm_facets.Rd @@ -47,7 +47,7 @@ tm_facets_hstack(by = "VARS__", ...) tm_facets_vstack(by = "VARS__", ...) -tm_facets_flip() +tm_facets_flip(...) } \arguments{ \item{by}{Group by variable (only for a facet wrap or facet stack)} diff --git a/man/tm_shape.Rd b/man/tm_shape.Rd index b097f015..2253b8ae 100644 --- a/man/tm_shape.Rd +++ b/man/tm_shape.Rd @@ -37,3 +37,7 @@ bounding box of the map?} Specify a shape, which is a spatial object from one of these spatial object class packages: \code{\link[sf:sf]{sf}}, \code{\link[stars:st_as_stars]{stars}}, or \code{terra}. } +\examples{ +tm_shape(World, crs = "+proj=robin", filter = World$continent=="Africa") + + tm_polygons() +} diff --git a/man/tm_text.Rd b/man/tm_text.Rd index 3cc16b93..df16c40f 100644 --- a/man/tm_text.Rd +++ b/man/tm_text.Rd @@ -168,7 +168,6 @@ tm_labels( angle.legend = tm_legend_hide(), angle.chart = tm_chart_none(), angle.free = NA, - shadow = FALSE, plot.order = tm_plot_order("AREA", reverse = FALSE, na.order = "bottom"), zindex = NA, group = NA,