R/step4_plot.R

Defines functions step4_plot process_components2 process_components pane_name

pane_name = function(id) {
	paste0("tmap", sprintf("%03d", id))
}

process_components = function(cdt, o) {
	gs = tmap_graphics_name()


	# update added legends
	is_added_leg = vapply(cdt$comp, inherits, logical(1), "tm_add_legend")

	if (any(is_added_leg)) {
		cdt$comp[is_added_leg] = lapply(cdt$comp[is_added_leg], tmapAddedLegend, o = o)
	}


	cdt$class = sapply(cdt$comp, function(l) l$position$type)
	cdt$cell.h = sapply(cdt$comp, function(l) {x = l$position$cell.h; if (is.null(x)) NA else x})
	cdt$cell.v = sapply(cdt$comp, function(l) {x = l$position$cell.v; if (is.null(x)) NA else x})
	cdt$pos.h = sapply(cdt$comp, function(l) {x = l$position$pos.h; if (is.null(x)) NA else x})
	cdt$pos.v = sapply(cdt$comp, function(l) {x = l$position$pos.v; if (is.null(x)) NA else x})
	cdt$z = sapply(cdt$comp, function(l) {x = l$z; if (is.null(x)) NA_integer_ else x})

	# to make sure legends positions are based on one-facet-per-page
	if (o$type == "page") {
		cdt[, by3__ := by1__]
		cdt[, by1__ := NA]
	}


	if (gs != "Grid") {
		cdt[class == "out", class := "in"]
		cdt[class == "autoout", class := "in"]
	}

	funP = paste0("tmap", gs, "CompPrepare")
	funW = paste0("tmap", gs, "CompWidth")
	funH = paste0("tmap", gs, "CompHeight")

	cdt$comp = lapply(cdt$comp, function(comp) do.call(funP, list(comp = comp, o = o)))

	# split components into facets (if needed)
	is_multi = sapply(cdt$comp, inherits, what = "tm_multi_comp")

	if (any(is_multi)) {
		cdt_single = cdt[!is_multi, ]
		cdt_multi = cdt[is_multi, ]

		allby_temp = as.data.table(do.call(expand.grid, lapply(o$fn, seq_len)))
		names(allby_temp) = paste0("by", 1:3, "__")

		cdt_rows = data.table::rbindlist(lapply(seq_len(nrow(cdt_multi)), function(i) {
			if (cdt_multi$class[i] %in% c("out", "autoout")) {
				if (length(cdt_multi$comp[[i]]) > 1) {
					warning("Component is specified for multiple facets (e.g. title text), but since placement is outside, only the first is used", call. = FALSE)				}
				cdt_multi$comp[[i]] = cdt_multi$comp[[i]][[1]]
				cdt_multi[i, ]
			} else {
				cbind(allby_temp,
					  comp = rep(cdt_multi$comp[[i]], length.out = nrow(allby_temp)),
					  cdt_multi[i, setdiff(names(cdt_multi), c(names(allby_temp), "comp")), with = FALSE])
			}
		}))
		cdt = rbind(cdt_single, cdt_rows)
	}









	# filter components that are not shown (e.g. not implemented), which is determined in the Prepare step
	cdt$show = sapply(cdt$comp, function(l) l$show)
	cdt = cdt[cdt$show,][,show:=NULL]


	## do to: proper S3 methods
	#if (gs == "Grid") {
		cdt$comp = lapply(cdt$comp, function(comp) do.call(funW, list(comp = comp, o = o)))
		cdt$comp = lapply(cdt$comp, function(comp) do.call(funH, list(comp = comp, o = o)))

		cdt[, ':='(facet_row = character(), facet_col = character())]
		cdt$stack_auto = vapply(cdt$comp, function(l) {
			s = l$stack
			length(s) > 1
		}, FUN.VALUE = logical(1))
		cdt$stack = vapply(cdt$comp, function(l) {
			s = l$stack
			if (length(s) > 1 && "manual" %in% names(s)) s["manual"] else s[1]
		}, FUN.VALUE = character(1))


		getLW = function(x) sapply(x, function(y) {
			yW = y$Win
			yW %||% 0
		})
		getLH = function(x) sapply(x, function(y) {
			yH = y$Hin
			yH %||% 0
		})
		# attempt to determine margins
		cdt[, legW := getLW(comp)]
		cdt[, legH := getLH(comp)]

		if (any(is.na(cdt$z))) {
			cdt[is.na(z), z := seq(1L,(sum(is.na(z))))]
		}
		if (nrow(cdt)>0L) {
			data.table::setorder(cdt, "z")
		}
	# } else if (gs == "Leaflet") {
	#
	# }

	#cdt[, page := NA_integer_]


	cdt
}

