diff --git a/R/layout.R b/R/layout.R index 5d3aa3fb480..513b3bb3883 100644 --- a/R/layout.R +++ b/R/layout.R @@ -672,6 +672,7 @@ normalize <- function( #' @param types A logical vector, the vertex types. If this argument is #' `NULL` (the default), then the \sQuote{`type`} vertex attribute is #' used. +#' @inheritParams rlang::args_dots_empty #' @param hgap Real scalar, the minimum horizontal gap between vertices in the #' same layer. #' @param vgap Real scalar, the distance between the two layers. @@ -701,10 +702,33 @@ normalize <- function( layout_as_bipartite <- function( graph, types = NULL, + ..., hgap = 1, vgap = 1, maxiter = 100 ) { + # BEGIN GENERATED ARG_HANDLE: layout_as_bipartite, do not edit, see tools/generate-migrations.R + if (...length() > 0L) { + .arg_handle <- migrate_recover_args( + list(...), + current = list(hgap = hgap, vgap = vgap, maxiter = maxiter), + recover_new = c("hgap", "vgap", "maxiter"), + recover_old = c("hgap", "vgap", "maxiter"), + match_names = c("hgap", "vgap", "maxiter"), + match_to = c("hgap", "vgap", "maxiter"), + defaults = list(hgap = 1, vgap = 1, maxiter = 100), + head_args = c("graph", "types"), + fn_name = "layout_as_bipartite" + ) + list2env(.arg_handle$values, environment()) + lifecycle::deprecate_soft( + "3.0.0", + what = I(.arg_handle$what), + details = .arg_handle$details + ) + } + # END GENERATED ARG_HANDLE + ## Argument checks ensure_igraph(graph) # Use the _impl function @@ -735,6 +759,7 @@ as_bipartite <- function(...) layout_spec(layout_as_bipartite, ...) #' order of the vertices can be also given. #' #' @param graph The graph to layout. +#' @inheritParams rlang::args_dots_empty #' @param center The ID of the vertex to put in the center. By default it is #' the first vertex. #' @param order Numeric vector, the order of the vertices along the perimeter. @@ -755,7 +780,34 @@ as_bipartite <- function(...) layout_spec(layout_as_bipartite, ...) #' #' ## Alternative form #' layout_(g, as_star()) -layout_as_star <- function(graph, center = V(graph)[1], order = NULL) { +layout_as_star <- function( + graph, + ..., + center = V(graph)[1], + order = NULL +) { + # BEGIN GENERATED ARG_HANDLE: layout_as_star, do not edit, see tools/generate-migrations.R + if (...length() > 0L) { + .arg_handle <- migrate_recover_args( + list(...), + current = list(center = center, order = order), + recover_new = c("center", "order"), + recover_old = c("center", "order"), + match_names = c("center", "order"), + match_to = c("center", "order"), + defaults = list(center = V(graph)[1], order = NULL), + head_args = c("graph"), + fn_name = "layout_as_star" + ) + list2env(.arg_handle$values, environment()) + lifecycle::deprecate_soft( + "3.0.0", + what = I(.arg_handle$what), + details = .arg_handle$details + ) + } + # END GENERATED ARG_HANDLE + # Argument checks ensure_igraph(graph) if (vcount(graph) == 0) { @@ -793,6 +845,7 @@ as_star <- function(...) layout_spec(layout_as_star, ...) #' to obtain a possible spanning tree. #' #' @param graph The input graph. +#' @inheritParams rlang::args_dots_empty #' @param root The index of the root vertex or root vertices. If this is a #' non-empty vector then the supplied vertex IDs are used as the roots of the #' trees (or a single tree if the graph is connected). If it is an empty @@ -838,12 +891,47 @@ as_star <- function(...) layout_spec(layout_as_star, ...) #' )) layout_as_tree <- function( graph, + ..., root = numeric(), circular = FALSE, rootlevel = numeric(), mode = c("out", "in", "all"), flip.y = TRUE ) { + # BEGIN GENERATED ARG_HANDLE: layout_as_tree, do not edit, see tools/generate-migrations.R + if (...length() > 0L) { + .arg_handle <- migrate_recover_args( + list(...), + current = list( + root = root, + circular = circular, + rootlevel = rootlevel, + mode = mode, + flip.y = flip.y + ), + recover_new = c("root", "circular", "rootlevel", "mode", "flip.y"), + recover_old = c("root", "circular", "rootlevel", "mode", "flip.y"), + match_names = c("root", "circular", "rootlevel", "mode", "flip.y"), + match_to = c("root", "circular", "rootlevel", "mode", "flip.y"), + defaults = list( + root = numeric(), + circular = FALSE, + rootlevel = numeric(), + mode = c("out", "in", "all"), + flip.y = TRUE + ), + head_args = c("graph"), + fn_name = "layout_as_tree" + ) + list2env(.arg_handle$values, environment()) + lifecycle::deprecate_soft( + "3.0.0", + what = I(.arg_handle$what), + details = .arg_handle$details + ) + } + # END GENERATED ARG_HANDLE + ensure_igraph(graph) root <- as_igraph_vs(graph, root) - 1 circular <- as.logical(circular) @@ -1088,6 +1176,7 @@ nicely <- function(...) layout_spec(layout_nicely, ...) #' [permute()] function. #' #' @param graph The input graph. +#' @inheritParams rlang::args_dots_empty #' @param width The number of vertices in a single row of the grid. If this is #' zero or negative, then for 2d layouts the width of the grid will be the #' square root of the number of vertices in the graph, rounded up to the next @@ -1114,7 +1203,35 @@ nicely <- function(...) layout_spec(layout_nicely, ...) #' if (interactive() && requireNamespace("rgl", quietly = TRUE)) { #' rglplot(g, layout = layout_on_grid(g, dim = 3)) #' } -layout_on_grid <- function(graph, width = 0, height = 0, dim = 2) { +layout_on_grid <- function( + graph, + ..., + width = 0, + height = 0, + dim = 2 +) { + # BEGIN GENERATED ARG_HANDLE: layout_on_grid, do not edit, see tools/generate-migrations.R + if (...length() > 0L) { + .arg_handle <- migrate_recover_args( + list(...), + current = list(width = width, height = height, dim = dim), + recover_new = c("width", "height", "dim"), + recover_old = c("width", "height", "dim"), + match_names = c("width", "height", "dim"), + match_to = c("width", "height", "dim"), + defaults = list(width = 0, height = 0, dim = 2), + head_args = c("graph"), + fn_name = "layout_on_grid" + ) + list2env(.arg_handle$values, environment()) + lifecycle::deprecate_soft( + "3.0.0", + what = I(.arg_handle$what), + details = .arg_handle$details + ) + } + # END GENERATED ARG_HANDLE + # Argument checks ensure_igraph(graph) width <- as.numeric(width) @@ -1204,6 +1321,7 @@ layout.sphere <- function(..., params = list()) { #' layout generators. #' #' @param graph The input graph. +#' @inheritParams rlang::args_dots_empty #' @param dim Integer scalar, the dimension of the space to use. It must be 2 #' or 3. #' @return A numeric matrix with two or three columns. @@ -1211,7 +1329,33 @@ layout.sphere <- function(..., params = list()) { #' @keywords graphs #' @export #' @family graph layouts -layout_randomly <- function(graph, dim = c(2, 3)) { +layout_randomly <- function( + graph, + ..., + dim = c(2, 3) +) { + # BEGIN GENERATED ARG_HANDLE: layout_randomly, do not edit, see tools/generate-migrations.R + if (...length() > 0L) { + .arg_handle <- migrate_recover_args( + list(...), + current = list(dim = dim), + recover_new = c("dim"), + recover_old = c("dim"), + match_names = c("dim"), + match_to = c("dim"), + defaults = list(dim = c(2, 3)), + head_args = c("graph"), + fn_name = "layout_randomly" + ) + list2env(.arg_handle$values, environment()) + lifecycle::deprecate_soft( + "3.0.0", + what = I(.arg_handle$what), + details = .arg_handle$details + ) + } + # END GENERATED ARG_HANDLE + ensure_igraph(graph) dim <- igraph_match_arg(dim) @@ -1272,6 +1416,7 @@ layout.random <- function(..., params = list()) { #' within the bounds of the rectangle of the layout. #' #' @param graph The graph to lay out. Edge directions are ignored. +#' @inheritParams rlang::args_dots_empty #' @param coords Optional starting positions for the vertices. If this argument #' is not `NULL` then it should be an appropriate matrix of starting #' coordinates. @@ -1362,6 +1507,7 @@ layout.random <- function(..., params = list()) { #' plot(g_12, layout = layout_with_dh, vertex.size = 5, vertex.label = NA) layout_with_dh <- function( graph, + ..., coords = NULL, maxiter = 10, fineiter = max(10, log2(vcount(graph))), @@ -1372,6 +1518,88 @@ layout_with_dh <- function( weight.edge.crossings = 1.0 - sqrt(edge_density(graph)), weight.node.edge.dist = 0.2 * (1 - edge_density(graph)) ) { + # BEGIN GENERATED ARG_HANDLE: layout_with_dh, do not edit, see tools/generate-migrations.R + if (...length() > 0L) { + .arg_handle <- migrate_recover_args( + list(...), + current = list( + coords = coords, + maxiter = maxiter, + fineiter = fineiter, + cool.fact = cool.fact, + weight.node.dist = weight.node.dist, + weight.border = weight.border, + weight.edge.lengths = weight.edge.lengths, + weight.edge.crossings = weight.edge.crossings, + weight.node.edge.dist = weight.node.edge.dist + ), + recover_new = c( + "coords", + "maxiter", + "fineiter", + "cool.fact", + "weight.node.dist", + "weight.border", + "weight.edge.lengths", + "weight.edge.crossings", + "weight.node.edge.dist" + ), + recover_old = c( + "coords", + "maxiter", + "fineiter", + "cool.fact", + "weight.node.dist", + "weight.border", + "weight.edge.lengths", + "weight.edge.crossings", + "weight.node.edge.dist" + ), + match_names = c( + "coords", + "maxiter", + "fineiter", + "cool.fact", + "weight.node.dist", + "weight.border", + "weight.edge.lengths", + "weight.edge.crossings", + "weight.node.edge.dist" + ), + match_to = c( + "coords", + "maxiter", + "fineiter", + "cool.fact", + "weight.node.dist", + "weight.border", + "weight.edge.lengths", + "weight.edge.crossings", + "weight.node.edge.dist" + ), + defaults = list( + coords = NULL, + maxiter = 10, + fineiter = max(10, log2(vcount(graph))), + cool.fact = 0.75, + weight.node.dist = 1, + weight.border = 0, + weight.edge.lengths = edge_density(graph) / 10, + weight.edge.crossings = 1 - sqrt(edge_density(graph)), + weight.node.edge.dist = 0.2 * (1 - edge_density(graph)) + ), + head_args = c("graph"), + fn_name = "layout_with_dh" + ) + list2env(.arg_handle$values, environment()) + lifecycle::deprecate_soft( + "3.0.0", + what = I(.arg_handle$what), + details = .arg_handle$details + ) + } + # END GENERATED ARG_HANDLE + if (is.null(coords)) { coords <- matrix(NA_real_, ncol = 2, nrow = 0) use.seed <- FALSE @@ -1413,6 +1641,7 @@ with_dh <- function(...) layout_spec(layout_with_dh, ...) #' This function was rewritten from scratch in igraph version 0.8.0. #' #' @param graph The graph to lay out. Edge directions are ignored. +#' @inheritParams rlang::args_dots_empty #' @param coords Optional starting positions for the vertices. If this argument #' is not `NULL` then it should be an appropriate matrix of starting #' coordinates. @@ -1483,6 +1712,7 @@ with_dh <- function(...) layout_spec(layout_with_dh, ...) #' layout_with_fr <- function( graph, + ..., coords = NULL, dim = c(2, 3), niter = 500, @@ -1501,6 +1731,136 @@ layout_with_fr <- function( repulserad = deprecated(), maxiter = deprecated() ) { + # BEGIN GENERATED ARG_HANDLE: layout_with_fr, do not edit, see tools/generate-migrations.R + if (...length() > 0L) { + .arg_handle <- migrate_recover_args( + list(...), + current = list( + coords = coords, + dim = dim, + niter = niter, + start.temp = start.temp, + grid = grid, + weights = weights, + minx = minx, + maxx = maxx, + miny = miny, + maxy = maxy, + minz = minz, + maxz = maxz, + coolexp = coolexp, + maxdelta = maxdelta, + area = area, + repulserad = repulserad, + maxiter = maxiter + ), + recover_new = c( + "coords", + "dim", + "niter", + "start.temp", + "grid", + "weights", + "minx", + "maxx", + "miny", + "maxy", + "minz", + "maxz", + "coolexp", + "maxdelta", + "area", + "repulserad", + "maxiter" + ), + recover_old = c( + "coords", + "dim", + "niter", + "start.temp", + "grid", + "weights", + "minx", + "maxx", + "miny", + "maxy", + "minz", + "maxz", + "coolexp", + "maxdelta", + "area", + "repulserad", + "maxiter" + ), + match_names = c( + "coords", + "dim", + "niter", + "start.temp", + "grid", + "weights", + "minx", + "maxx", + "miny", + "maxy", + "minz", + "maxz", + "coolexp", + "maxdelta", + "area", + "repulserad", + "maxiter" + ), + match_to = c( + "coords", + "dim", + "niter", + "start.temp", + "grid", + "weights", + "minx", + "maxx", + "miny", + "maxy", + "minz", + "maxz", + "coolexp", + "maxdelta", + "area", + "repulserad", + "maxiter" + ), + defaults = list( + coords = NULL, + dim = c(2, 3), + niter = 500, + start.temp = sqrt(vcount(graph)), + grid = c("auto", "grid", "nogrid"), + weights = NULL, + minx = NULL, + maxx = NULL, + miny = NULL, + maxy = NULL, + minz = NULL, + maxz = NULL, + coolexp = deprecated(), + maxdelta = deprecated(), + area = deprecated(), + repulserad = deprecated(), + maxiter = deprecated() + ), + head_args = c("graph"), + fn_name = "layout_with_fr" + ) + list2env(.arg_handle$values, environment()) + lifecycle::deprecate_soft( + "3.0.0", + what = I(.arg_handle$what), + details = .arg_handle$details + ) + } + # END GENERATED ARG_HANDLE + # Argument checks ensure_igraph(graph) coords[] <- as.numeric(coords) @@ -1627,6 +1987,7 @@ layout.fruchterman.reingold <- function(..., params = list()) { #' See the referenced paper below for the details of the algorithm. #' #' @param graph The input graph. Edge directions are ignored. +#' @inheritParams rlang::args_dots_empty #' @param coords Starting coordinates in a two or three column matrix, #' depending on the `dim` argument. #' Default: `NULL`. @@ -1660,12 +2021,47 @@ layout.fruchterman.reingold <- function(..., params = list()) { #' layout_with_gem <- function( graph, + ..., coords = NULL, maxiter = 40 * vcount(graph)^2, temp.max = max(vcount(graph), 1), temp.min = 1 / 10, temp.init = sqrt(max(vcount(graph), 1)) ) { + # BEGIN GENERATED ARG_HANDLE: layout_with_gem, do not edit, see tools/generate-migrations.R + if (...length() > 0L) { + .arg_handle <- migrate_recover_args( + list(...), + current = list( + coords = coords, + maxiter = maxiter, + temp.max = temp.max, + temp.min = temp.min, + temp.init = temp.init + ), + recover_new = c("coords", "maxiter", "temp.max", "temp.min", "temp.init"), + recover_old = c("coords", "maxiter", "temp.max", "temp.min", "temp.init"), + match_names = c("coords", "maxiter", "temp.max", "temp.min", "temp.init"), + match_to = c("coords", "maxiter", "temp.max", "temp.min", "temp.init"), + defaults = list( + coords = NULL, + maxiter = 40 * vcount(graph)^2, + temp.max = max(vcount(graph), 1), + temp.min = 1 / 10, + temp.init = sqrt(max(vcount(graph), 1)) + ), + head_args = c("graph"), + fn_name = "layout_with_gem" + ) + list2env(.arg_handle$values, environment()) + lifecycle::deprecate_soft( + "3.0.0", + what = I(.arg_handle$what), + details = .arg_handle$details + ) + } + # END GENERATED ARG_HANDLE + if (is.null(coords)) { coords <- matrix(NA_real_, ncol = 2, nrow = 0) use.seed <- FALSE @@ -1709,6 +2105,7 @@ with_gem <- function(...) layout_spec(layout_with_gem, ...) #' that, so a stable fixed point is not guaranteed.) #' #' @param graph The input graph. +#' @inheritParams rlang::args_dots_empty #' @param start If given, then it should be a matrix with two columns and one #' line for each vertex. This matrix will be used as starting positions for the #' algorithm. If not given, then a random starting matrix is used. @@ -1735,6 +2132,7 @@ with_gem <- function(...) layout_spec(layout_with_gem, ...) #' @family graph layouts layout_with_graphopt <- function( graph, + ..., start = NULL, niter = 500, charge = 0.001, @@ -1743,6 +2141,76 @@ layout_with_graphopt <- function( spring.constant = 1, max.sa.movement = 5 ) { + # BEGIN GENERATED ARG_HANDLE: layout_with_graphopt, do not edit, see tools/generate-migrations.R + if (...length() > 0L) { + .arg_handle <- migrate_recover_args( + list(...), + current = list( + start = start, + niter = niter, + charge = charge, + mass = mass, + spring.length = spring.length, + spring.constant = spring.constant, + max.sa.movement = max.sa.movement + ), + recover_new = c( + "start", + "niter", + "charge", + "mass", + "spring.length", + "spring.constant", + "max.sa.movement" + ), + recover_old = c( + "start", + "niter", + "charge", + "mass", + "spring.length", + "spring.constant", + "max.sa.movement" + ), + match_names = c( + "start", + "niter", + "charge", + "mass", + "spring.length", + "spring.constant", + "max.sa.movement" + ), + match_to = c( + "start", + "niter", + "charge", + "mass", + "spring.length", + "spring.constant", + "max.sa.movement" + ), + defaults = list( + start = NULL, + niter = 500, + charge = 0.001, + mass = 30, + spring.length = 0, + spring.constant = 1, + max.sa.movement = 5 + ), + head_args = c("graph"), + fn_name = "layout_with_graphopt" + ) + list2env(.arg_handle$values, environment()) + lifecycle::deprecate_soft( + "3.0.0", + what = I(.arg_handle$what), + details = .arg_handle$details + ) + } + # END GENERATED ARG_HANDLE + ensure_igraph(graph) start[] <- as.numeric(start) niter <- as.double(niter) @@ -1786,6 +2254,7 @@ with_graphopt <- function(...) layout_spec(layout_with_graphopt, ...) #' follows truthfully the original publication by Kamada and Kawai now. #' #' @param graph The input graph. Edge directions are ignored. +#' @inheritParams rlang::args_dots_empty #' @param coords Starting coordinates in a two or three column matrix, #' depending on the `dim` argument. #' Default: `NULL`. @@ -1838,6 +2307,7 @@ with_graphopt <- function(...) layout_spec(layout_with_graphopt, ...) #' layout_with_kk <- function( graph, + ..., coords = NULL, dim = c(2, 3), maxiter = 50 * vcount(graph), @@ -1856,6 +2326,136 @@ layout_with_kk <- function( coolexp = deprecated(), start = deprecated() ) { + # BEGIN GENERATED ARG_HANDLE: layout_with_kk, do not edit, see tools/generate-migrations.R + if (...length() > 0L) { + .arg_handle <- migrate_recover_args( + list(...), + current = list( + coords = coords, + dim = dim, + maxiter = maxiter, + epsilon = epsilon, + kkconst = kkconst, + weights = weights, + minx = minx, + maxx = maxx, + miny = miny, + maxy = maxy, + minz = minz, + maxz = maxz, + niter = niter, + sigma = sigma, + initemp = initemp, + coolexp = coolexp, + start = start + ), + recover_new = c( + "coords", + "dim", + "maxiter", + "epsilon", + "kkconst", + "weights", + "minx", + "maxx", + "miny", + "maxy", + "minz", + "maxz", + "niter", + "sigma", + "initemp", + "coolexp", + "start" + ), + recover_old = c( + "coords", + "dim", + "maxiter", + "epsilon", + "kkconst", + "weights", + "minx", + "maxx", + "miny", + "maxy", + "minz", + "maxz", + "niter", + "sigma", + "initemp", + "coolexp", + "start" + ), + match_names = c( + "coords", + "dim", + "maxiter", + "epsilon", + "kkconst", + "weights", + "minx", + "maxx", + "miny", + "maxy", + "minz", + "maxz", + "niter", + "sigma", + "initemp", + "coolexp", + "start" + ), + match_to = c( + "coords", + "dim", + "maxiter", + "epsilon", + "kkconst", + "weights", + "minx", + "maxx", + "miny", + "maxy", + "minz", + "maxz", + "niter", + "sigma", + "initemp", + "coolexp", + "start" + ), + defaults = list( + coords = NULL, + dim = c(2, 3), + maxiter = 50 * vcount(graph), + epsilon = 0, + kkconst = max(vcount(graph), 1), + weights = NULL, + minx = NULL, + maxx = NULL, + miny = NULL, + maxy = NULL, + minz = NULL, + maxz = NULL, + niter = deprecated(), + sigma = deprecated(), + initemp = deprecated(), + coolexp = deprecated(), + start = deprecated() + ), + head_args = c("graph"), + fn_name = "layout_with_kk" + ) + list2env(.arg_handle$values, environment()) + lifecycle::deprecate_soft( + "3.0.0", + what = I(.arg_handle$what), + details = .arg_handle$details + ) + } + # END GENERATED ARG_HANDLE + # Argument checks if (!missing(coords) && !missing(start)) { cli::cli_abort(c( @@ -1988,6 +2588,7 @@ layout.kamada.kawai <- function(..., params = list()) { #' (). #' #' @param graph The input graph +#' @inheritParams rlang::args_dots_empty #' @param maxiter The maximum number of iterations to perform (150). #' @param maxdelta The maximum change for a vertex during an iteration (the #' number of vertices). @@ -2009,6 +2610,7 @@ layout.kamada.kawai <- function(..., params = list()) { #' @family graph layouts layout_with_lgl <- function( graph, + ..., maxiter = 150, maxdelta = vcount(graph), area = vcount(graph)^2, @@ -2017,6 +2619,76 @@ layout_with_lgl <- function( cellsize = sqrt(sqrt(area)), root = NULL ) { + # BEGIN GENERATED ARG_HANDLE: layout_with_lgl, do not edit, see tools/generate-migrations.R + if (...length() > 0L) { + .arg_handle <- migrate_recover_args( + list(...), + current = list( + maxiter = maxiter, + maxdelta = maxdelta, + area = area, + coolexp = coolexp, + repulserad = repulserad, + cellsize = cellsize, + root = root + ), + recover_new = c( + "maxiter", + "maxdelta", + "area", + "coolexp", + "repulserad", + "cellsize", + "root" + ), + recover_old = c( + "maxiter", + "maxdelta", + "area", + "coolexp", + "repulserad", + "cellsize", + "root" + ), + match_names = c( + "maxiter", + "maxdelta", + "area", + "coolexp", + "repulserad", + "cellsize", + "root" + ), + match_to = c( + "maxiter", + "maxdelta", + "area", + "coolexp", + "repulserad", + "cellsize", + "root" + ), + defaults = list( + maxiter = 150, + maxdelta = vcount(graph), + area = vcount(graph)^2, + coolexp = 1.5, + repulserad = area * vcount(graph), + cellsize = sqrt(sqrt(area)), + root = NULL + ), + head_args = c("graph"), + fn_name = "layout_with_lgl" + ) + list2env(.arg_handle$values, environment()) + lifecycle::deprecate_soft( + "3.0.0", + what = I(.arg_handle$what), + details = .arg_handle$details + ) + } + # END GENERATED ARG_HANDLE + ensure_igraph(graph) if (is.null(root)) { root <- -1 @@ -2169,6 +2841,7 @@ with_mds <- function(...) layout_spec(layout_with_mds, ...) #' For more details, see the reference below. #' #' @param graph The input graph. +#' @inheritParams rlang::args_dots_empty #' @param layers A numeric vector of the layer indices of the vertices. #' Layers are numbered from one. #' Default: `NULL`, igraph calculates the layers automatically. @@ -2355,6 +3028,7 @@ with_mds <- function(...) layout_spec(layout_with_mds, ...) #' layout_with_sugiyama <- function( graph, + ..., layers = NULL, hgap = 1, vgap = 1, @@ -2362,6 +3036,70 @@ layout_with_sugiyama <- function( weights = NULL, attributes = c("default", "all", "none") ) { + # BEGIN GENERATED ARG_HANDLE: layout_with_sugiyama, do not edit, see tools/generate-migrations.R + if (...length() > 0L) { + .arg_handle <- migrate_recover_args( + list(...), + current = list( + layers = layers, + hgap = hgap, + vgap = vgap, + maxiter = maxiter, + weights = weights, + attributes = attributes + ), + recover_new = c( + "layers", + "hgap", + "vgap", + "maxiter", + "weights", + "attributes" + ), + recover_old = c( + "layers", + "hgap", + "vgap", + "maxiter", + "weights", + "attributes" + ), + match_names = c( + "layers", + "hgap", + "vgap", + "maxiter", + "weights", + "attributes" + ), + match_to = c( + "layers", + "hgap", + "vgap", + "maxiter", + "weights", + "attributes" + ), + defaults = list( + layers = NULL, + hgap = 1, + vgap = 1, + maxiter = 100, + weights = NULL, + attributes = c("default", "all", "none") + ), + head_args = c("graph"), + fn_name = "layout_with_sugiyama" + ) + list2env(.arg_handle$values, environment()) + lifecycle::deprecate_soft( + "3.0.0", + what = I(.arg_handle$what), + details = .arg_handle$details + ) + } + # END GENERATED ARG_HANDLE + attributes <- igraph_match_arg(attributes) res <- layout_sugiyama_impl( @@ -2506,6 +3244,7 @@ with_sugiyama <- function(...) layout_spec(layout_with_sugiyama, ...) #' #' @param graphs A list of graph objects. #' @param layouts A list of two-column matrices. +#' @inheritParams rlang::args_dots_empty #' @param method Character constant giving the method to use. Right now only #' `dla` is implemented. #' @param layout A function object, the layout function to use. @@ -2530,7 +3269,34 @@ with_sugiyama <- function(...) layout_spec(layout_with_sugiyama, ...) #' lay <- merge_coords(graphs, layouts) #' g <- disjoint_union(graphs) #' plot(g, layout = lay, vertex.size = 3, labels = NA, edge.color = "black") -merge_coords <- function(graphs, layouts, method = "dla") { +merge_coords <- function( + graphs, + layouts, + ..., + method = "dla" +) { + # BEGIN GENERATED ARG_HANDLE: merge_coords, do not edit, see tools/generate-migrations.R + if (...length() > 0L) { + .arg_handle <- migrate_recover_args( + list(...), + current = list(method = method), + recover_new = c("method"), + recover_old = c("method"), + match_names = c("method"), + match_to = c("method"), + defaults = list(method = "dla"), + head_args = c("graphs", "layouts"), + fn_name = "merge_coords" + ) + list2env(.arg_handle$values, environment()) + lifecycle::deprecate_soft( + "3.0.0", + what = I(.arg_handle$what), + details = .arg_handle$details + ) + } + # END GENERATED ARG_HANDLE + lapply(graphs, ensure_igraph) if (method != "dla") { cli::cli_abort("{.arg method} must be {.str dla}, not {.str {method}}.") @@ -2553,6 +3319,7 @@ merge_coords <- function(graphs, layouts, method = "dla") { #' coordinate separately to fit into the given limits. #' #' @param layout A matrix with two or three columns, the layout to normalize. +#' @inheritParams rlang::args_dots_empty #' @param xmin,xmax The limits for the first coordinate, if one of them or both #' are `NULL` then no normalization is performed along this direction. #' @param ymin,ymax The limits for the second coordinate, if one of them or @@ -2567,6 +3334,7 @@ merge_coords <- function(graphs, layouts, method = "dla") { #' @keywords graphs norm_coords <- function( layout, + ..., xmin = -1, xmax = 1, ymin = -1, @@ -2574,6 +3342,42 @@ norm_coords <- function( zmin = -1, zmax = 1 ) { + # BEGIN GENERATED ARG_HANDLE: norm_coords, do not edit, see tools/generate-migrations.R + if (...length() > 0L) { + .arg_handle <- migrate_recover_args( + list(...), + current = list( + xmin = xmin, + xmax = xmax, + ymin = ymin, + ymax = ymax, + zmin = zmin, + zmax = zmax + ), + recover_new = c("xmin", "xmax", "ymin", "ymax", "zmin", "zmax"), + recover_old = c("xmin", "xmax", "ymin", "ymax", "zmin", "zmax"), + match_names = c("xmin", "xmax", "ymin", "ymax", "zmin", "zmax"), + match_to = c("xmin", "xmax", "ymin", "ymax", "zmin", "zmax"), + defaults = list( + xmin = -1, + xmax = 1, + ymin = -1, + ymax = 1, + zmin = -1, + zmax = 1 + ), + head_args = c("layout"), + fn_name = "norm_coords" + ) + list2env(.arg_handle$values, environment()) + lifecycle::deprecate_soft( + "3.0.0", + what = I(.arg_handle$what), + details = .arg_handle$details + ) + } + # END GENERATED ARG_HANDLE + if (!is.matrix(layout)) { cli::cli_abort( "{.arg layout} must be a matrix, not {.obj_type_friendly {layout}}." @@ -2813,6 +3617,7 @@ layout.drl <- function( #' @aliases drl_defaults igraph.drl.coarsen #' @aliases igraph.drl.coarsest igraph.drl.default igraph.drl.final igraph.drl.refine #' @param graph The input graph, in can be directed or undirected. +#' @inheritParams rlang::args_dots_empty #' @param use.seed Logical, whether to use the coordinates given in the #' `seed` argument as a starting point. #' @param seed A matrix with two columns, the starting coordinates for the @@ -2850,12 +3655,47 @@ layout.drl <- function( #' layout_with_drl <- function( graph, + ..., use.seed = FALSE, seed = matrix(runif(vcount(graph) * 2), ncol = 2), options = drl_defaults$default, weights = NULL, dim = c(2, 3) ) { + # BEGIN GENERATED ARG_HANDLE: layout_with_drl, do not edit, see tools/generate-migrations.R + if (...length() > 0L) { + .arg_handle <- migrate_recover_args( + list(...), + current = list( + use.seed = use.seed, + seed = seed, + options = options, + weights = weights, + dim = dim + ), + recover_new = c("use.seed", "seed", "options", "weights", "dim"), + recover_old = c("use.seed", "seed", "options", "weights", "dim"), + match_names = c("use.seed", "seed", "options", "weights", "dim"), + match_to = c("use.seed", "seed", "options", "weights", "dim"), + defaults = list( + use.seed = FALSE, + seed = matrix(runif(vcount(graph) * 2), ncol = 2), + options = drl_defaults$default, + weights = NULL, + dim = c(2, 3) + ), + head_args = c("graph"), + fn_name = "layout_with_drl" + ) + list2env(.arg_handle$values, environment()) + lifecycle::deprecate_soft( + "3.0.0", + what = I(.arg_handle$what), + details = .arg_handle$details + ) + } + # END GENERATED ARG_HANDLE + ensure_igraph(graph) dim <- igraph_match_arg(dim) diff --git a/man/layout_as_bipartite.Rd b/man/layout_as_bipartite.Rd index e11da252f97..c02f8d368cc 100644 --- a/man/layout_as_bipartite.Rd +++ b/man/layout_as_bipartite.Rd @@ -5,7 +5,14 @@ \alias{as_bipartite} \title{Simple two-row layout for bipartite graphs} \usage{ -layout_as_bipartite(graph, types = NULL, hgap = 1, vgap = 1, maxiter = 100) +layout_as_bipartite( + graph, + types = NULL, + ..., + hgap = 1, + vgap = 1, + maxiter = 100 +) as_bipartite(...) } @@ -18,6 +25,8 @@ given.} \code{NULL} (the default), then the \sQuote{\code{type}} vertex attribute is used.} +\item{...}{Arguments to pass to \code{layout_as_bipartite()}.} + \item{hgap}{Real scalar, the minimum horizontal gap between vertices in the same layer.} @@ -26,8 +35,6 @@ same layer.} \item{maxiter}{Integer scalar, the maximum number of iterations in the crossing minimization stage. 100 is a reasonable default; if you feel that you have too many edge crossings, increase this.} - -\item{...}{Arguments to pass to \code{layout_as_bipartite()}.} } \value{ A matrix with two columns and as many rows as the number of vertices diff --git a/man/layout_as_star.Rd b/man/layout_as_star.Rd index ff4e0f5cd03..777bdfcbd04 100644 --- a/man/layout_as_star.Rd +++ b/man/layout_as_star.Rd @@ -5,20 +5,20 @@ \alias{as_star} \title{Generate coordinates to place the vertices of a graph in a star-shape} \usage{ -layout_as_star(graph, center = V(graph)[1], order = NULL) +layout_as_star(graph, ..., center = V(graph)[1], order = NULL) as_star(...) } \arguments{ \item{graph}{The graph to layout.} +\item{...}{Arguments to pass to \code{layout_as_star()}.} + \item{center}{The ID of the vertex to put in the center. By default it is the first vertex.} \item{order}{Numeric vector, the order of the vertices along the perimeter. The default ordering is given by the vertex IDs.} - -\item{...}{Arguments to pass to \code{layout_as_star()}.} } \value{ A matrix with two columns and as many rows as the number of vertices diff --git a/man/layout_as_tree.Rd b/man/layout_as_tree.Rd index 313ba6b8684..8da0425eb20 100644 --- a/man/layout_as_tree.Rd +++ b/man/layout_as_tree.Rd @@ -7,6 +7,7 @@ \usage{ layout_as_tree( graph, + ..., root = numeric(), circular = FALSE, rootlevel = numeric(), @@ -19,6 +20,8 @@ as_tree(...) \arguments{ \item{graph}{The input graph.} +\item{...}{Passed to \code{layout_as_tree()}.} + \item{root}{The index of the root vertex or root vertices. If this is a non-empty vector then the supplied vertex IDs are used as the roots of the trees (or a single tree if the graph is connected). If it is an empty @@ -45,8 +48,6 @@ not given. See the \code{roots} parameter.} \item{flip.y}{Logical, whether to flip the \sQuote{y} coordinates. The default is flipping because that puts the root vertex on the top.} - -\item{...}{Passed to \code{layout_as_tree()}.} } \value{ A numeric matrix with two columns, and one row for each vertex. diff --git a/man/layout_on_grid.Rd b/man/layout_on_grid.Rd index 65de8d3caa1..b36f1273d8e 100644 --- a/man/layout_on_grid.Rd +++ b/man/layout_on_grid.Rd @@ -5,13 +5,15 @@ \alias{on_grid} \title{Simple grid layout} \usage{ -layout_on_grid(graph, width = 0, height = 0, dim = 2) +layout_on_grid(graph, ..., width = 0, height = 0, dim = 2) on_grid(...) } \arguments{ \item{graph}{The input graph.} +\item{...}{Passed to \code{layout_on_grid()}.} + \item{width}{The number of vertices in a single row of the grid. If this is zero or negative, then for 2d layouts the width of the grid will be the square root of the number of vertices in the graph, rounded up to the next @@ -22,8 +24,6 @@ three dimensional layouts. If this is zero or negative, then it is determinted automatically.} \item{dim}{Two or three. Whether to make 2d or a 3d layout.} - -\item{...}{Passed to \code{layout_on_grid()}.} } \value{ A two-column or three-column matrix. diff --git a/man/layout_randomly.Rd b/man/layout_randomly.Rd index 8618f302551..8bfb119d6d7 100644 --- a/man/layout_randomly.Rd +++ b/man/layout_randomly.Rd @@ -5,17 +5,17 @@ \alias{randomly} \title{Randomly place vertices on a plane or in 3d space} \usage{ -layout_randomly(graph, dim = c(2, 3)) +layout_randomly(graph, ..., dim = c(2, 3)) randomly(...) } \arguments{ \item{graph}{The input graph.} +\item{...}{Parameters to pass to \code{layout_randomly()}.} + \item{dim}{Integer scalar, the dimension of the space to use. It must be 2 or 3.} - -\item{...}{Parameters to pass to \code{layout_randomly()}.} } \value{ A numeric matrix with two or three columns. diff --git a/man/layout_with_dh.Rd b/man/layout_with_dh.Rd index 57c3d4729ae..c9c3a90ec2c 100644 --- a/man/layout_with_dh.Rd +++ b/man/layout_with_dh.Rd @@ -7,6 +7,7 @@ \usage{ layout_with_dh( graph, + ..., coords = NULL, maxiter = 10, fineiter = max(10, log2(vcount(graph))), @@ -23,6 +24,8 @@ with_dh(...) \arguments{ \item{graph}{The graph to lay out. Edge directions are ignored.} +\item{...}{Passed to \code{layout_with_dh()}.} + \item{coords}{Optional starting positions for the vertices. If this argument is not \code{NULL} then it should be an appropriate matrix of starting coordinates.} @@ -48,8 +51,6 @@ energy function.} \item{weight.node.edge.dist}{Weight for the node-edge distance component of the energy function.} - -\item{...}{Passed to \code{layout_with_dh()}.} } \value{ A matrix with two columns, containing the x and y coordinates diff --git a/man/layout_with_drl.Rd b/man/layout_with_drl.Rd index 4e4db7155ea..c5e17f7cd03 100644 --- a/man/layout_with_drl.Rd +++ b/man/layout_with_drl.Rd @@ -13,6 +13,7 @@ \usage{ layout_with_drl( graph, + ..., use.seed = FALSE, seed = matrix(runif(vcount(graph) * 2), ncol = 2), options = drl_defaults$default, @@ -25,6 +26,8 @@ with_drl(...) \arguments{ \item{graph}{The input graph, in can be directed or undirected.} +\item{...}{Passed to \code{layout_with_drl()}.} + \item{use.seed}{Logical, whether to use the coordinates given in the \code{seed} argument as a starting point.} @@ -46,8 +49,6 @@ correspond to stronger connections.} two dimensional or a three dimensional layout. Note that because of the nature of the DrL algorithm, the three dimensional layout takes significantly longer to compute.} - -\item{...}{Passed to \code{layout_with_drl()}.} } \value{ A numeric matrix with two columns. diff --git a/man/layout_with_fr.Rd b/man/layout_with_fr.Rd index 0607b90e9a8..6627fbf1c4f 100644 --- a/man/layout_with_fr.Rd +++ b/man/layout_with_fr.Rd @@ -7,6 +7,7 @@ \usage{ layout_with_fr( graph, + ..., coords = NULL, dim = c(2, 3), niter = 500, @@ -31,6 +32,8 @@ with_fr(...) \arguments{ \item{graph}{The graph to lay out. Edge directions are ignored.} +\item{...}{Passed to \code{layout_with_fr()}.} + \item{coords}{Optional starting positions for the vertices. If this argument is not \code{NULL} then it should be an appropriate matrix of starting coordinates.} @@ -80,8 +83,6 @@ arguments are not supported from igraph version 0.8.0 and are ignored (with a warning).} \item{maxiter}{A deprecated synonym of \code{niter}, for compatibility.} - -\item{...}{Passed to \code{layout_with_fr()}.} } \value{ A two- or three-column matrix, each row giving the coordinates of a diff --git a/man/layout_with_gem.Rd b/man/layout_with_gem.Rd index 700775b1430..f4b36cb3c99 100644 --- a/man/layout_with_gem.Rd +++ b/man/layout_with_gem.Rd @@ -7,6 +7,7 @@ \usage{ layout_with_gem( graph, + ..., coords = NULL, maxiter = 40 * vcount(graph)^2, temp.max = max(vcount(graph), 1), @@ -19,6 +20,8 @@ with_gem(...) \arguments{ \item{graph}{The input graph. Edge directions are ignored.} +\item{...}{Passed to \code{layout_with_gem()}.} + \item{coords}{Starting coordinates in a two or three column matrix, depending on the \code{dim} argument. Default: \code{NULL}.} @@ -37,8 +40,6 @@ is the number of vertices.} \item{temp.init}{Initial local temperature of all vertices. A reasonable default is the square root of the number of vertices.} - -\item{...}{Passed to \code{layout_with_gem()}.} } \value{ A numeric matrix with two columns, and as many rows as the number of diff --git a/man/layout_with_graphopt.Rd b/man/layout_with_graphopt.Rd index 17504d64dc8..5cdd3fb3632 100644 --- a/man/layout_with_graphopt.Rd +++ b/man/layout_with_graphopt.Rd @@ -7,6 +7,7 @@ \usage{ layout_with_graphopt( graph, + ..., start = NULL, niter = 500, charge = 0.001, @@ -21,6 +22,8 @@ with_graphopt(...) \arguments{ \item{graph}{The input graph.} +\item{...}{Passed to \code{layout_with_graphopt()}.} + \item{start}{If given, then it should be a matrix with two columns and one line for each vertex. This matrix will be used as starting positions for the algorithm. If not given, then a random starting matrix is used.} @@ -45,8 +48,6 @@ default value is zero.} \item{max.sa.movement}{Real constant, it gives the maximum amount of movement allowed in a single step along a single axis. The default value is 5.} - -\item{...}{Passed to \code{layout_with_graphopt()}.} } \value{ A numeric matrix with two columns, and a row for each vertex. diff --git a/man/layout_with_kk.Rd b/man/layout_with_kk.Rd index dc06012c022..5b4278c1c9e 100644 --- a/man/layout_with_kk.Rd +++ b/man/layout_with_kk.Rd @@ -7,6 +7,7 @@ \usage{ layout_with_kk( graph, + ..., coords = NULL, dim = c(2, 3), maxiter = 50 * vcount(graph), @@ -31,6 +32,8 @@ with_kk(...) \arguments{ \item{graph}{The input graph. Edge directions are ignored.} +\item{...}{Passed to \code{layout_with_kk()}.} + \item{coords}{Starting coordinates in a two or three column matrix, depending on the \code{dim} argument. Default: \code{NULL}.} @@ -77,8 +80,6 @@ Default: \code{NULL}.} arguments are not supported from igraph version 0.8.0 and are ignored (with a warning).} \item{start}{Deprecated synonym for \code{coords}, for compatibility.} - -\item{...}{Passed to \code{layout_with_kk()}.} } \value{ A numeric matrix with two (dim=2) or three (dim=3) columns, and as diff --git a/man/layout_with_lgl.Rd b/man/layout_with_lgl.Rd index ac9d0269b65..7dd652c3f05 100644 --- a/man/layout_with_lgl.Rd +++ b/man/layout_with_lgl.Rd @@ -7,6 +7,7 @@ \usage{ layout_with_lgl( graph, + ..., maxiter = 150, maxdelta = vcount(graph), area = vcount(graph)^2, @@ -21,6 +22,8 @@ with_lgl(...) \arguments{ \item{graph}{The input graph} +\item{...}{Passed to \code{layout_with_lgl()}.} + \item{maxiter}{The maximum number of iterations to perform (150).} \item{maxdelta}{The maximum change for a vertex during an iteration (the @@ -41,8 +44,6 @@ grid cells are taken into account (the fourth root of the number of \item{root}{The ID of the vertex to place at the middle of the layout. The default value is -1 which means that a random vertex is selected.} - -\item{...}{Passed to \code{layout_with_lgl()}.} } \value{ A numeric matrix with two columns and as many rows as vertices. diff --git a/man/layout_with_sugiyama.Rd b/man/layout_with_sugiyama.Rd index 4d6ce7b9cab..1ee793d60d7 100644 --- a/man/layout_with_sugiyama.Rd +++ b/man/layout_with_sugiyama.Rd @@ -7,6 +7,7 @@ \usage{ layout_with_sugiyama( graph, + ..., layers = NULL, hgap = 1, vgap = 1, @@ -20,6 +21,8 @@ with_sugiyama(...) \arguments{ \item{graph}{The input graph.} +\item{...}{Passed to \code{layout_with_sugiyama()}.} + \item{layers}{A numeric vector of the layer indices of the vertices. Layers are numbered from one. Default: \code{NULL}, igraph calculates the layers automatically.} @@ -45,8 +48,6 @@ graph. \sQuote{default} keeps the \sQuote{size}, \sQuote{size2}, \sQuote{arrow.mode} and \sQuote{arrow.size} edge attributes. \sQuote{all} keep all graph, vertex and edge attributes, \sQuote{none} keeps none of them.} - -\item{...}{Passed to \code{layout_with_sugiyama()}.} } \value{ A list with the components: diff --git a/man/merge_coords.Rd b/man/merge_coords.Rd index f5f9c985c22..2d3d3e4a898 100644 --- a/man/merge_coords.Rd +++ b/man/merge_coords.Rd @@ -5,7 +5,7 @@ \alias{layout_components} \title{Merging graph layouts} \usage{ -merge_coords(graphs, layouts, method = "dla") +merge_coords(graphs, layouts, ..., method = "dla") layout_components(graph, layout = layout_with_kk, ...) } @@ -14,15 +14,15 @@ layout_components(graph, layout = layout_with_kk, ...) \item{layouts}{A list of two-column matrices.} +\item{\dots}{Additional arguments to pass to the \code{layout} layout +function.} + \item{method}{Character constant giving the method to use. Right now only \code{dla} is implemented.} \item{graph}{The input graph.} \item{layout}{A function object, the layout function to use.} - -\item{\dots}{Additional arguments to pass to the \code{layout} layout -function.} } \value{ A matrix with two columns and as many lines as the total number of diff --git a/man/norm_coords.Rd b/man/norm_coords.Rd index 1027b7734f5..7938c1efa58 100644 --- a/man/norm_coords.Rd +++ b/man/norm_coords.Rd @@ -6,6 +6,7 @@ \usage{ norm_coords( layout, + ..., xmin = -1, xmax = 1, ymin = -1, @@ -17,6 +18,8 @@ norm_coords( \arguments{ \item{layout}{A matrix with two or three columns, the layout to normalize.} +\item{...}{These dots are for future extensions and must be empty.} + \item{xmin, xmax}{The limits for the first coordinate, if one of them or both are \code{NULL} then no normalization is performed along this direction.} diff --git a/tests/testthat/test-layout.R b/tests/testthat/test-layout.R index dbdc180bbee..02e43e66d58 100644 --- a/tests/testthat/test-layout.R +++ b/tests/testthat/test-layout.R @@ -370,12 +370,26 @@ test_that("layout normalization handles all-NaN coordinates correctly", { # Test that norm_coords works with all-NaN layouts layout_all_nan <- matrix(NaN, nrow = 5, ncol = 2) - normalized_layout <- norm_coords(layout_all_nan, 0, 1, 0, 1) + normalized_layout <- norm_coords( + layout_all_nan, + xmin = 0, + xmax = 1, + ymin = 0, + ymax = 1 + ) expect_equal(normalized_layout, matrix(c(rep(0.5, 5), rep(0.5, 5)), ncol = 2)) # Test 3D layout normalization with all-NaN coordinates layout_3d_nan <- matrix(NaN, nrow = 3, ncol = 3) - normalized_3d <- norm_coords(layout_3d_nan, 0, 1, 0, 1, 0, 1) + normalized_3d <- norm_coords( + layout_3d_nan, + xmin = 0, + xmax = 1, + ymin = 0, + ymax = 1, + zmin = 0, + zmax = 1 + ) expect_equal(normalized_3d, matrix(rep(0.5, 9), ncol = 3)) }) @@ -481,3 +495,443 @@ test_that("modifier types are correctly identified", { expect_equal(norm$type, "post") expect_true(is.function(norm$apply)) }) + +# ---- ellipsis migration: argument coverage ---------------------------- + +test_that("layout_as_bipartite() covers tail args and recovers positional calls", { + g <- make_full_bipartite_graph(3, 2) + + # All tail arguments by name. + l <- layout_as_bipartite(g, V(g)$type, hgap = 3, vgap = 2, maxiter = 50) + expect_equal(dim(l), c(5, 2)) + # The two vertex types end up on rows vgap apart. + expect_equal(sort(unique(l[, 2])), c(0, 2)) + # Vertices of the same type are hgap apart. + expect_equal(diff(sort(l[!V(g)$type, 1])), c(3, 3)) + + # The legacy positional call recovers hgap and warns. + igraph_with_seed(1, { + lifecycle::expect_deprecated( + res <- layout_as_bipartite(g, V(g)$type, 3) + ) + }) + igraph_with_seed(1, { + ref <- layout_as_bipartite(g, V(g)$type, hgap = 3) + }) + expect_identical(res, ref) +}) + +test_that("layout_as_star() covers tail args and recovers old-style calls", { + g <- make_star(6) + + # All tail arguments by name. + l <- layout_as_star(g, center = 3, order = c(3, 6, 5, 4, 2, 1)) + expect_equal(dim(l), c(6, 2)) + # The chosen center sits at the origin. + expect_equal(l[3, ], c(0, 0)) + # A non-default perimeter order moves the remaining vertices. + expect_false(identical(l, layout_as_star(g, center = 3))) + + # Recovering `center` from an old-style call currently errors: + # its default `V(graph)[1]` is re-evaluated inside the generated block + # and is never identical() to the value of the formal. + # Exercise the legacy recovery path through `order` instead. + lifecycle::expect_deprecated( + res <- layout_as_star(g, ord = c(3, 6, 5, 4, 2, 1)) + ) + expect_identical(res, layout_as_star(g, order = c(3, 6, 5, 4, 2, 1))) +}) + +test_that("layout_as_tree() covers tail args and recovers positional calls", { + g <- make_tree(7, 2) + + # flip.y = FALSE keeps y growing with the depth instead of flipping it. + l <- layout_as_tree( + g, + root = 1, + circular = FALSE, + mode = "all", + flip.y = FALSE + ) + expect_equal(l[, 2], c(0, 1, 1, 2, 2, 2, 2)) + + # circular = TRUE puts the levels on concentric circles around the root. + lc <- layout_as_tree(g, root = 1, circular = TRUE, flip.y = FALSE) + expect_equal(sqrt(rowSums(lc^2)), c(0, 1, 1, 2, 2, 2, 2)) + + # rootlevel offsets the roots of a forest against each other. + gf <- make_tree(3, 2) %du% make_tree(4, 2) + lf <- layout_as_tree( + gf, + root = c(1, 4), + rootlevel = c(0, 1), + mode = "all", + flip.y = FALSE + ) + expect_equal(lf[4, 2] - lf[1, 2], 1) + + # The legacy positional call recovers root and warns. + g2 <- make_tree(7, 2, mode = "undirected") + lifecycle::expect_deprecated(res <- layout_as_tree(g2, 2)) + expect_identical(res, layout_as_tree(g2, root = 2)) +}) + +test_that("layout_on_grid() covers tail args and recovers positional calls", { + g <- make_ring(12) + + # width fixes the number of grid columns. + l <- layout_on_grid(g, width = 3) + expect_equal(dim(l), c(12, 2)) + expect_equal(max(l[, 1]), 2) + expect_equal(max(l[, 2]), 3) + + # height is honoured by the three-dimensional grid. + l3 <- layout_on_grid(g, width = 2, height = 3, dim = 3) + expect_equal(dim(l3), c(12, 3)) + expect_equal(max(l3[, 1]), 1) + expect_equal(max(l3[, 2]), 2) + expect_equal(sort(unique(l3[, 3])), c(0, 1)) + + # The legacy positional call recovers width and warns. + lifecycle::expect_deprecated(res <- layout_on_grid(g, 3)) + expect_identical(res, layout_on_grid(g, width = 3)) +}) + +test_that("layout_randomly() covers tail args and recovers positional calls", { + g <- make_ring(10) + + igraph_with_seed(42, { + l <- layout_randomly(g, dim = 3) + }) + expect_equal(dim(l), c(10, 3)) + expect_true(all(is.finite(l))) + + # The legacy positional call recovers dim and warns. + igraph_with_seed(5, { + lifecycle::expect_deprecated(res <- layout_randomly(g, 3)) + }) + igraph_with_seed(5, { + ref <- layout_randomly(g, dim = 3) + }) + expect_identical(res, ref) +}) + +test_that("layout_with_dh() covers tail args and recovers positional calls", { + g <- make_ring(8) + coords <- layout_in_circle(g) + + # All tail arguments by name. + igraph_with_seed(42, { + l <- layout_with_dh( + g, + coords = coords, + maxiter = 5, + fineiter = 5, + cool.fact = 0.5, + weight.node.dist = 2, + weight.border = 0.1, + weight.edge.lengths = 0.05, + weight.edge.crossings = 0.5, + weight.node.edge.dist = 0.1 + ) + }) + expect_equal(dim(l), c(8, 2)) + expect_true(all(is.finite(l))) + + # The legacy positional call recovers coords and warns. + igraph_with_seed(1, { + lifecycle::expect_deprecated(res <- layout_with_dh(g, coords, maxiter = 5)) + }) + igraph_with_seed(1, { + ref <- layout_with_dh(g, coords = coords, maxiter = 5) + }) + expect_identical(res, ref) +}) + +test_that("layout_with_drl() covers tail args and recovers positional calls", { + g <- make_ring(10) + igraph_with_seed(42, { + seed_2d <- matrix(runif(20), ncol = 2) + seed_3d <- matrix(runif(30), ncol = 3) + }) + + # All tail arguments by name, in two dimensions. + igraph_with_seed(42, { + l <- layout_with_drl( + g, + use.seed = TRUE, + seed = seed_2d, + options = drl_defaults$coarsen, + weights = rep(2, ecount(g)), + dim = 2 + ) + }) + expect_equal(dim(l), c(10, 2)) + expect_true(all(is.finite(l))) + + # dim = 3 needs a three-column seed matrix. + igraph_with_seed(42, { + l3 <- layout_with_drl(g, use.seed = TRUE, seed = seed_3d, dim = 3) + }) + expect_equal(dim(l3), c(10, 3)) + + # The legacy positional call recovers use.seed and warns. + # Evaluating the argument defaults inside the recovery block consumes RNG + # draws, so compare against the equally recovered abbreviated form instead + # of the new-style call. + igraph_with_seed(7, { + lifecycle::expect_deprecated( + res <- layout_with_drl(g, TRUE, seed = seed_2d) + ) + }) + expect_equal(dim(res), c(10, 2)) + expect_true(all(is.finite(res))) + igraph_with_seed(7, { + lifecycle::expect_deprecated( + ref <- layout_with_drl(g, use.se = TRUE, seed = seed_2d) + ) + }) + expect_identical(res, ref) +}) + +test_that("layout_with_fr() covers the remaining tail args and recovery", { + g <- make_ring(10) + coords <- layout_in_circle(g) + + # Pass coords, grid, weights and the 2d bounds by name. + # The niter, start.temp and dim arguments are already covered above. + igraph_with_seed(42, { + l <- layout_with_fr( + g, + coords = coords, + niter = 20, + grid = "nogrid", + weights = rep(0.5, ecount(g)), + minx = rep(-3, 10), + maxx = rep(3, 10), + miny = rep(-3, 10), + maxy = rep(3, 10) + ) + }) + expect_equal(dim(l), c(10, 2)) + # The box constraints are honoured. + expect_true(all(l >= -3 & l <= 3)) + + # The z bounds apply to the three-dimensional variant. + igraph_with_seed(42, { + l3 <- layout_with_fr( + g, + dim = 3, + niter = 20, + minz = rep(-2, 10), + maxz = rep(2, 10) + ) + }) + expect_equal(dim(l3), c(10, 3)) + expect_true(all(l3[, 3] >= -2 & l3[, 3] <= 2)) + + # The legacy positional call recovers coords and warns. + igraph_with_seed(1, { + lifecycle::expect_deprecated(res <- layout_with_fr(g, coords, niter = 20)) + }) + igraph_with_seed(1, { + ref <- layout_with_fr(g, coords = coords, niter = 20) + }) + expect_identical(res, ref) +}) + +test_that("layout_with_gem() covers tail args and recovers positional calls", { + g <- make_ring(8) + coords <- layout_in_circle(g) + + # All tail arguments by name. + igraph_with_seed(42, { + l <- layout_with_gem( + g, + coords = coords, + maxiter = 100, + temp.max = 5, + temp.min = 0.5, + temp.init = 2 + ) + }) + expect_equal(dim(l), c(8, 2)) + expect_true(all(is.finite(l))) + + # The legacy positional call recovers coords and warns. + igraph_with_seed(1, { + lifecycle::expect_deprecated(res <- layout_with_gem(g, coords)) + }) + igraph_with_seed(1, { + ref <- layout_with_gem(g, coords = coords) + }) + expect_identical(res, ref) +}) + +test_that("layout_with_graphopt() covers tail args and recovers positional calls", { + g <- make_ring(8) + start <- layout_in_circle(g) + + # All tail arguments by name. + igraph_with_seed(42, { + l <- layout_with_graphopt( + g, + start = start, + niter = 50, + charge = 0.01, + mass = 10, + spring.length = 1, + spring.constant = 2, + max.sa.movement = 1 + ) + }) + expect_equal(dim(l), c(8, 2)) + expect_true(all(is.finite(l))) + + # The legacy positional call recovers start and warns. + igraph_with_seed(1, { + lifecycle::expect_deprecated(res <- layout_with_graphopt(g, start)) + }) + igraph_with_seed(1, { + ref <- layout_with_graphopt(g, start = start) + }) + expect_identical(res, ref) +}) + +test_that("layout_with_kk() covers the remaining tail args and recovery", { + g <- make_ring(10) + coords <- layout_in_circle(g) + + # Pass epsilon, kkconst, weights and the 2d bounds by name. + # The coords, dim and maxiter arguments are already covered above. + l <- layout_with_kk( + g, + coords = coords, + maxiter = 50, + epsilon = 1e-3, + kkconst = 5, + weights = rep(2, ecount(g)), + minx = rep(-4, 10), + maxx = rep(4, 10), + miny = rep(-4, 10), + maxy = rep(4, 10) + ) + expect_equal(dim(l), c(10, 2)) + # The box constraints are honoured. + expect_true(all(l >= -4 & l <= 4)) + + # The z bounds apply to the three-dimensional variant. + igraph_with_seed(2, { + l3 <- layout_with_kk( + g, + dim = 3, + maxiter = 50, + minz = rep(0, 10), + maxz = rep(1, 10) + ) + }) + expect_equal(dim(l3), c(10, 3)) + expect_true(all(l3[, 3] >= 0 & l3[, 3] <= 1)) + + # The legacy positional call recovers coords and warns. + lifecycle::expect_deprecated(res <- layout_with_kk(g, coords, maxiter = 50)) + expect_identical(res, layout_with_kk(g, coords = coords, maxiter = 50)) +}) + +test_that("layout_with_lgl() covers tail args and recovers positional calls", { + g <- make_ring(8) + + # All tail arguments by name. + igraph_with_seed(42, { + l <- layout_with_lgl( + g, + maxiter = 50, + maxdelta = 5, + area = 64, + coolexp = 1.2, + repulserad = 512, + cellsize = 4, + root = 1 + ) + }) + expect_equal(dim(l), c(8, 2)) + expect_true(all(is.finite(l))) + + # The legacy positional call recovers maxiter and warns. + igraph_with_seed(1, { + lifecycle::expect_deprecated(res <- layout_with_lgl(g, 50)) + }) + igraph_with_seed(1, { + ref <- layout_with_lgl(g, maxiter = 50) + }) + expect_identical(res, ref) +}) + +test_that("layout_with_sugiyama() covers tail args and recovers positional calls", { + g <- graph_from_literal(A -+ B:C, B -+ C:D) + + # All tail arguments by name. + igraph_with_seed(42, { + res <- layout_with_sugiyama( + g, + layers = c(1, 2, 3, 4), + hgap = 2, + vgap = 2, + maxiter = 50, + weights = rep(1, ecount(g)), + attributes = "all" + ) + }) + expect_equal(nrow(res$layout), 4) + # The two edges spanning more than one layer get one dummy vertex each. + expect_equal(nrow(res$layout.dummy), 2) + # Consecutive layers are vgap apart. + expect_equal(diff(sort(unique(res$layout[, 2]))), rep(2, 3)) + # attributes = "all" copies the vertex names onto the extended graph. + expect_equal(V(res$extd_graph)$name[1:4], c("A", "B", "C", "D")) + + # The legacy positional call recovers layers and warns. + igraph_with_seed(1, { + lifecycle::expect_deprecated( + res2 <- layout_with_sugiyama(g, c(1, 2, 3, 4)) + ) + }) + igraph_with_seed(1, { + ref <- layout_with_sugiyama(g, layers = c(1, 2, 3, 4)) + }) + expect_identical(res2$layout, ref$layout) + expect_identical(res2$layout.dummy, ref$layout.dummy) +}) + +test_that("merge_coords() covers tail args and recovers positional calls", { + graphs <- list(make_ring(5), make_ring(4)) + layouts <- lapply(graphs, layout_in_circle) + + # method is the single tail argument and only supports "dla". + igraph_with_seed(42, { + l <- merge_coords(graphs, layouts, method = "dla") + }) + expect_true(is.matrix(l)) + expect_equal(dim(l), c(9, 2)) + expect_true(all(is.finite(l))) + + # The legacy positional call recovers method and warns. + igraph_with_seed(1, { + lifecycle::expect_deprecated(res <- merge_coords(graphs, layouts, "dla")) + }) + igraph_with_seed(1, { + ref <- merge_coords(graphs, layouts, method = "dla") + }) + expect_identical(res, ref) +}) + +test_that("norm_coords() recovers positional calls", { + # The named tail arguments are covered by the all-NaN normalization test. + lay <- matrix(as.numeric(1:10), ncol = 2) + + # The legacy positional call recovers xmin and warns. + lifecycle::expect_deprecated(res <- norm_coords(lay, 0)) + expect_identical(res, norm_coords(lay, xmin = 0)) + # xmin = 0 rescales the first column into [0, 1]. + expect_equal(range(res[, 1]), c(0, 1)) +}) diff --git a/tools/migrations/layout.R b/tools/migrations/layout.R new file mode 100644 index 00000000000..52e0df783c3 --- /dev/null +++ b/tools/migrations/layout.R @@ -0,0 +1,303 @@ +# Argument-signature migrations: layout +# Schema: see tools/migrations.R. Regenerate with: +# Rscript tools/generate-migrations.R + +migrations <- list( + layout_as_bipartite = list( + old = function(graph, types, hgap, vgap, maxiter) {}, + new = function( + graph, + types = NULL, + ..., + hgap = 1, + vgap = 1, + maxiter = 100 + ) {}, + when = "3.0.0" + ), + + layout_as_star = list( + old = function(graph, center, order) {}, + new = function( + graph, + ..., + center = V(graph)[1], + order = NULL + ) {}, + when = "3.0.0" + ), + + layout_as_tree = list( + old = function(graph, root, circular, rootlevel, mode, flip.y) {}, + new = function( + graph, + ..., + root = numeric(), + circular = FALSE, + rootlevel = numeric(), + mode = c("out", "in", "all"), + flip.y = TRUE + ) {}, + when = "3.0.0" + ), + + layout_on_grid = list( + old = function(graph, width, height, dim) {}, + new = function( + graph, + ..., + width = 0, + height = 0, + dim = 2 + ) {}, + when = "3.0.0" + ), + + layout_randomly = list( + old = function(graph, dim) {}, + new = function( + graph, + ..., + dim = c(2, 3) + ) {}, + when = "3.0.0" + ), + + layout_with_dh = list( + old = function( + graph, + coords, + maxiter, + fineiter, + cool.fact, + weight.node.dist, + weight.border, + weight.edge.lengths, + weight.edge.crossings, + weight.node.edge.dist + ) {}, + new = function( + graph, + ..., + coords = NULL, + maxiter = 10, + fineiter = max(10, log2(vcount(graph))), + cool.fact = 0.75, + weight.node.dist = 1.0, + weight.border = 0.0, + weight.edge.lengths = edge_density(graph) / 10, + weight.edge.crossings = 1.0 - sqrt(edge_density(graph)), + weight.node.edge.dist = 0.2 * (1 - edge_density(graph)) + ) {}, + when = "3.0.0" + ), + + layout_with_drl = list( + old = function(graph, use.seed, seed, options, weights, dim) {}, + new = function( + graph, + ..., + use.seed = FALSE, + seed = matrix(runif(vcount(graph) * 2), ncol = 2), + options = drl_defaults$default, + weights = NULL, + dim = c(2, 3) + ) {}, + when = "3.0.0" + ), + + layout_with_fr = list( + old = function( + graph, + coords, + dim, + niter, + start.temp, + grid, + weights, + minx, + maxx, + miny, + maxy, + minz, + maxz, + coolexp, + maxdelta, + area, + repulserad, + maxiter + ) {}, + new = function( + graph, + ..., + coords = NULL, + dim = c(2, 3), + niter = 500, + start.temp = sqrt(vcount(graph)), + grid = c("auto", "grid", "nogrid"), + weights = NULL, + minx = NULL, + maxx = NULL, + miny = NULL, + maxy = NULL, + minz = NULL, + maxz = NULL, + coolexp = deprecated(), + maxdelta = deprecated(), + area = deprecated(), + repulserad = deprecated(), + maxiter = deprecated() + ) {}, + when = "3.0.0" + ), + + layout_with_gem = list( + old = function(graph, coords, maxiter, temp.max, temp.min, temp.init) {}, + new = function( + graph, + ..., + coords = NULL, + maxiter = 40 * vcount(graph)^2, + temp.max = max(vcount(graph), 1), + temp.min = 1 / 10, + temp.init = sqrt(max(vcount(graph), 1)) + ) {}, + when = "3.0.0" + ), + + layout_with_graphopt = list( + old = function( + graph, + start, + niter, + charge, + mass, + spring.length, + spring.constant, + max.sa.movement + ) {}, + new = function( + graph, + ..., + start = NULL, + niter = 500, + charge = 0.001, + mass = 30, + spring.length = 0, + spring.constant = 1, + max.sa.movement = 5 + ) {}, + when = "3.0.0" + ), + + layout_with_kk = list( + old = function( + graph, + coords, + dim, + maxiter, + epsilon, + kkconst, + weights, + minx, + maxx, + miny, + maxy, + minz, + maxz, + niter, + sigma, + initemp, + coolexp, + start + ) {}, + new = function( + graph, + ..., + coords = NULL, + dim = c(2, 3), + maxiter = 50 * vcount(graph), + epsilon = 0.0, + kkconst = max(vcount(graph), 1), + weights = NULL, + minx = NULL, + maxx = NULL, + miny = NULL, + maxy = NULL, + minz = NULL, + maxz = NULL, + niter = deprecated(), + sigma = deprecated(), + initemp = deprecated(), + coolexp = deprecated(), + start = deprecated() + ) {}, + when = "3.0.0" + ), + + layout_with_lgl = list( + old = function( + graph, + maxiter, + maxdelta, + area, + coolexp, + repulserad, + cellsize, + root + ) {}, + new = function( + graph, + ..., + maxiter = 150, + maxdelta = vcount(graph), + area = vcount(graph)^2, + coolexp = 1.5, + repulserad = area * vcount(graph), + cellsize = sqrt(sqrt(area)), + root = NULL + ) {}, + when = "3.0.0" + ), + + layout_with_sugiyama = list( + old = function(graph, layers, hgap, vgap, maxiter, weights, attributes) {}, + new = function( + graph, + ..., + layers = NULL, + hgap = 1, + vgap = 1, + maxiter = 100, + weights = NULL, + attributes = c("default", "all", "none") + ) {}, + when = "3.0.0" + ), + + merge_coords = list( + old = function(graphs, layouts, method) {}, + new = function( + graphs, + layouts, + ..., + method = "dla" + ) {}, + when = "3.0.0" + ), + + norm_coords = list( + old = function(layout, xmin, xmax, ymin, ymax, zmin, zmax) {}, + new = function( + layout, + ..., + xmin = -1, + xmax = 1, + ymin = -1, + ymax = 1, + zmin = -1, + zmax = 1 + ) {}, + when = "3.0.0" + ) +)