Skip to content

Commit

Permalink
Merge branch 'shiny'
Browse files Browse the repository at this point in the history
  • Loading branch information
mtennekes committed Jul 16, 2024
2 parents c26139a + f9d7fac commit 70f0769
Show file tree
Hide file tree
Showing 21 changed files with 495 additions and 146 deletions.
1 change: 1 addition & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,7 @@ importFrom(grDevices,dev.size)
importFrom(grDevices,png)
importFrom(grDevices,rgb)
importFrom(htmlwidgets,saveWidget)
importFrom(htmlwidgets,shinyWidgetOutput)
importFrom(leaflet,providers)
importFrom(rlang,expr)
importFrom(rlang,missing_arg)
Expand Down
12 changes: 10 additions & 2 deletions R/print.R
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,19 @@
#' @param show show the map
#' @param vp viewport (for `"plot"` mode)
#' @param knit A logical, should knit?
#' @param in.shiny A logical, is the map drawn in **shiny**?
#' @param proxy A logical, if `in.shiny`, is \code{\link{tmapProxy}} used?
#' @param options A vector of options
#' @param ... not used
#' @export
print.tmap = function(x, return.asp = FALSE, show = TRUE, vp = NULL, knit = FALSE, options = NULL, ...) {
print.tmap = function(x, return.asp = FALSE, show = TRUE, vp = NULL, knit = FALSE, options = NULL, in.shiny = FALSE, proxy = FALSE, ...) {
args = list(...)

.TMAP$in.shiny = in.shiny
.TMAP$proxy = proxy

# view mode will use panes, in principle one for each layer. They start at 400, unless shiny proxy is used

dev = getOption("tmap.devel.mode")
if (dev) timing_init()
x2 = step1_rearrange(x)
Expand All @@ -18,7 +26,7 @@ print.tmap = function(x, return.asp = FALSE, show = TRUE, vp = NULL, knit = FALS
if (dev) timing_add("step 2")
x4 = step3_trans(x3)
if (dev) timing_add("step 3")
res = step4_plot(x4, vp = vp, return.asp = return.asp, show = show, knit = knit, args)
res = step4_plot(x4, vp = vp, return.asp = return.asp, show = show, in.shiny = in.shiny, knit = knit, args)
if (dev) timing_add("step 4")
if (dev) timing_eval()

Expand Down
85 changes: 85 additions & 0 deletions R/shiny.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
#' Wrapper functions for using **tmap** in **shiny**
#'
#' Use \code{tmapOutput} to create a UI element, and \code{renderTmap} to render the tmap map. To update the map in \code{view} mode, use \code{tmapProxy}. Adding layers is as usual via the map layer functions like \code{\link{tm_polygons}}. Removing layers can be done , removing with the function \code{tm_remove_layer}.
#'
#' Two features from tmap are not (yet) supported in Shiny: small multiples (facets) and colored backgrounds (argument bg.color of \code{\link{tm_layout}}). Workarounds for small multiples: create multiple independent maps or specify as.layers = TRUE in \code{\link{tm_facets}}.
#'
#' @param expr A tmap object. A tmap object is created with \code{\link{qtm}} or by stacking \code{\link{tmap-element}}s.
#' @param mode tmap mode, see [tmap_mode()] If not defined, the current mode is used
#' @param env The environment in which to evaluate expr
#' @param quoted Is expr a quoted expression (with quote())? This is useful if you want to save an expression in a variable
#' @param outputId Output variable to read from
#' @param width,height the width and height of the map
#' @param mapId single-element character vector indicating the output ID of the map to modify (if invoked from a Shiny module, the namespace will be added automatically)
#' @param session the Shiny session object to which the map belongs; usually the default value will suffice
#' @param x the tmap object that specifies the added and removed layers.
#' @param zindex the z index of the pane in which the layer is contained that is going to be removed. It is recommended to specify the `zindex` for this layer when creating the map (inside \code{renderTmap}).
#' @param execOnResize If `TRUE` (default), when the plot is resized, the map is regenerated. When set to `FALSE` the map is rescaled: the aspect ratio is kept, but the layout will be less desirable.
#' @importFrom htmlwidgets shinyWidgetOutput
#' @rdname renderTmap
#' @example ./examples/tmapOutput.R
#' @export
renderTmap <- function(expr, env = parent.frame(), quoted = FALSE, execOnResize = TRUE, mode = NA) {
if (is.na(mode)) mode = getOption("tmap.mode")
gs = tmap_graphics_name(mode)

if (gs == "Leaflet") {
if (!quoted)
expr = substitute(expr)
expr = substitute(getFromNamespace("print.tmap", "tmap")(expr, in.shiny = TRUE))
htmlwidgets::shinyRenderWidget(expr, leafletOutput, env,
quoted = TRUE)
} else if (gs == "Grid") {
#if (!quoted)
# expr = substitute(expr)
expr = substitute(getFromNamespace("print.tmap", "tmap")(expr))
#htmlwidgets::shinyRenderWidget(expr, plotOutput, env,
# quoted = TRUE)
shiny::renderPlot(expr, env = env, quoted = TRUE, execOnResize = execOnResize)
#shiny::renderPlot(plot(1:10), env = env, quoted = quoted)

}
}




#' @name tmapOutput
#' @rdname renderTmap
#' @export
tmapOutput <- function(outputId, width = "100%", height = 400, mode = NA) {
if (is.na(mode)) mode = getOption("tmap.mode")
gs = tmap_graphics_name(mode)
if (gs == "Leaflet") {
leaflet::leafletOutput(outputId, width = width, height = height)
} else if (gs == "Grid") {
shiny::plotOutput(outputId = outputId, width = width, height = height)
}
}

#' @name tmapProxy
#' @rdname renderTmap
#' @export
tmapProxy <- function(mapId, session = shiny::getDefaultReactiveDomain(), x, mode = NA) {
if (is.na(mode)) mode = getOption("tmap.mode")
gs = tmap_graphics_name(mode)

if (gs == "Leaflet") {
print.tmap(x, lf = leaflet::leafletProxy(mapId, session), show = FALSE, in.shiny = TRUE, proxy = TRUE)
} else if (gs == "Grid") {
message("tmapProxy not working for plot mode (yet)")
print.tmap(x, show = TRUE)
}


}


#' @name tm_remove_layer
#' @rdname renderTmap
#' @export
tm_remove_layer <- function(zindex) {
tm_element_list(tm_element(zindex = zindex,
subclass = c("tm_proxy", "tm_remove_layer")))
}

38 changes: 33 additions & 5 deletions R/step1_rearrange.R
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,38 @@ step1_rearrange = function(tmel) {
is_other = !is_tml & !is_tms & !is_tmf

is_aux = vapply(tmel, inherits, "tm_aux_layer", FUN.VALUE = logical(1))
is_proxy = vapply(tmel, inherits, "tm_proxy", FUN.VALUE = logical(1))

# find layer id numbers (needed to plot layers in correct order, which is not trivial due to the two layer types)
lay_id = cumsum(is_tml | is_aux)
lay_id[!is_tml & !is_aux] = 0


if (any(is_proxy)) {
prx = tmel[is_proxy]
proxy_z = lapply(prx, function(px) px$zindex)
} else {
prx = list()
proxy_z = numeric(0)
}

# remove layers from q (via tm_remove_layer)
# and determine highest pane number (400 default)
if (.TMAP$proxy) {
q = .TMAP$q
.TMAP$pane_ids = setdiff(unique(c(.TMAP$pane_ids, q$lid)), 0)
q = q[!(q$lid %in% proxy_z), ]
.TMAP$q = q
if (any(q$lid != 0)) {
.TMAP$start_pane_id = max(q$lid)
} else {
.TMAP$start_pane_id = 400
}
} else {
.TMAP$start_pane_id = 400
.TMAP$pane_ids = NULL
}

# create groups, for each group: tms (tmap shape), tmls (tmap layers), tmf (tmap facets)
ids = cumsum(is_tms)
ids[is_other] = 0L
Expand Down Expand Up @@ -96,7 +123,7 @@ step1_rearrange = function(tmel) {

# extract layers and add layer id number
tmls = mapply(function(l, i) {
l$lid = if (is.na(l$zindex)) i else l$zindex
l$lid = if (is.na(l$zindex)) i + .TMAP$start_pane_id else l$zindex
l
}, tmg[is_tml], lid[is_tml], SIMPLIFY = FALSE)

Expand Down Expand Up @@ -157,8 +184,8 @@ step1_rearrange = function(tmel) {
if (dev) timing_add(s2 = "facet meta")


# # add basemaps
if (o$basemap.show) {
# add basemaps
if (o$basemap.show && !.TMAP$proxy) {
if (!any(vapply(oth, inherits, "tm_basemap", FUN.VALUE = logical(1)))) {
oth = c(oth, tm_basemap())
oth_lay_id = c(oth_lay_id, 0L)
Expand All @@ -170,7 +197,7 @@ step1_rearrange = function(tmel) {
if (any(is_aux)) {

aux = mapply(function(l, i) {
l$lid = if (is.na(l$zindex)) i else l$zindex
l$lid = if (is.na(l$zindex)) i + .TMAP$start_pane_id else l$zindex

cls = class(l)[1]
ot = get_prefix_opt(class = cls, o = o)
Expand All @@ -190,6 +217,7 @@ step1_rearrange = function(tmel) {
cmp = list()
}


# to be used later
o$main = ids # to determine total bounding box in step 4
o$main_class = main_class # inner.margins are class specific (preprecess_meta)
Expand All @@ -210,7 +238,7 @@ step1_rearrange = function(tmel) {

if (dev) timing_add(s2 = "prep shape")

list(tmo = tmo, aux = aux, cmp = cmp, o = o)
list(tmo = tmo, aux = aux, cmp = cmp, prx = prx, o = o)
}

# see above
Expand Down
5 changes: 3 additions & 2 deletions R/step2_data.R
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ step2_data = function(tm) {
o = tm$o
aux = tm$aux
cmp = tm$cmp
prx = tm$prx

if (is.null(tmo)) {
return( list(tmo = NULL, aux = aux, cmp = cmp, o = o))
return( list(tmo = NULL, aux = aux, cmp = cmp, prx = prx, o = o))
}

groupnames = paste0("group", seq_along(tmo))
Expand Down Expand Up @@ -173,5 +174,5 @@ step2_data = function(tm) {
}
})

list(tmo = grps, aux = aux, cmp = cmp, o = o)
list(tmo = grps, aux = aux, cmp = cmp, prx = prx, o = o)
}
3 changes: 2 additions & 1 deletion R/step3_trans.R
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ step3_trans = function(tm) {
o = tm$o
aux = tm$aux
cmp = tm$cmp
prx = tm$prx

bd = lapply(ad, function(adi) {
shpDT = adi$shpDT
Expand Down Expand Up @@ -70,6 +71,6 @@ step3_trans = function(tm) {
adi
})

list(tmo = bd, aux = aux, cmp = cmp, o = o)
list(tmo = bd, aux = aux, cmp = cmp, prx = prx, o = o)
}

77 changes: 47 additions & 30 deletions R/step4_plot.R
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
pane_name = function(id) {
paste0("tmap", sprintf("%03d", id))
}

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

Expand Down Expand Up @@ -207,11 +211,12 @@ process_components2 = function(cdt, o) {
cdt
}

step4_plot = function(tm, vp, return.asp, show, knit, args) {
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

if (length(tmx) && ("bbx" %in% names(tmx[[o$main]]))) {
bbm = tmx[[o$main]]$bbx
Expand Down Expand Up @@ -571,65 +576,76 @@ step4_plot = function(tm, vp, return.asp, show, knit, args) {
if (nrow(cdt)) cdt = process_components2(cdt, o)

# init
asp = do.call(FUNinit, list(o = o, return.asp = return.asp, vp = vp))
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))
}

# 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_lid)) {
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(
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 = ig, glid = 1:nl, lid = lid, group = group, group.control = group.control)
data.frame(gid = ig, glid = 1:nl, lid = lid, group = group, group.control = group.control, lid2 = 0, pane = "", new = TRUE)
})
} else {
NULL
}},
{if (length(aux_lid)) list(data.frame(gid = 0, glid = 1L:length(aux), lid = aux_lid, group = aux_group, group.control = aux_group.control)) 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)) else NULL}))

q$lid2 = 0
#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] = paste0("tmap", 400 + q$lid2[q$lid2 > 0])

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


# 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)



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


Expand Down Expand Up @@ -676,7 +692,7 @@ step4_plot = function(tm, vp, return.asp, show, knit, args) {
}
}

for (qi in 1L:nrow(q)) {
for (qi in which(q$new)) {
gid = q$gid[qi]
glid = q$glid[qi]
pane = q$pane[qi]
Expand Down Expand Up @@ -798,8 +814,9 @@ step4_plot = function(tm, vp, return.asp, show, knit, args) {

if (show) save_last_map()



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

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

0 comments on commit 70f0769

Please sign in to comment.