process_components2 = function(cdt, o) {
	gs = tmap_graphics_name()




	if (o$type != "grid" && o$n > 1) {
		#if (o$nrows == 1 && o$ncols == 1)
		if (o$nrows == 1) {
			# -use by2 and not by1 when they form a row
			cdt[, by2__ := by1__]
			cdt[, by1__ := NA]
		}
	}

	stacks = o$legend.stack


	# place legends inside if needed
	#if (o$ncols > 1 && o$nrows > 1) {
	if (o$type == "wrap") {
		# all free legends inside
		cdt[!is.na(by1__) | !is.na(by2__) & class == "autoout", ':='(class = "in")]
	} else if (o$type == "grid") {
		# all free-per-facet legends inside
		cdt[!is.na(by1__) & !is.na(by2__) & class == "autoout", ':='(class = "in")]
	}



	# update auto position (for 'all', 'rows', 'columns' legends)
	cdt[is.na(by1__) & is.na(by2__) & class == "autoout", ':='(cell.h = o$legend.position.all$cell.h, cell.v = o$legend.position.all$cell.v)]
	cdt[!is.na(by1__) & is.na(by2__) & class == "autoout", ':='(cell.h = o$legend.position.sides$cell.h, cell.v = "by")]
	cdt[is.na(by1__) & !is.na(by2__) & class == "autoout", ':='(cell.h = "by", cell.v = o$legend.position.sides$cell.v)]

	cdt[is.na(by1__) & is.na(by2__) & class == "autoout", ':='(stack = ifelse(stack_auto, ifelse(cell.h == "center", stacks["all_col"], ifelse(cell.v == "center", stacks["all_row"], stacks["all"])), stack))]
	cdt[!is.na(by1__) & is.na(by2__) & class == "autoout", ':='(stack = ifelse(stack_auto, stacks["per_row"], stack))]
	cdt[is.na(by1__) & !is.na(by2__) & class == "autoout", ':='(stack = ifelse(stack_auto, stacks["per_col"], stack))]


	cdt[class == "autoout", class := "out"]


	lai = which(cdt$class == "autoin")
	if (length(lai)) {
		cdt[class == "autoin", ":="(pos.h = ifelse(is.na(pos.h), o$legend.autoin.pos[1], pos.h),
									pos.v = ifelse(is.na(pos.v), o$legend.autoin.pos[2], pos.v),
									class = "in")]


		# cdt$comp = lapply(cdt$comp, function(l) {
		# 	if (is.na(l$position$pos.h)) o$legend.autoin.pos[1]
		# 	if (is.na(l$position$pos.v)) o$legend.autoin.pos[2]
		# 	#l$position[c("pos.h", "pos.v")] = as.list(o$legend.autoin.pos)
		# 	l
		# })
		# cdt[class == "autoin", ":="(pos.h = o$legend.autoin.pos[1], pos.v = o$legend.autoin.pos[2], class = "in")]
	}

	vby = any(cdt$cell.v == "by" & !is.na(cdt$cell.v))
	hby = any(cdt$cell.h == "by" & !is.na(cdt$cell.h))

	toC = function(x) {
		paste(x, collapse = "_")
	}


	# manual outside legends -2 is top or left, -1 is bottom or right
	cdt[class %in% c("autoout", "out"), ':='(facet_row =
			ifelse(cell.v == "center", ifelse(vby, "1", toC(1:o$nrows)),
			ifelse(cell.v == "by", as.character(by1__),
			ifelse(cell.v == "top", as.character(-2), as.character(-1)))),
		facet_col =
			ifelse(cell.h == "center", ifelse(hby, "1", toC(1:o$ncols)),
			ifelse(cell.h == "by", as.character(by2__),
			ifelse(cell.h == "left", as.character(-2), as.character(-1)))))]
	if (o$type == "page") {
		cdt[cell.v == "by", facet_row := "1"]
		cdt[cell.h == "by", facet_col := "1"]
	}

	if (o$type == "page") {
		cdt[, by1__ := by3__]
		cdt[, by3__ := NA]
	}


	cdt
}

step4_plot = function(tm, vp, return.asp, show, in.shiny, knit, args) {
	tmx = tm$tmo
	o = tm$o
	aux = tm$aux
	cmp = tm$cmp
	prx = tm$prx

	# shortcut if no data layers are used, but only a tm_shape
	if (length(tmx)) {
		bbx_def = vapply(o$main, function(mid) {
			"bbx" %in% names(tmx[[mid]])
		}, FUN.VALUE = logical(1))
		if (any(bbx_def)) {
			bbxs = lapply(o$main[which(bbx_def)], FUN = function(mid) {
				tmx[[mid]]$bbx
			})
			bbm = stm_merge_bbox(bbxs)
		} else {
			bbm = NULL
		}
	} else {
		bbm = NULL
	}

	# remove empty data layers
	any_groups = (length(tmx) > 0L)

	tmx = lapply(tmx, function(tmxi) {
		tmxi$layers = lapply(tmxi$layers, function(tml) {
			empt = vapply(tml$shpDT$shpTM, function(sdt) {
				length(sdt$shp) == 0L
			}, FUN.VALUE = logical(1))
			if (all(empt)) NULL else tml
		})
		empt = vapply(tmxi$layers, is.null, logical(1))

		if (all(empt)) {
			NULL
		} else {
			tmxi$layers = tmxi$layers[!empt]
			tmxi
		}
	})
	empt = vapply(tmx, is.null, logical(1))
	tmx = tmx[!empt]


	any_data_layer = (length(tmx) > 0L)

	# split tm in case of as.layers in tm_facets
	# TODO

	# get name of graphics engine (for function names e.g. tmapGridInit)
	gs = tmap_graphics_name()

	o = prepreprocess_meta(o, vp)

	# get legends from layer data and put them in "components data.table" (cdt)

	cdt_cmp = if (length(cmp) && !o$legend.only) {
		 data.table::rbindlist(lapply(cmp, function(cp) {
			data.table::data.table(by1__ = NA_integer_,
								   by2__ = NA_integer_,
								   by3__ = NA_integer_,
								   comp = list(cp))
		}))
	} else {
		data.table::data.table(by1__ = integer(0),
							   by2__ = integer(0),
							   by3__ = integer(0),
							   comp = list())
	}

	cdt = if (any_data_layer) {
		cdt_legs = step4_plot_collect_legends(tmx)
		cdt_crts = step4_plot_collect_charts(tmx)
		data.table::rbindlist(list(cdt_legs, cdt_crts, cdt_cmp))
	} else {
		cdt_cmp
	}


	## place components top left
	if (o$legend.only) {
		cdt$comp = lapply(cdt$comp, function(cc) {
			cc$position = l = complete_options(tm_pos_in("left", "top"), o$legend.position)
			cc
		})
	}


	if (nrow(cdt)) cdt = process_components(cdt, o)

	# determine panel type, inner margins, and automatic comp placement
	if (!o$legend.only) {
		o = preprocess_meta(o, cdt)

		# add shape unit (needed for e.g. tm_scale_bar)
		unit = ifelse(o$unit == "metric", "km", ifelse(o$unit == "imperial", "mi", o$unit))
		crs = get_option_class(o$crs_step4, "sf") #o$crs
		longlat = sf::st_is_longlat(crs)
	} else {
		o$npages = 1L
	}



	# function to get shape object
	get_shpTM = function(shpDT, by1, by2, by3) {
		b = list(by1, by2, by3)
		bynames = intersect(names(shpDT), paste0("by", 1:3, "__"))
		byids = as.integer(substr(bynames, 3, 3))

		sel = rep(TRUE, nrow(shpDT))
		if (length(bynames)) {
			for (i in 1L:length(bynames)) {
				sel = sel & shpDT[[bynames[i]]] %in% b[[byids[i]]]
			}
		}
		shpDT$shpTM[which(sel)]
	}

	# function to subset data
	get_dt = function(dt, by1, by2, by3) {
		b = list(by1, by2, by3)
		bynames = intersect(names(dt), paste0("by", 1:3, "__"))
		byids = as.integer(substr(bynames, 3, 3))

		sel = rep(TRUE, nrow(dt))
		if (length(bynames)) {
			for (i in 1:length(bynames)) {
				sel = sel & dt[[bynames[i]]] %in% b[[byids[i]]]
			}
		}
		dt[which(sel),]
	}

	# function to get bbox per facet, also take into account bbm (for groups without data-layers)
	get_bbox = function(by1, by2, by3) {
		bbxs = lapply(tmain, function(tmi) {
			shpTM = get_shpTM(tmi$shpDT, by1, by2, by3)
			mdt = get_dt(tmi$mapping_dt, by1, by2, by3)
			bbxs2 = lapply(shpTM, stm_bbox, tmapID = mdt$tmapID__, crs = crs)
			if (!is.null(bbm)) bbxs2 = c(list(bbm), bbxs2)
			bbx = stm_merge_bbox(bbxs2)
			if (is.na(bbx)) bbx else tmaptools::bb(bbx, asp.limit = 10)
		})
		list(list(bb_ext(stm_merge_bbox(bbxs), o$inner.margins)))
	}

	# main group (that determines bounding box)
	# TODO take into account multiple main groups (see step1_rearrange and get_main_ids)

	if (o$legend.only) {
		d = NULL
	} else if (any_data_layer) {
		# create table with meta data for the facets (row, col id, bbox, asp)
		d = data.table::data.table(do.call(expand.grid, lapply(structure(o$nby, names = c("by1", "by2", "by3")), seq_len)))
		d[, i := seq_len(nrow(d))]
		grps = c("by1", "by2", "by3")[o$free.coords]

		grp_ids = as.integer(substr(names(tmx), 6, nchar(names(tmx))))
		mains_in_grp = intersect(o$main[!bbx_def], grp_ids)
		if (length(mains_in_grp)) {
			lookup = match(mains_in_grp, grp_ids)
			tmain = unlist(unlist(tmx[lookup], recursive = FALSE, use.names = FALSE), recursive = FALSE, use.names = FALSE)
			d[, bbox:=do.call(get_bbox, as.list(.SD)), by = grps, .SDcols = c("by1", "by2", "by3")]
		} else {
			if (is.null(bbm)) {
				bbo = o$bbox
				if (!is.null(bbo)) {
					bbm = tmaptools::bb(bbo)
				} else {
					bbm = sf::st_bbox()
				}
			}
			d[, bbox:=rep(list(bbm),nrow(d))]
		}
	} else {
		if (is.null(bbm)) {
			bbo = o$bbox
			if (!is.null(bbo)) {
				bbm = tmaptools::bb(bbo)
			} else {
				bbm = sf::st_bbox()
			}
		}
		d = data.table::data.table(by1 = 1L, by2 = 1L, by3 = 1L, i = 1, bbox = list(bbm))
	}

	if (!o$legend.only) {
		d[, asp:=get_asp(bbox)]
		d = d[!is.na(asp)]


		if (!(o$type %in% c("grid", "page")) && !is.na(o$nrows) && !is.na(o$ncols)) {
			# limit facets
			n_lim = limit_nx(o$n)
			if (n_lim != o$n) {
				fn_lim = pmin(o$fn, n_lim)
				while(prod(fn_lim) > n_lim) {
					fn_lim[which.max(fn_lim)] = fn_lim[which.max(fn_lim)] - 1L
				}
				d = d[by1<= fn_lim[1] & by2<= fn_lim[2] & by3 <= fn_lim[3]]
				o$fl = mapply(function(a, b) a[1:b], o$fl, fn_lim, SIMPLIFY = FALSE)
				o$fn = fn_lim
				o$n = n_lim
			}
		}

		d[, bbox:=lapply(bbox, FUN = function(bbx) {
			if (!is.na(bbx) && !is.na(longlat) && longlat && !sf::st_is_longlat(bbx)) {
				sf::st_bbox(sf::st_transform(tmaptools::bb_poly(bbx), crs = 4326))
			} else {
				bbx
			}
		})]

		d[, units:=lapply(bbox, FUN = function(bbx) {
			if (is.na(bbx)) {
				list()
			} else {
				if (!is.na(bbx) && !is.na(longlat) && longlat) {
					latitude <- mean.default(bbx[c(2,4)])
					bbxll <- c(xmin=0, ymin=latitude, xmax=1, ymax=latitude)
					ad <- suppressWarnings({tmaptools::approx_distances(bbxll, projection=crs)})
					to <- as.numeric(units::set_units(ad$hdist, units::as_units(unit), mode = "standard"))
				} else {
					ad <- suppressWarnings({tmaptools::approx_distances(bbx, projection=crs)})

					if (is.na(crs)) {
						to <- ad$hdist
					} else {
						to <- as.numeric(units::set_units(units::set_units(1, attr(ad$hdist, "units")$numerator, mode = "standard"), units::as_units(unit), mode = "standard"))
					}
				}
				list(projection=crs, unit=unit, to=to, projected = !longlat)
			}
		})]

		# determine automatic position of inside comp
		if (!any(o$free.coords) && any(cdt$class == "autoin")) {
			shp = tmain[[1]]$shpDT$shpTM[[1]]$shp
			# TODO take into account multiple main shapes
			# TODO take use areas instead of coordinates for polygons
			if (inherits(shp, c("sf", "sfc"))) {
				bbx = d$bbox[[1]]
				co = sf::st_coordinates(sf::st_centroid(shp))

				xn = (co[,1]-bbx[1])/(bbx[3]-bbx[1])
				yn = (co[,2]-bbx[2])/(bbx[4]-bbx[2])
				cornerID = which.max(c(
					bl = min(sqrt((xn^2) + (yn^2)), na.rm = TRUE),
					tl = min(sqrt((xn^2) + ((1-yn)^2)), na.rm = TRUE),
					tr = min(sqrt(((1-xn)^2) + ((1-yn)^2)), na.rm = TRUE),
					br = min(sqrt(((xn-1)^2) + (yn^2)), na.rm = TRUE)))

				o$legend.autoin.pos = switch(names(cornerID), tl = c("left", "top"), tr = c("right", "top"), bl = c("left", "bottom"), br = c("right", "bottom"))
			} else {
				o$legend.autoin.pos = c("left", "top")
			}
		} else {
			o$legend.autoin.pos = c("left", "top")
		}
	}


	# calculate margins, number of rows and colums, etc.
	o = process_meta(o, d, cdt, aux)

	if (!o$legend.only) {
		# workaround to move panels to titles in view mode
		# TO DO: make this generic (e.g. component prep function?)
		if (gs != "Grid") {
			if (o$panel.type != "none") {
				cdt = rbindlist(c({if (nrow(cdt) == 0L) NULL else list(cdt)}, mapply(function(lab, i) {
					data.table::data.table(by1__ = i,
										   by2__ = NA_integer_,
										   by3__ = NA_integer_,
										   comp = list(impute_comp(tm_title(lab)[[1]], o)),
										   class=  "in",
										   cell.h = NA,
										   cell.v = NA,
										   pos.h = "left",
										   pos.v = "top",
										   z = 1,
										   facet_row = NA_character_,
										   facet_col = NA_character_,
										   stack_auto = TRUE,
										   stack = "vertical",
										   legW = 0,
										   legH = 0)
				}, o$panel.labels[[1]], seq_len(o$fn[1]), SIMPLIFY = FALSE)))
			}
			o$panel.type = "none"
		}

		o$ng = length(tmx)

		# determine row and col ids

		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 {
			# wrap
			if (o$facet.flip) {
				d[, row := as.integer((i - 1) %% o$nrows + 1)]
				d[, col := as.integer((((i - 1) %/% o$nrows + 1) - 1) %% o$ncols + 1)]
			} else {
				d[, col := as.integer((i - 1) %% o$ncols + 1)]
				d[, row := as.integer((((i - 1) %/% o$ncols + 1) - 1) %% o$nrows + 1)]
			}

		}
		d[, page := as.integer(i - 1) %/% (o$nrows * o$ncols) + 1]


		### facet.flip and reverse
		# if (o$facet.flip) {
		# 	labcols= o$panel.labels[[1]]
		# 	labrows = o$panel.labels[[2]]
		# 	nr = o$nrows
		# 	o$nrows = o$ncols
		# 	o$ncols = nr
		# } else {
		# 	labrows = o$panel.labels[[1]]
		# 	labcols = o$panel.labels[[2]]
		# }
		#


		# # reverse if specified (with '-' in front of row/col/page variable name in tm_facets)
		# if (o$rev1) {
		# 	labs1 = o$panel.labels[[1]]
		# 	d[, by1:=(1L+length(labs1)) - by1]
		# 	o$panel.labels[[1]] = structure(rev(labs1), showNA = attr(labs1, "showNA"))
		# }
		# if (o$rev2) {
		# 	labs2 = o$panel.labels[[2]]
		# 	d[, by2:=(1L+length(labs2)) - by2]
		# 	o$panel.labels[[2]] = rev(labs2)
		# }
		# if (o$rev3) {
		# 	d[, by3:=(1L+max(by3)) - by3]
		# }
		#
	}



	#####o$legend.autoin.pos = c("left", "top")

	# prepare function names
	FUNinit = paste0("tmap", gs, "Init")
	FUNaux = paste0("tmap", gs, "Aux")
	FUNrun = paste0("tmap", gs, "Run")
	FUNshape = paste0("tmap", gs, "Shape")
	FUNoverlay = paste0("tmap", gs, "Overlay")
	FUNwrap = paste0("tmap", gs, "Wrap")
	FUNxtab = paste0("tmap", gs, "Xtab")

	FUNxlab = paste0("tmap", gs, "Xlab")
	FUNylab = paste0("tmap", gs, "Ylab")


	FUNgridxlab = paste0("tmap", gs, "GridXLab")
	FUNgridylab = paste0("tmap", gs, "GridYLab")

	if (!o$legend.only) {
		# create table with bounding boxes (the only important property, apart from settings)
		db = data.table(bbox = unique(d$bbox[!is.na(d$asp)]))
		db[, i:=1L:nrow(db)]
		d[, bi:=db$i[match(d$bbox, db$bbox)]]

		## process components
		if (nrow(cdt)) cdt = process_components2(cdt, o)

		# init
		asp = do.call(FUNinit, c(list(o = o, return.asp = return.asp, vp = vp, prx = prx), args))
		if (return.asp) return(asp)

		## prepare aux layers
		if (length(aux)) {
			# prepare aux layers and return group label (in case it is not user specified)
			aux_group_def = mapply(function(a, id) {
				FUNaux_prep = paste0("tmap", gs, a$mapping.fun, "Prep")
				do.call(FUNaux_prep, list(a = a$args, bs = db$bbox, id = id, o = o))
			}, aux, 1:length(aux))
			aux_group = mapply(function(a, agd) {
				if (is.na(a$group)) agd else as.character(a$group)
			}, aux, aux_group_def, USE.NAMES = FALSE)

			aux_group.control = vapply(aux, function(a) {
				a$group.control
			}, FUN.VALUE = character(1))

			# find lid (layer plot id values) for aux layers
			aux_lid = vapply(aux, function(a) a$lid, FUN.VALUE = numeric(1))
		}

		if (!any_data_layer && !length(aux)) {
			message_nothing_to_show(any_groups)
			return(invisible(NULL))
		}



		# data frame for layer ids
		q = do.call(rbind, c(
			{if (any_data_layer) {
				lapply(1L:o$ng, function(ig) {
					tmxi = tmx[[ig]]
					nl = length(tmxi$layers)
					lid = vapply(tmxi$layers, function(l) {l$lid}, FUN.VALUE = numeric(1))
					group = vapply(tmxi$layers, function(l) {l$group}, FUN.VALUE = character(1))
					group.control = vapply(tmxi$layers, function(l) {l$group.control}, FUN.VALUE = character(1)) # used to determine control layer group (view mode)
					data.frame(gid = rep(ig, nl), glid = 1:nl, lid = lid, group = group, group.control = group.control, lid2 = rep(0, nl), pane = rep("", nl), new = rep(TRUE, nl), group.zoom_levels = I(rep(list(NA),nl)))
				})
			} else {
				NULL
			}},
			{if (length(aux)) list(data.frame(gid = 0, glid = 1L:length(aux), lid = aux_lid, group = aux_group, group.control = aux_group.control, lid2 = 0, pane = "", new = TRUE, group.zoom_levels = I(rep(list(NA),length(aux))))) else NULL}))

		#q$lid[q$lid != 0] = q$lid[q$lid != 0] + 400L

		if (.TMAP$proxy) {
			q0 = .TMAP$q
			q0$new = FALSE
			q = rbind(q0, q)
		}

		qnotnull = (q$lid != 0)
		if (any(qnotnull)) q$lid2[qnotnull] = rank(q$lid[qnotnull])

		q = q[order(q$lid2), ]
		q$pane = "tilePane"

		q$pane[q$lid2 > 0] = pane_name(q$lid[q$lid2 > 0])


		# via tm_group, control and zoom levels can be set
		# these are stored in tmap options view_group_{name}, and merged with q:
		og = o[substr(names(o), 1, 10) == "view_group"]
		ogdf = 	data.frame(name = vapply(og, "[[",FUN.VALUE = character(1), "name", USE.NAMES = FALSE),
						   control = vapply(og, function(x) {
						   	as.character(x[["control"]])
						   } , FUN.VALUE = character(1), USE.NAMES = FALSE),
						   zoom_levels = I(unname(lapply(og, "[[", "zoom_levels"))))
		q$group.control = mapply(function(gi, v) {
			id = which(ogdf$name == gi)
			if (!length(id)) return(v)
			v2 = ogdf$control[id]
			if (is.na(v2)) return(v) else return(v2)
		}, q$group, q$group.control, USE.NAMES = FALSE, SIMPLIFY = TRUE)
		q$group.zoom_levels = mapply(function(gi, v) {
			id = which(ogdf$name == gi)
			if (!length(id)) return(NA)
			return(ogdf$zoom_levels[[id]])
		}, q$group, q$group.control, USE.NAMES = FALSE, SIMPLIFY = FALSE)
		q$group.control[!is.na(q$group.zoom_levels)] = "none" # see leaflet::groupOptions



		# q data frame:
		# gid = tmap-group counter
		# glid = layer counter inside tmap-group
		# lid = possibly-user-defined layer order number
		# lid2 = same as lid, but 1,2,3,...
		# pane = pane name (for view mode)
		# group = group name (for selecting layers in view mode)
		# group.control = leaflet::addLayerControl: check, radio or none (view mode)
		# group.zoom_levels = leaflet::groupOptions: zoom levels displayed at

		do.call(FUNaux, list(o = o, q = q))


		# plot xtab headers
		if (o$panel.type == "xtab") {
			for (k in 1:o$npages) {
				labrows = o$panel.labels[[1]]
				labcols = o$panel.labels[[2]]
				if (length(labrows) == o$nrows) for (i in 1:o$nrows) do.call(FUNxtab, list(label = labrows[i], facet_row = i, facet_page = k, o = o))
				if (length(labcols) == o$ncols) for (j in 1:o$ncols) do.call(FUNxtab, list(label = labcols[j], facet_col = j, facet_page = k, o = o))

			}
		}


		# plot xlab and ylab
		if (o$xlab.show) {
			for (k in 1:o$npages) {
				do.call(FUNxlab, list(facet_page = k, o = o))
			}
		}
		if (o$ylab.show) {
			for (k in 1:o$npages) {
				do.call(FUNylab, list(facet_page = k, o = o))
			}
		}



		for (i in seq_len(nrow(d))) {
			bbx = d$bbox[[i]]
			if (o$panel.type == "wrap") do.call(FUNwrap, list(label = o$panel.labels[[1]][d$i[i]], facet_row = d$row[i], facet_col = d$col[i], facet_page = d$page[i], o = o))
			if (is.na(d$asp[i])) next
			do.call(FUNshape, list(bbx = bbx, facet_row = d$row[i], facet_col = d$col[i], facet_page = d$page[i], o = o))


			# plot grid labels
			if (o$grid.show && !o$grid.labels.inside_frame) {
				if (o$grid.labels.show[2] && ((o$grid.labels.pos[1] == "left" && d$col[i] == 1) || (o$grid.labels.pos[1] == "right" && d$col[i] == o$ncols))) {
					do.call(FUNgridylab, list(bi = d$bi[i], bbx = bbx, facet_row = d$row[i], facet_col = d$col[i], facet_page = d$page[i], o = o))
				}
				if (o$grid.labels.show[1] && ((o$grid.labels.pos[2] == "top" && d$row[i] == 1) || (o$grid.labels.pos[2] == "bottom" && d$row[i] == o$nrows))) {
					do.call(FUNgridxlab, list(bi = d$bi[i], bbx = bbx, facet_row = d$row[i], facet_col = d$col[i], facet_page = d$page[i], o = o))
				}
			}

			for (qi in which(q$new)) {
				gid = q$gid[qi]
				glid = q$glid[qi]
				pane = q$pane[qi]
				group = q$group[qi]
				if (gid > 0) {
					# data layer
					bl = tmx[[gid]]$layers[[glid]]
					shpTM = get_shpTM(bl$shpDT, d$by1[i], d$by2[i], d$by3[i])[[1]]
					mdt = get_dt(bl$mapping_dt, d$by1[i], d$by2[i], d$by3[i])

					id = paste0("f", sprintf("%03d", i), "g", sprintf("%02d", gid), "l", sprintf("%02d", glid))

					if (nrow(mdt) != 0) {
						gp = bl$gp

						FUN = paste0("tmap", gs, bl$mapping_fun)

						do.call(FUN, c(list(shpTM = shpTM, dt = mdt, pdt = bl$popup.data, popup.format = bl$popup.format, hdt = bl$hover.data, idt = bl$id.data, gp = gp, bbx = bbx, facet_col = d$col[i], facet_row = d$row[i], facet_page = d$page[i], id = id, pane = pane, group = group, o = o), bl$mapping_args))
					}

				} else {
					glid = q$glid[qi]


					# aux layer
					a = aux[[glid]]
					FUNaux_plot = paste0("tmap", gs, a$mapping.fun)

					id = glid # to do: test!
					do.call(FUNaux_plot, list(bi = d$bi[i], bbx = bbx, facet_col = d$col[i], facet_row = d$row[i], facet_page = d$page[i], id = id, pane = pane, group = group, o = o))

				}
			}
			do.call(FUNoverlay, list(bbx = bbx, facet_row = d$row[i], facet_col = d$col[i], facet_page = d$page[i], o = o))
		}

	} else {
		# init
		asp = do.call(FUNinit, list(o = o, return.asp = return.asp, vp = vp))
	}


	if (o$legend.only) {
		cdt[, facet_row := "1"]
		cdt[, facet_col := "1"]
		cdt[, page := 1L]
		cdt[, bbox := list()]
	} else {
		is_in = cdt$class == "in"
		#is_in = rep(TRUE, nrow(cdt))
		if (any(is_in)) {
			legs_in = lapply(which(is_in), function(i) {
				d2 = data.table::copy(d)
				legsi = cdt[i, ]
				if (o$type != "grid" && o$nrows == 1) {
					# reverse above
					d2[, by2 := by1]
					d2[, by1 := 1]
				}

				if (is.na(legsi$by1__)) d2[, by1:= NA]
				if (is.na(legsi$by2__)) d2[, by2:= NA]
				if (is.na(legsi$by3__)) d2[, by3:= NA]
				legsi = merge(legsi, d2[, c("by1", "by2", "by3", "row", "col", "page", "bbox", "units"), with = FALSE], by.x = c("by1__", "by2__", "by3__"), by.y = c("by1", "by2", "by3"))
				legsi[, ':='(facet_row = as.character(row), facet_col = as.character(col), row = NULL, col = NULL)]
				legsi
			})
		} else {
			legs_in = NULL
		}

		legs_out = copy(cdt[!is_in])
		legs_out[, page:=NA_integer_]

		# legs_out[, bbox:=list()]
		# legs_out[, units:=list()]

		# ad-hoc method: take first bbox and units
		bbox_nb = d$bbox[1]
		attr(bbox_nb, "borrow") = list(col = d$col[1], row = d$row[1])
		legs_out[, bbox:=list(bbox_nb)]
		legs_out[, units:=d$units[1]]


		cdt = data.table::rbindlist(c(list(legs_out), legs_in))

		cdt$comp = mapply(function(cmp, bbx, u) {
			cmp$bbox = bbx
			cmp$units = u
			cmp
		}, cdt$comp, cdt$bbox, cdt$units, SIMPLIFY = FALSE)

	}







	legfun = paste0("tmap", gs, "Comp")

	toI = function(x) {
		as.integer(strsplit(x, split = "_")[[1]])
	}

	if (o$type == "page") {
		cdt$page = cdt$by1__
	}

	if (nrow(cdt) > 0L) for (k in seq_len(o$npages)) {
		klegs = cdt[is.na(page) | (page == k), ] # was by3__ instead of page
		klegs[, pos.h.id := pos.h][pos.h %in% c("left", "center", "right"), pos.h.id:="lower"][pos.h %in% c("LEFT", "CENTER", "RIGHT"), pos.h.id:="upper"]
		klegs[, pos.v.id := pos.v][pos.v %in% c("top", "center", "bottom"), pos.v.id:="lower"][pos.v %in% c("TOP", "CENTER", "BOTTOM"), pos.v.id:="upper"]
		klegs[, id:=paste(pos.h.id, pos.v.id, sep = "__")]

		klegs[, do.call(legfun, args = list(comp = .SD$comp, o = o, facet_row = toI(.SD$facet_row[1]), facet_col = toI(.SD$facet_col[1]), facet_page = k, class = .SD$class[1], stack = .SD$stack, stack_auto = .SD$stack_auto, pos.h = .SD$pos.h, pos.v = .SD$pos.v, .SD$bbox)), by = list(facet_row, facet_col, id), .SDcols = c("comp", "facet_row", "facet_col", "class", "stack", "stack_auto", "pos.h", "pos.v", "bbox")]
	}

	if (show) save_last_map()

	if (.TMAP$in.shiny) {
		.TMAP$q = q
	}

	do.call(FUNrun, list(o = o, q = q, show = show, knit = knit, args))
}

Try the tmap package in your browser

Any scripts or data that you put into this service are public.

tmap documentation built on April 4, 2025, 2:05 a.m.