Nothing
#' Merging graph layouts
#'
#' @description
#' `r lifecycle::badge("deprecated")`
#'
#' `piecewise.layout()` was renamed to [layout_components()] to create a more
#' consistent API.
#' @inheritParams layout_components
#' @keywords internal
#' @export
piecewise.layout <- function(graph, layout = layout_with_kk, ...) {
# nocov start
lifecycle::deprecate_soft(
"2.0.0",
"piecewise.layout()",
"layout_components()"
)
layout_components(graph = graph, layout = layout, ...)
} # nocov end
#' The Sugiyama graph layout generator
#'
#' @description
#' `r lifecycle::badge("deprecated")`
#'
#' `layout.sugiyama()` was renamed to [layout_with_sugiyama()] to create a more
#' consistent API.
#' @inheritParams layout_with_sugiyama
#' @keywords internal
#' @export
layout.sugiyama <- function(
graph,
layers = NULL,
hgap = 1,
vgap = 1,
maxiter = 100,
weights = NULL,
attributes = c("default", "all", "none")
) {
# nocov start
lifecycle::deprecate_soft(
"2.0.0",
"layout.sugiyama()",
"layout_with_sugiyama()"
)
layout_with_sugiyama(
graph = graph,
layers = layers,
hgap = hgap,
vgap = vgap,
maxiter = maxiter,
weights = weights,
attributes = attributes
)
} # nocov end
#' Generate coordinates to place the vertices of a graph in a star-shape
#'
#' @description
#' `r lifecycle::badge("deprecated")`
#'
#' `layout.star()` was renamed to [layout_as_star()] to create a more
#' consistent API.
#' @inheritParams layout_as_star
#' @keywords internal
#' @export
layout.star <- function(graph, center = V(graph)[1], order = NULL) {
# nocov start
lifecycle::deprecate_soft("2.0.0", "layout.star()", "layout_as_star()")
layout_as_star(graph = graph, center = center, order = order)
} # nocov end
#' Normalize coordinates for plotting graphs
#'
#' @description
#' `r lifecycle::badge("deprecated")`
#'
#' `layout.norm()` was renamed to [norm_coords()] to create a more
#' consistent API.
#' @inheritParams norm_coords
#' @keywords internal
#' @export
layout.norm <- function(
layout,
xmin = -1,
xmax = 1,
ymin = -1,
ymax = 1,
zmin = -1,
zmax = 1
) {
# nocov start
lifecycle::deprecate_soft("2.0.0", "layout.norm()", "norm_coords()")
norm_coords(
layout = layout,
xmin = xmin,
xmax = xmax,
ymin = ymin,
ymax = ymax,
zmin = zmin,
zmax = zmax
)
} # nocov end
#' Merging graph layouts
#'
#' @description
#' `r lifecycle::badge("deprecated")`
#'
#' `layout.merge()` was renamed to [merge_coords()] to create a more
#' consistent API.
#' @inheritParams merge_coords
#' @keywords internal
#' @export
layout.merge <- function(graphs, layouts, method = "dla") {
# nocov start
lifecycle::deprecate_soft("2.0.0", "layout.merge()", "merge_coords()")
merge_coords(graphs = graphs, layouts = layouts, method = method)
} # nocov end
#' Graph layout by multidimensional scaling
#'
#' @description
#' `r lifecycle::badge("deprecated")`
#'
#' `layout.mds()` was renamed to [layout_with_mds()] to create a more
#' consistent API.
#' @inheritParams layout_with_mds
#' @keywords internal
#' @export
layout.mds <- function(
graph,
dist = NULL,
dim = 2,
options = arpack_defaults()
) {
# nocov start
lifecycle::deprecate_soft("2.0.0", "layout.mds()", "layout_with_mds()")
layout_with_mds(graph = graph, dist = dist, dim = dim, options = options)
} # nocov end
#' Simple grid layout
#'
#' @description
#' `r lifecycle::badge("deprecated")`
#'
#' `layout.grid()` was renamed to [layout_on_grid()] to create a more
#' consistent API.
#' @inheritParams layout_on_grid
#' @keywords internal
#' @export
layout.grid <- function(graph, width = 0, height = 0, dim = 2) {
# nocov start
lifecycle::deprecate_soft("2.0.0", "layout.grid()", "layout_on_grid()")
layout_on_grid(graph = graph, width = width, height = height, dim = dim)
} # nocov end
#' The graphopt layout algorithm
#'
#' @description
#' `r lifecycle::badge("deprecated")`
#'
#' `layout.graphopt()` was renamed to [layout_with_graphopt()] to create a more
#' consistent API.
#' @inheritParams layout_with_graphopt
#' @keywords internal
#' @export
layout.graphopt <- function(
graph,
start = NULL,
niter = 500,
charge = 0.001,
mass = 30,
spring.length = 0,
spring.constant = 1,
max.sa.movement = 5
) {
# nocov start
lifecycle::deprecate_soft(
"2.0.0",
"layout.graphopt()",
"layout_with_graphopt()"
)
layout_with_graphopt(
graph = graph,
start = start,
niter = niter,
charge = charge,
mass = mass,
spring.length = spring.length,
spring.constant = spring.constant,
max.sa.movement = max.sa.movement
)
} # nocov end
#' The GEM layout algorithm
#'
#' @description
#' `r lifecycle::badge("deprecated")`
#'
#' `layout.gem()` was renamed to [layout_with_gem()] to create a more
#' consistent API.
#' @inheritParams layout_with_gem
#' @keywords internal
#' @export
layout.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))
) {
# nocov start
lifecycle::deprecate_soft("2.0.0", "layout.gem()", "layout_with_gem()")
layout_with_gem(
graph = graph,
coords = coords,
maxiter = maxiter,
temp.max = temp.max,
temp.min = temp.min,
temp.init = temp.init
)
} # nocov end
#' The Davidson-Harel layout algorithm
#'
#' @description
#' `r lifecycle::badge("deprecated")`
#'
#' `layout.davidson.harel()` was renamed to [layout_with_dh()] to create a more
#' consistent API.
#' @inheritParams layout_with_dh
#' @keywords internal
#' @export
layout.davidson.harel <- 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))
) {
# nocov start
lifecycle::deprecate_soft(
"2.0.0",
"layout.davidson.harel()",
"layout_with_dh()"
)
layout_with_dh(
graph = graph,
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
)
} # nocov end
#' Simple two-row layout for bipartite graphs
#'
#' @description
#' `r lifecycle::badge("deprecated")`
#'
#' `layout.bipartite()` was renamed to [layout_as_bipartite()] to create a more
#' consistent API.
#' @inheritParams layout_as_bipartite
#' @keywords internal
#' @export
layout.bipartite <- function(
graph,
types = NULL,
hgap = 1,
vgap = 1,
maxiter = 100
) {
# nocov start
lifecycle::deprecate_soft(
"2.0.0",
"layout.bipartite()",
"layout_as_bipartite()"
)
layout_as_bipartite(
graph = graph,
types = types,
hgap = hgap,
vgap = vgap,
maxiter = maxiter
)
} # nocov end
#' Choose an appropriate graph layout algorithm automatically
#'
#' @description
#' `r lifecycle::badge("deprecated")`
#'
#' `layout.auto()` was renamed to [layout_nicely()] to create a more
#' consistent API.
#' @inheritParams layout_nicely
#' @keywords internal
#' @export
layout.auto <- function(graph, dim = 2, ...) {
# nocov start
lifecycle::deprecate_soft("2.0.0", "layout.auto()", "layout_nicely()")
layout_nicely(graph = graph, dim = dim, ...)
} # nocov end
## ----------------------------------------------------------------
##
## IGraph R package
## Copyright (C) 2003-2014 Gabor Csardi <csardi.gabor@gmail.com>
## 334 Harvard street, Cambridge, MA 02139 USA
##
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 2 of the License, or
## (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program; if not, write to the Free Software
## Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
## 02110-1301 USA
##
## ----------------------------------------------------------------
## ----------------------------------------------------------------
## This is the new layout API
## ----------------------------------------------------------------
#' Graph layouts
#'
#' This is a generic function to apply a layout function to
#' a graph.
#'
#' There are two ways to calculate graph layouts in igraph.
#' The first way is to call a layout function (they all have
#' prefix `layout_()` on a graph, to get the vertex coordinates.
#'
#' The second way (new in igraph 0.8.0), has two steps, and it
#' is more flexible. First you call a layout specification
#' function (the one without the `layout_()` prefix, and
#' then `layout_()` (or [add_layout_()]) to
#' perform the layouting.
#'
#' The second way is preferred, as it is more flexible. It allows
#' operations before and after the layouting. E.g. using the
#' `component_wise()` argument, the layout can be calculated
#' separately for each component, and then merged to get the
#' final results.
#'
#' @aliases layout
#' @section Modifiers:
#' Modifiers modify how a layout calculation is performed.
#' Modifiers are applied in the order they are specified as arguments to
#' `layout_()`.
#'
#' There are two types of modifiers:
#' \itemize{
#' \item **Pre-layout modifiers** affect how the layout is calculated.
#' Only one pre-layout modifier can be used at a time.
#' \item **Post-layout modifiers** transform the resulting coordinates.
#' Multiple post-layout modifiers can be chained together.
#' }
#'
#' Currently implemented modifiers:
#' \itemize{
#' \item `component_wise()` (pre-layout) calculates the layout separately
#' for each component of the graph, and then merges them.
#' \item `normalize()` (post-layout) scales the layout to a square.
#' }
#'
#' Custom modifiers can be created using the `layout_modifier()` function.
#' A custom modifier must specify:
#' \itemize{
#' \item `id`: A unique identifier string for the modifier
#' \item `type`: Either `"pre"` for pre-layout or `"post"` for post-layout
#' \item `args`: A list of arguments to pass to the apply function
#' \item `apply`: A function with signature
#' `function(graph, layout, modifier_args)` that performs the modification.
#' For pre-layout modifiers, `layout` is the layout specification.
#' For post-layout modifiers, `layout` is the coordinate matrix to transform.
#' }
#'
#' @param graph The input graph.
#' @param layout The layout specification. It must be a call
#' to a layout specification function.
#' @param ... Further modifiers, see a complete list below.
#' For the [print()] methods, it is ignored.
#' @return The return value of the layout function, usually a
#' two column matrix. For 3D layouts a three column matrix.
#'
#' @seealso [add_layout_()] to add the layout to the
#' graph as an attribute.
#' @export
#' @family graph layouts
#' @examples
#' g <- make_ring(10) + make_full_graph(5)
#' coords <- layout_(g, as_star())
#' plot(g, layout = coords)
#'
#' # Using modifiers
#' g <- make_ring(10) + make_ring(5)
#' coords <- layout_(g, in_circle(), component_wise(), normalize())
#' plot(g, layout = coords)
#'
#' # Creating a custom post-layout modifier
#' scale_by <- function(factor) {
#' layout_modifier(
#' id = "scale_by",
#' type = "post",
#' args = list(factor = factor),
#' apply = function(graph, layout, modifier_args) {
#' layout * modifier_args$factor
#' }
#' )
#' }
#' coords <- layout_(make_ring(10), in_circle(), scale_by(3))
#' plot(make_ring(10), layout = coords)
layout_ <- function(graph, layout, ...) {
modifiers <- list(...)
stopifnot(all(sapply(modifiers, inherits, what = "igraph_layout_modifier")))
ids <- sapply(modifiers, "[[", "id")
if (anyDuplicated(ids)) {
cli::cli_abort("Duplicate modifiers.")
}
names(modifiers) <- ids
# Separate modifiers by type
is_pre <- vapply(modifiers, function(m) isTRUE(m$type == "pre"), logical(1))
pre_modifiers <- modifiers[is_pre]
post_modifiers <- modifiers[!is_pre]
# Apply pre-layout modifiers
if (length(pre_modifiers) > 0) {
# Enforce single pre-layout modifier restriction
if (length(pre_modifiers) > 1) {
cli::cli_abort("Multiple pre-layout modifiers are not supported.")
}
modifier <- pre_modifiers[[1]]
result <- modifier$apply(graph, layout, modifier$args)
} else {
# No pre-layout modifiers, do standard layout
result <- do_call(layout$fun, list(graph = graph), layout$args)
}
# Apply post-layout modifiers in order
for (modifier in post_modifiers) {
result <- modifier$apply(graph, result, modifier$args)
}
result
}
#' Add layout to graph
#'
#' @param graph The input graph.
#' @param ... Additional arguments are passed to [layout_()].
#' @param overwrite Whether to overwrite the layout of the graph,
#' if it already has one.
#' @return The input graph, with the layout added.
#'
#' @seealso [layout_()] for a description of the layout API.
#' @export
#' @family graph layouts
#' @examples
#' (make_star(11) + make_star(11)) %>%
#' add_layout_(as_star(), component_wise()) %>%
#' plot()
add_layout_ <- function(graph, ..., overwrite = TRUE) {
if (overwrite && "layout" %in% graph_attr_names(graph)) {
graph <- delete_graph_attr(graph, "layout")
}
graph$layout <- layout_(graph, ...)
graph
}
layout_spec <- function(fun, ...) {
my_call <- match.call(sys.function(1), sys.call(1))
my_call[[1]] <- substitute(fun)
structure(
list(
fun = fun,
call_str = sub("(", "(<graph>, ", deparse(my_call), fixed = TRUE),
args = list(...)
),
class = "igraph_layout_spec"
)
}
#' @rdname layout_
#' @param x The layout specification
#' @method print igraph_layout_spec
#' @export
print.igraph_layout_spec <- function(x, ...) {
cat(paste(
sep = "",
"igraph layout specification, see ?layout_:\n",
x$call_str,
"\n"
))
}
#' Create a layout modifier
#'
#' This is a constructor function for creating custom layout modifiers.
#' Layout modifiers can be used with [layout_()] to modify how layouts
#' are calculated or to transform the resulting coordinates.
#'
#' @param ... Named arguments that define the modifier. Must include:
#' \describe{
#' \item{id}{A unique identifier string for the modifier}
#' \item{type}{Either `"pre"` for pre-layout or `"post"` for post-layout}
#' \item{args}{A list of arguments to pass to the apply function}
#' \item{apply}{A function with signature
#' `function(graph, layout, modifier_args)` that performs the modification}
#' }
#'
#' @return An object of class `igraph_layout_modifier`.
#'
#' @seealso [layout_()] for using modifiers, [component_wise()], [normalize()]
#' for examples of built-in modifiers.
#'
#' @family layout modifiers
#' @export
#' @examples
#' # Create a custom post-layout modifier that scales coordinates
#' scale_by <- function(factor) {
#' layout_modifier(
#' id = "scale_by",
#' type = "post",
#' args = list(factor = factor),
#' apply = function(graph, layout, modifier_args) {
#' layout * modifier_args$factor
#' }
#' )
#' }
#'
#' # Use the custom modifier
#' g <- make_ring(10)
#' coords <- layout_(g, in_circle(), scale_by(2))
#' plot(g, layout = coords)
layout_modifier <- function(...) {
structure(
list(...),
class = "igraph_layout_modifier"
)
}
#' @rdname layout_
#' @method print igraph_layout_modifier
#' @export
print.igraph_layout_modifier <- function(x, ...) {
cat(sep = "", "igraph layout modifier: ", x$id, ".\n")
}
#' Component-wise layout
#'
#' This is a layout modifier function, and it can be used
#' to calculate the layout separately for each component
#' of the graph.
#'
#' @param merge_method Merging algorithm, the `method`
#' argument of [merge_coords()].
#'
#' @family layout modifiers
#' @family graph layouts
#' @seealso [merge_coords()], [layout_()].
#' @export
#' @examples
#' g <- make_ring(10) + make_ring(10)
#' g %>%
#' add_layout_(in_circle(), component_wise()) %>%
#' plot()
component_wise <- function(merge_method = "dla") {
args <- grab_args()
layout_modifier(
id = "component_wise",
type = "pre",
args = args,
apply = function(graph, layout, modifier_args) {
graph$id <- seq(vcount(graph))
comps <- decompose(graph)
coords <- lapply(comps, function(comp) {
do_call(layout$fun, list(graph = comp), layout$args)
})
all_coords <- merge_coords(
comps,
coords,
method = modifier_args$merge_method
)
all_coords[unlist(sapply(comps, vertex_attr, "id")), ] <- all_coords[]
all_coords
}
)
}
#' Normalize layout
#'
#' Scale coordinates of a layout.
#'
#' @param xmin,xmax Minimum and maximum for x coordinates.
#' @param ymin,ymax Minimum and maximum for y coordinates.
#' @param zmin,zmax Minimum and maximum for z coordinates.
#'
#' @family layout modifiers
#' @family graph layouts
#' @seealso [merge_coords()], [layout_()].
#' @export
#' @examples
#' layout_(make_ring(10), with_fr(), normalize())
normalize <- function(
xmin = -1,
xmax = 1,
ymin = xmin,
ymax = xmax,
zmin = xmin,
zmax = xmax
) {
args <- grab_args()
layout_modifier(
id = "normalize",
type = "post",
args = args,
apply = function(graph, layout, modifier_args) {
do_call(
norm_coords,
list(layout),
modifier_args
)
}
)
}
## ----------------------------------------------------------------
## Layout definitions for the new API
## ----------------------------------------------------------------
#' Simple two-row layout for bipartite graphs
#'
#' Minimize edge-crossings in a simple two-row (or column) layout for bipartite
#' graphs.
#'
#' The layout is created by first placing the vertices in two rows, according
#' to their types. Then the positions within the rows are optimized to minimize
#' edge crossings, using the Sugiyama algorithm (see
#' [layout_with_sugiyama()]).
#'
#' @param graph The bipartite input graph. It should have a logical
#' \sQuote{`type`} vertex attribute, or the `types` argument must be
#' given.
#' @param types A logical vector, the vertex types. If this argument is
#' `NULL` (the default), then the \sQuote{`type`} vertex attribute is
#' used.
#' @param hgap Real scalar, the minimum horizontal gap between vertices in the
#' same layer.
#' @param vgap Real scalar, the distance between the two layers.
#' @param 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.
#' @return A matrix with two columns and as many rows as the number of vertices
#' in the input graph.
#' @author Gabor Csardi \email{csardi.gabor@@gmail.com}
#' @seealso [layout_with_sugiyama()]
#' @keywords graphs
#' @export
#' @family graph layouts
#' @examples
#' # Random bipartite graph
#' inc <- matrix(sample(0:1, 50, replace = TRUE, prob = c(2, 1)), 10, 5)
#' g <- graph_from_biadjacency_matrix(inc)
#' plot(g,
#' layout = layout_as_bipartite,
#' vertex.color = c("green", "cyan")[V(g)$type + 1]
#' )
#'
#' # Two columns
#' g %>%
#' add_layout_(as_bipartite()) %>%
#' plot()
layout_as_bipartite <- function(
graph,
types = NULL,
hgap = 1,
vgap = 1,
maxiter = 100
) {
## Argument checks
ensure_igraph(graph)
# Use the _impl function
layout_bipartite_impl(
graph = graph,
types = types,
hgap = hgap,
vgap = vgap,
maxiter = maxiter
)
}
#' @rdname layout_as_bipartite
#' @param ... Arguments to pass to `layout_as_bipartite()`.
#' @export
as_bipartite <- function(...) layout_spec(layout_as_bipartite, ...)
## ----------------------------------------------------------------
#' Generate coordinates to place the vertices of a graph in a star-shape
#'
#' A simple layout generator, that places one vertex in the center of a circle
#' and the rest of the vertices equidistantly on the perimeter.
#'
#' It is possible to choose the vertex that will be in the center, and the
#' order of the vertices can be also given.
#'
#' @param graph The graph to layout.
#' @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.
#' The default ordering is given by the vertex ids.
#' @return A matrix with two columns and as many rows as the number of vertices
#' in the input graph.
#' @author Gabor Csardi \email{csardi.gabor@@gmail.com}
#' @seealso [layout()] and [layout_with_drl()] for other layout
#' algorithms, [plot.igraph()] and [tkplot()] on how to
#' plot graphs and [star()] on how to create ring graphs.
#' @keywords graphs
#' @export
#' @family graph layouts
#' @examples
#'
#' g <- make_star(10)
#' layout_as_star(g)
#'
#' ## Alternative form
#' layout_(g, as_star())
layout_as_star <- function(graph, center = V(graph)[1], order = NULL) {
# Argument checks
ensure_igraph(graph)
if (vcount(graph) == 0) {
# Any other layout will do so just pick one that supports graphs with no
# vertices
return(layout_in_circle(graph))
}
# Use the _impl function
layout_star_impl(
graph = graph,
center = center,
order = order
)
}
#' @rdname layout_as_star
#' @param ... Arguments to pass to `layout_as_star()`.
#' @export
as_star <- function(...) layout_spec(layout_as_star, ...)
## ----------------------------------------------------------------
#' The Reingold-Tilford graph layout algorithm
#'
#' A tree-like layout, it is perfect for trees, acceptable for graphs with not
#' too many cycles.
#'
#' Arranges the nodes in a tree where the given node is used as the root. The
#' tree is directed downwards and the parents are centered above its children.
#' For the exact algorithm, the reference below.
#'
#' If the given graph is not a tree, a breadth-first search is executed first
#' to obtain a possible spanning tree.
#'
#' @param graph The input graph.
#' @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
#' vector, then the root vertices are automatically calculated based on
#' topological sorting, performed with the opposite mode than the `mode`
#' argument. After the vertices have been sorted, one is selected from each
#' component.
#' @param circular Logical scalar, whether to plot the tree in a circular
#' fashion. Defaults to `FALSE`, so the tree branches are going bottom-up
#' (or top-down, see the `flip.y` argument.
#' @param rootlevel This argument can be useful when drawing forests which are
#' not trees (i.e. they are unconnected and have tree components). It specifies
#' the level of the root vertices for every tree in the forest. It is only
#' considered if the `roots` argument is not an empty vector.
#' @param mode Specifies which edges to consider when building the tree. If it
#' is \sQuote{out}, then only the outgoing, if it is \sQuote{in}, then only the
#' incoming edges of a parent are considered. If it is \sQuote{all} then all
#' edges are used (this was the behavior in igraph 0.5 and before). This
#' parameter also influences how the root vertices are calculated, if they are
#' not given. See the `roots` parameter.
#' @param flip.y Logical scalar, whether to flip the \sQuote{y} coordinates.
#' The default is flipping because that puts the root vertex on the top.
#' @return A numeric matrix with two columns, and one row for each vertex.
#' @author Tamas Nepusz \email{ntamas@@gmail.com} and Gabor Csardi
#' \email{csardi.gabor@@gmail.com}
#' @references Reingold, E and Tilford, J (1981). Tidier drawing of trees.
#' *IEEE Trans. on Softw. Eng.*, SE-7(2):223--228.
#' @keywords graphs
#' @export
#' @family graph layouts
#' @examples
#'
#' tree <- make_tree(20, 3)
#' plot(tree, layout = layout_as_tree)
#' plot(tree, layout = layout_as_tree(tree, flip.y = FALSE))
#' plot(tree, layout = layout_as_tree(tree, circular = TRUE))
#'
#' tree2 <- make_tree(10, 3) + make_tree(10, 2)
#' plot(tree2, layout = layout_as_tree)
#' plot(tree2, layout = layout_as_tree(tree2,
#' root = c(1, 11),
#' rootlevel = c(2, 1)
#' ))
layout_as_tree <- function(
graph,
root = numeric(),
circular = FALSE,
rootlevel = numeric(),
mode = c("out", "in", "all"),
flip.y = TRUE
) {
ensure_igraph(graph)
root <- as_igraph_vs(graph, root) - 1
circular <- as.logical(circular)
rootlevel <- as.double(rootlevel)
mode <- switch(
igraph_match_arg(mode),
"out" = 1,
"in" = 2,
"all" = 3,
"total" = 3
)
flip.y <- as.logical(flip.y)
on.exit(.Call(Rx_igraph_finalizer))
res <- .Call(
Rx_igraph_layout_reingold_tilford,
graph,
root,
mode,
rootlevel,
circular
)
if (flip.y && vcount(graph) > 0) {
res[, 2] <- max(res[, 2]) - res[, 2]
}
res
}
#' @rdname layout_as_tree
#' @param ... Passed to `layout_as_tree()`.
#' @export
as_tree <- function(...) layout_spec(layout_as_tree, ...)
#' The Reingold-Tilford graph layout algorithm
#' @description
#' `r lifecycle::badge("deprecated")`
#'
#' `layout.reingold.tilford()` was renamed to [layout_as_tree()] to create a more
#' consistent API.
#' @param ... Passed to the new layout functions.
#' @param params Passed to the new layout functions as arguments.
#' @keywords internal
#' @export
layout.reingold.tilford <- function(..., params = list()) {
lifecycle::deprecate_soft(
"2.1.0",
"layout.reingold.tilford()",
"layout_as_tree()"
)
do_call(layout_as_tree, .args = c(list(...), params))
}
## ----------------------------------------------------------------
#' Graph layout with vertices on a circle.
#'
#' Place vertices on a circle, in the order of their vertex ids.
#'
#' If you want to order the vertices differently, then permute them using the
#' [permute()] function.
#'
#' @param graph The input graph.
#' @param order The vertices to place on the circle, in the order of their
#' desired placement. Vertices that are not included here will be placed at
#' (0,0).
#' @return A numeric matrix with two columns, and one row for each vertex.
#' @author Gabor Csardi \email{csardi.gabor@@gmail.com}
#' @keywords graphs
#' @export
#' @family graph layouts
#' @examplesIf igraph:::has_glpk() && rlang::is_installed("igraphdata")
#'
#' ## Place vertices on a circle, order them according to their
#' ## community
#' library(igraphdata)
#' data(karate)
#' karate_groups <- cluster_optimal(karate)
#' coords <- layout_in_circle(karate,
#' order =
#' order(membership(karate_groups))
#' )
#' V(karate)$label <- sub("Actor ", "", V(karate)$name)
#' V(karate)$label.color <- membership(karate_groups)
#' V(karate)$shape <- "none"
#' plot(karate, layout = coords)
layout_in_circle <- function(graph, order = V(graph)) {
# Use the _impl function
layout_circle_impl(
graph = graph,
order = order
)
}
#' @rdname layout_in_circle
#' @param ... Passed to `layout_in_circle()`.
#' @export
in_circle <- function(...) layout_spec(layout_in_circle, ...)
#' Graph layout with vertices on a circle
#' @description
#' `r lifecycle::badge("deprecated")`
#'
#' `layout.circle()` was renamed to [layout_in_circle()] to create a more
#' consistent API.
#' @param ... Passed to the new layout functions.
#' @param params Passed to the new layout functions as arguments.
#' @keywords internal
#' @export
layout.circle <- function(..., params = list()) {
lifecycle::deprecate_soft("2.1.0", "layout.circle()", "layout_in_circle()")
do_call(layout_in_circle, .args = c(list(...), params))
}
## ----------------------------------------------------------------
#' Choose an appropriate graph layout algorithm automatically
#'
#' This function tries to choose an appropriate graph layout algorithm for the
#' graph, automatically, based on a simple algorithm. See details below.
#'
#' `layout_nicely()` tries to choose an appropriate layout function for the
#' supplied graph, and uses that to generate the layout. The current
#' implementation works like this:
#' \enumerate{
#' \item If the graph has a graph attribute called \sQuote{layout},
#' then this is used. If this attribute is an R function, then it is called, with the graph and any other extra arguments.
#'
#' \item Otherwise, if the graph has vertex attributes called \sQuote{x} and
#' \sQuote{y}, then these are used as coordinates. If the graph has an
#' additional \sQuote{z} vertex attribute, that is also used.
#'
#' \item Otherwise, if the graph is a forest and has less than 30 vertices, `layout_as_tree()` is used.
#'
#' \item Otherwise,
#' if the graph is connected and has less than 1000 vertices, the
#' Fruchterman-Reingold layout is used, by calling `layout_with_fr()`.
#' \item Otherwise the DrL layout is used, `layout_with_drl()` is called. }
#'
#' In layout algorithm implementations, an argument named \sQuote{weights} is
#' typically used to specify the weights of the edges if the layout algorithm
#' supports them. In this case, omitting \sQuote{weights} or setting it to
#' `NULL` will make igraph use the 'weight' edge attribute from the graph
#' if it is present. However, most layout algorithms do not support non-positive
#' weights, so `layout_nicely()` would fail if you simply called it on
#' your graph without specifying explicit weights and the weights happened to
#' include non-positive numbers. We strive to ensure that `layout_nicely()`
#' works out-of-the-box for most graphs, so the rule is that if you omit
#' \sQuote{weights} or set it to `NULL` and `layout_nicely()` would
#' end up calling `layout_with_fr()` or `layout_with_drl()`, we do not
#' forward the weights to these functions and issue a warning about this. You
#' can use `weights = NA` to silence the warning.
#'
#' @param graph The input graph
#' @param dim Dimensions, should be 2 or 3.
#' @param \dots For `layout_nicely()` the extra arguments are passed to
#' the real layout function. For `nicely()` all argument are passed to
#' `layout_nicely()`.
#' @return A numeric matrix with two or three columns.
#' @author Gabor Csardi \email{csardi.gabor@@gmail.com}
#' @seealso [plot.igraph()]
#' @keywords graphs
#' @export
#' @family graph layouts
layout_nicely <- function(graph, dim = 2, ...) {
## 1. If there is a 'layout' graph attribute, we just use that.
## 2. Otherwise, if there are vertex attributes called 'x' and 'y',
## we use those (and the 'z' vertex attribute as well, if present).
## 3. If the graph is a forest and has less than 30 vertices, layout_as_tree is used
## 3. Otherwise, if the graph is small (<1000) we use
## the Fruchterman-Reingold layout.
## 4. Otherwise we use the DrL layout generator.
if ("layout" %in% graph_attr_names(graph)) {
lay <- graph_attr(graph, "layout")
if (is.function(lay)) {
if (!identical(lay, layout_nicely)) {
return(lay(graph, ...))
} else {
# nop, we'll deal with it later below
}
} else {
return(lay)
}
}
if (all(c("x", "y") %in% vertex_attr_names(graph))) {
if ("z" %in% vertex_attr_names(graph)) {
cbind(V(graph)$x, V(graph)$y, V(graph)$z)
} else {
cbind(V(graph)$x, V(graph)$y)
}
} else {
args <- list(...)
if (!("weights" %in% names(args)) || is.null(args$weights)) {
if ("weight" %in% edge_attr_names(graph)) {
weights <- E(graph)$weight
if (any(weights <= 0, na.rm = TRUE)) {
cli::cli_warn(
"Non-positive edge weight found, ignoring all weights during graph layout."
)
args$weights <- NA
} else {
args$weights <- weights
}
}
}
args$graph <- graph
if (is_forest(graph) && vcount(graph) <= 30 && is.null(args$weights)) {
return(do.call(layout_as_tree, args))
}
args$dim <- dim
if (vcount(graph) < 1000) {
layout <- do.call(layout_with_fr, args)
align_layout(graph, layout)
} else {
do.call(layout_with_drl, args)
}
}
}
#' @rdname layout_nicely
#' @export
nicely <- function(...) layout_spec(layout_nicely, ...)
## ----------------------------------------------------------------
#' Simple grid layout
#'
#' This layout places vertices on a rectangular grid, in two or three
#' dimensions.
#'
#' The function places the vertices on a simple rectangular grid, one after the
#' other. If you want to change the order of the vertices, then see the
#' [permute()] function.
#'
#' @param graph The input graph.
#' @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
#' integer. Similarly, it will be the cube root for 3d layouts.
#' @param height The number of vertices in a single column of the grid, for
#' three dimensional layouts. If this is zero or negative, then it is
#' determinted automatically.
#' @param dim Two or three. Whether to make 2d or a 3d layout.
#' @return A two-column or three-column matrix.
#' @author Tamas Nepusz \email{ntamas@@gmail.com}
#' @seealso [layout()] for other layout generators
#' @keywords graphs
#' @export
#' @family graph layouts
#' @examples
#'
#' g <- make_lattice(c(3, 3))
#' layout_on_grid(g)
#'
#' g2 <- make_lattice(c(3, 3, 3))
#' layout_on_grid(g2, dim = 3)
#'
#' plot(g, layout = layout_on_grid)
#' 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) {
# Argument checks
ensure_igraph(graph)
width <- as.numeric(width)
dim <- as.numeric(dim)
stopifnot(dim == 2 || dim == 3)
if (dim == 3) {
height <- as.numeric(height)
}
# Function call
if (dim == 2) {
layout_grid_impl(
graph = graph,
width = width
)
} else {
layout_grid_3d_impl(
graph = graph,
width = width,
height = height
)
}
}
#' @rdname layout_on_grid
#' @param ... Passed to `layout_on_grid()`.
#' @export
on_grid <- function(...) layout_spec(layout_on_grid, ...)
#' Simple grid layout
#' @description
#' `r lifecycle::badge("deprecated")`
#'
#' Use [layout_on_grid()].
#' @inheritParams layout_on_grid
#' @export
#' @export
#' @keywords internal
layout.grid.3d <- function(graph, width = 0, height = 0) {
lifecycle::deprecate_stop("2.1.0", "layout.grid.3d()", "layout_on_grid()")
}
## ----------------------------------------------------------------
#' Graph layout with vertices on the surface of a sphere
#'
#' Place vertices on a sphere, approximately uniformly, in the order of their
#' vertex ids.
#'
#' `layout_on_sphere()` places the vertices (approximately) uniformly on the
#' surface of a sphere, this is thus a 3d layout. It is not clear however what
#' \dQuote{uniformly on a sphere} means.
#'
#' If you want to order the vertices differently, then permute them using the
#' [permute()] function.
#'
#' @param graph The input graph.
#' @return A numeric matrix with three columns, and one row for each vertex.
#' @author Gabor Csardi \email{csardi.gabor@@gmail.com}
#' @keywords graphs
#' @export
#' @family graph layouts
layout_on_sphere <- function(graph) {
# Use the _impl function
layout_sphere_impl(
graph = graph
)
}
#' @rdname layout_on_sphere
#' @param ... Passed to `layout_on_sphere()`.
#' @export
on_sphere <- function(...) layout_spec(layout_on_sphere, ...)
#' Graph layout with vertices on the surface of a sphere
#' @description
#' `r lifecycle::badge("deprecated")`
#'
#' `layout.sphere()` was renamed to [layout_on_sphere()] to create a more
#' consistent API.
#' @param ... Passed to the new layout functions.
#' @param params Passed to the new layout functions as arguments.
#' @keywords internal
#' @export
layout.sphere <- function(..., params = list()) {
lifecycle::deprecate_soft("2.1.0", "layout.sphere()", "layout_on_sphere()")
do_call(layout_on_sphere, .args = c(list(...), params))
}
## ----------------------------------------------------------------
#' Randomly place vertices on a plane or in 3d space
#'
#' This function uniformly randomly places the vertices of the graph in two or
#' three dimensions.
#'
#' Randomly places vertices on a \[-1,1\] square (in 2d) or in a cube (in 3d). It
#' is probably a useless layout, but it can use as a starting point for other
#' layout generators.
#'
#' @param graph The input graph.
#' @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.
#' @author Gabor Csardi \email{csardi.gabor@@gmail.com}
#' @keywords graphs
#' @export
#' @family graph layouts
layout_randomly <- function(graph, dim = c(2, 3)) {
ensure_igraph(graph)
dim <- igraph_match_arg(dim)
if (dim == 2) {
layout_random_impl(
graph = graph
)
} else if (dim == 3) {
layout_random_3d_impl(
graph = graph
)
}
}
#' @rdname layout_randomly
#' @param ... Parameters to pass to `layout_randomly()`.
#' @export
randomly <- function(...) layout_spec(layout_randomly, ...)
#' Randomly place vertices on a plane or in 3d space
#' @description
#' `r lifecycle::badge("deprecated")`
#'
#' `layout.random()` was renamed to [layout_randomly()] to create a more
#' consistent API.
#' @param ... Passed to the new layout functions.
#' @param params Passed to the new layout functions as arguments.
#' @keywords internal
#' @export
layout.random <- function(..., params = list()) {
lifecycle::deprecate_soft("2.1.0", "layout.random()", "layout_randomly()")
do_call(layout_randomly, .args = c(list(...), params))
}
## ----------------------------------------------------------------
#' The Davidson-Harel layout algorithm
#'
#' Place vertices of a graph on the plane, according to the simulated annealing
#' algorithm by Davidson and Harel.
#'
#' This function implements the algorithm by Davidson and Harel, see Ron
#' Davidson, David Harel: Drawing Graphs Nicely Using Simulated Annealing. ACM
#' Transactions on Graphics 15(4), pp. 301-331, 1996.
#'
#' The algorithm uses simulated annealing and a sophisticated energy function,
#' which is unfortunately hard to parameterize for different graphs. The
#' original publication did not disclose any parameter values, and the ones
#' below were determined by experimentation.
#'
#' The algorithm consists of two phases, an annealing phase, and a fine-tuning
#' phase. There is no simulated annealing in the second phase.
#'
#' Our implementation tries to follow the original publication, as much as
#' possible. The only major difference is that coordinates are explicitly kept
#' within the bounds of the rectangle of the layout.
#'
#' @param graph The graph to lay out. Edge directions are ignored.
#' @param coords Optional starting positions for the vertices. If this argument
#' is not `NULL` then it should be an appropriate matrix of starting
#' coordinates.
#' @param maxiter Number of iterations to perform in the first phase.
#' @param fineiter Number of iterations in the fine tuning phase.
#' @param cool.fact Cooling factor.
#' @param weight.node.dist Weight for the node-node distances component of the
#' energy function.
#' @param weight.border Weight for the distance from the border component of
#' the energy function. It can be set to zero, if vertices are allowed to sit
#' on the border.
#' @param weight.edge.lengths Weight for the edge length component of the
#' energy function.
#' @param weight.edge.crossings Weight for the edge crossing component of the
#' energy function.
#' @param weight.node.edge.dist Weight for the node-edge distance component of
#' the energy function.
#' @return A matrix with two columns, containing the x and y coordinates
#' of the vertices:
#' \describe{
#' \item{x}{
#' The x-coordinate of the vertex.
#' }
#' \item{y}{
#' The y-coordinate of the vertex.
#' }
#' }
#' @author Gabor Csardi \email{csardi.gabor@@gmail.com}
#' @seealso [layout_with_fr()],
#' [layout_with_kk()] for other layout algorithms.
#' @references Ron Davidson, David Harel: Drawing Graphs Nicely Using Simulated
#' Annealing. *ACM Transactions on Graphics* 15(4), pp. 301-331, 1996.
#' @export
#' @family graph layouts
#' @examples
#'
#' set.seed(42)
#' ## Figures from the paper
#' g_1b <- make_star(19, mode = "undirected") + path(c(2:19, 2)) +
#' path(c(seq(2, 18, by = 2), 2))
#' plot(g_1b, layout = layout_with_dh)
#'
#' g_2 <- make_lattice(c(8, 3)) + edges(1, 8, 9, 16, 17, 24)
#' plot(g_2, layout = layout_with_dh)
#'
#' g_3 <- make_empty_graph(n = 70)
#' plot(g_3, layout = layout_with_dh)
#'
#' g_4 <- make_empty_graph(n = 70, directed = FALSE) + edges(1:70)
#' plot(g_4, layout = layout_with_dh, vertex.size = 5, vertex.label = NA)
#'
#' g_5a <- make_ring(24)
#' plot(g_5a, layout = layout_with_dh, vertex.size = 5, vertex.label = NA)
#'
#' g_5b <- make_ring(40)
#' plot(g_5b, layout = layout_with_dh, vertex.size = 5, vertex.label = NA)
#'
#' g_6 <- make_lattice(c(2, 2, 2))
#' plot(g_6, layout = layout_with_dh)
#'
#' g_7 <- graph_from_literal(1:3:5 - -2:4:6)
#' plot(g_7, layout = layout_with_dh, vertex.label = V(g_7)$name)
#'
#' g_8 <- make_ring(5) + make_ring(10) + make_ring(5) +
#' edges(
#' 1, 6, 2, 8, 3, 10, 4, 12, 5, 14,
#' 7, 16, 9, 17, 11, 18, 13, 19, 15, 20
#' )
#' plot(g_8, layout = layout_with_dh, vertex.size = 5, vertex.label = NA)
#'
#' g_9 <- make_lattice(c(3, 2, 2))
#' plot(g_9, layout = layout_with_dh, vertex.size = 5, vertex.label = NA)
#'
#' g_10 <- make_lattice(c(6, 6))
#' plot(g_10, layout = layout_with_dh, vertex.size = 5, vertex.label = NA)
#'
#' g_11a <- make_tree(31, 2, mode = "undirected")
#' plot(g_11a, layout = layout_with_dh, vertex.size = 5, vertex.label = NA)
#'
#' g_11b <- make_tree(21, 4, mode = "undirected")
#' plot(g_11b, layout = layout_with_dh, vertex.size = 5, vertex.label = NA)
#'
#' g_12 <- make_empty_graph(n = 37, directed = FALSE) +
#' path(1:5, 10, 22, 31, 37:33, 27, 16, 6, 1) + path(6, 7, 11, 9, 10) + path(16:22) +
#' path(27:31) + path(2, 7, 18, 28, 34) + path(3, 8, 11, 19, 29, 32, 35) +
#' path(4, 9, 20, 30, 36) + path(1, 7, 12, 14, 19, 24, 26, 30, 37) +
#' path(5, 9, 13, 15, 19, 23, 25, 28, 33) + path(3, 12, 16, 25, 35, 26, 22, 13, 3)
#' 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))),
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))
) {
if (is.null(coords)) {
coords <- matrix(NA_real_, ncol = 2, nrow = 0)
use.seed <- FALSE
} else {
use.seed <- TRUE
}
layout_davidson_harel_impl(
graph = graph,
res = coords,
use_seed = use.seed,
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
)
}
#' @rdname layout_with_dh
#' @param ... Passed to `layout_with_dh()`.
#' @export
with_dh <- function(...) layout_spec(layout_with_dh, ...)
## ----------------------------------------------------------------
#' The Fruchterman-Reingold layout algorithm
#'
#' Place vertices on the plane using the force-directed layout algorithm by
#' Fruchterman and Reingold.
#'
#' See the referenced paper below for the details of the algorithm.
#'
#' This function was rewritten from scratch in igraph version 0.8.0.
#'
#' @param graph The graph to lay out. Edge directions are ignored.
#' @param coords Optional starting positions for the vertices. If this argument
#' is not `NULL` then it should be an appropriate matrix of starting
#' coordinates.
#' @param dim Integer scalar, 2 or 3, the dimension of the layout. Two
#' dimensional layouts are places on a plane, three dimensional ones in the 3d
#' space.
#' @param niter Integer scalar, the number of iterations to perform.
#' @param start.temp Real scalar, the start temperature. This is the maximum
#' amount of movement alloved along one axis, within one step, for a vertex.
#' Currently it is decreased linearly to zero during the iteration.
#' @param grid Character scalar, whether to use the faster, but less accurate
#' grid based implementation of the algorithm. By default (\dQuote{auto}), the
#' grid-based implementation is used if the graph has more than one thousand
#' vertices.
#' @param weights A vector giving edge weights. The `weight` edge
#' attribute is used by default, if present. If weights are given, then the
#' attraction along the edges will be multiplied by the given edge weights.
#' This places vertices connected with a highly weighted edge closer to
#' each other. Weights must be positive.
#' @param minx If not `NULL`, then it must be a numeric vector that gives
#' lower boundaries for the \sQuote{x} coordinates of the vertices. The length
#' of the vector must match the number of vertices in the graph.
#' @param maxx Similar to `minx`, but gives the upper boundaries.
#' @param miny Similar to `minx`, but gives the lower boundaries of the
#' \sQuote{y} coordinates.
#' @param maxy Similar to `minx`, but gives the upper boundaries of the
#' \sQuote{y} coordinates.
#' @param minz Similar to `minx`, but gives the lower boundaries of the
#' \sQuote{z} coordinates.
#' @param maxz Similar to `minx`, but gives the upper boundaries of the
#' \sQuote{z} coordinates.
#' @param coolexp,maxdelta,area,repulserad `r lifecycle::badge("deprecated")` These
#' arguments are not supported from igraph version 0.8.0 and are ignored
#' (with a warning).
#' @param maxiter A deprecated synonym of `niter`, for compatibility.
#' @return A two- or three-column matrix, each row giving the coordinates of a
#' vertex, according to the ids of the vertex ids.
#' @author Gabor Csardi \email{csardi.gabor@@gmail.com}
#' @seealso [layout_with_drl()], [layout_with_kk()] for
#' other layout algorithms.
#' @references Fruchterman, T.M.J. and Reingold, E.M. (1991). Graph Drawing by
#' Force-directed Placement. *Software - Practice and Experience*,
#' 21(11):1129-1164.
#' @export
#' @family graph layouts
#' @keywords graphs
#' @examples
#'
#' # Fixing ego
#' g <- sample_pa(20, m = 2)
#' minC <- rep(-Inf, vcount(g))
#' maxC <- rep(Inf, vcount(g))
#' minC[1] <- maxC[1] <- 0
#' co <- layout_with_fr(g,
#' minx = minC, maxx = maxC,
#' miny = minC, maxy = maxC
#' )
#' co[1, ]
#' plot(g,
#' layout = co, vertex.size = 30, edge.arrow.size = 0.2,
#' vertex.label = c("ego", rep("", vcount(g) - 1)), rescale = FALSE,
#' xlim = range(co[, 1]), ylim = range(co[, 2]), vertex.label.dist = 0,
#' vertex.label.color = "red"
#' )
#' axis(1)
#' axis(2)
#'
layout_with_fr <- 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()
) {
# Argument checks
ensure_igraph(graph)
coords[] <- as.numeric(coords)
dim <- igraph_match_arg(dim)
if (!missing(niter) && !missing(maxiter)) {
cli::cli_abort(c(
"{.arg niter} and {.arg maxiter} must not be specified at the same time.",
i = "Specify only {.arg niter}, {.arg maxiter} is deprecated."
))
}
if (!missing(maxiter)) {
niter <- maxiter
}
niter <- as.numeric(niter)
start.temp <- as.numeric(start.temp)
grid <- igraph_match_arg(grid)
grid <- switch(grid, "grid" = 0L, "nogrid" = 1L, "auto" = 2L)
if (is.null(weights) && "weight" %in% edge_attr_names(graph)) {
weights <- E(graph)$weight
}
if (!is.null(weights) && any(!is.na(weights))) {
weights <- as.numeric(weights)
} else {
weights <- NULL
}
if (!is.null(minx)) {
minx <- as.numeric(minx)
}
if (!is.null(maxx)) {
maxx <- as.numeric(maxx)
}
if (!is.null(miny)) {
miny <- as.numeric(miny)
}
if (!is.null(maxy)) {
maxy <- as.numeric(maxy)
}
if (!is.null(minz)) {
minz <- as.numeric(minz)
}
if (!is.null(maxz)) {
maxz <- as.numeric(maxz)
}
if (lifecycle::is_present(coolexp)) {
lifecycle::deprecate_stop("0.8.0", "layout_with_fr(coolexp = )")
}
if (lifecycle::is_present(maxdelta)) {
lifecycle::deprecate_stop("0.8.0", "layout_with_fr(maxdelta = )")
}
if (lifecycle::is_present(area)) {
lifecycle::deprecate_stop("0.8.0", "layout_with_fr(area = )")
}
if (lifecycle::is_present(repulserad)) {
lifecycle::deprecate_stop("0.8.0", "layout_with_fr(repulserad = )")
}
on.exit(.Call(Rx_igraph_finalizer))
if (dim == 2) {
res <- .Call(
Rx_igraph_layout_fruchterman_reingold,
graph,
coords,
niter,
start.temp,
weights,
minx,
maxx,
miny,
maxy,
grid
)
} else {
res <- .Call(
Rx_igraph_layout_fruchterman_reingold_3d,
graph,
coords,
niter,
start.temp,
weights,
minx,
maxx,
miny,
maxy,
minz,
maxz
)
}
res
}
#' @rdname layout_with_fr
#' @param ... Passed to `layout_with_fr()`.
#' @export
with_fr <- function(...) layout_spec(layout_with_fr, ...)
#' The Fruchterman-Reingold layout algorithm
#' @description
#' `r lifecycle::badge("deprecated")`
#'
#' `layout.fruchterman.reingold()` was renamed to [layout_with_fr()] to create a more
#' consistent API.
#' @param ... Passed to the new layout functions.
#' @param params Passed to the new layout functions as arguments.
#' @keywords internal
#' @export
layout.fruchterman.reingold <- function(..., params = list()) {
lifecycle::deprecate_soft(
"2.1.0",
"layout.fruchterman.reingold()",
"layout_with_fr()"
)
do_call(layout_with_fr, .args = c(list(...), params))
}
## ----------------------------------------------------------------
#' The GEM layout algorithm
#'
#' Place vertices on the plane using the GEM force-directed layout algorithm.
#'
#' See the referenced paper below for the details of the algorithm.
#'
#' @param graph The input graph. Edge directions are ignored.
#' @param coords If not `NULL`, then the starting coordinates should be
#' given here, in a two or three column matrix, depending on the `dim`
#' argument.
#' @param maxiter The maximum number of iterations to perform. Updating a
#' single vertex counts as an iteration. A reasonable default is 40 * n * n,
#' where n is the number of vertices. The original paper suggests 4 * n * n,
#' but this usually only works if the other parameters are set up carefully.
#' @param temp.max The maximum allowed local temperature. A reasonable default
#' is the number of vertices.
#' @param temp.min The global temperature at which the algorithm terminates
#' (even before reaching `maxiter` iterations). A reasonable default is
#' 1/10.
#' @param temp.init Initial local temperature of all vertices. A reasonable
#' default is the square root of the number of vertices.
#' @return A numeric matrix with two columns, and as many rows as the number of
#' vertices.
#' @author Gabor Csardi \email{csardi.gabor@@gmail.com}
#' @seealso [layout_with_fr()],
#' [plot.igraph()], [tkplot()]
#' @references Arne Frick, Andreas Ludwig, Heiko Mehldau: A Fast Adaptive
#' Layout Algorithm for Undirected Graphs, *Proc. Graph Drawing 1994*,
#' LNCS 894, pp. 388-403, 1995.
#' @export
#' @family graph layouts
#' @keywords graphs
#' @examples
#'
#' set.seed(42)
#' g <- make_ring(10)
#' plot(g, layout = layout_with_gem)
#'
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))
) {
if (is.null(coords)) {
coords <- matrix(NA_real_, ncol = 2, nrow = 0)
use.seed <- FALSE
} else {
use.seed <- TRUE
}
layout_gem_impl(
graph = graph,
res = coords,
use_seed = use.seed,
maxiter = maxiter,
temp_max = temp.max,
temp_min = temp.min,
temp_init = temp.init
)
}
#' @rdname layout_with_gem
#' @param ... Passed to `layout_with_gem()`.
#' @export
with_gem <- function(...) layout_spec(layout_with_gem, ...)
## ----------------------------------------------------------------
#' The graphopt layout algorithm
#'
#' A force-directed layout algorithm, that scales relatively well to large
#' graphs.
#'
#' `layout_with_graphopt()` is a port of the graphopt layout algorithm by Michael
#' Schmuhl. graphopt version 0.4.1 was rewritten in C and the support for
#' layers was removed (might be added later) and a code was a bit reorganized
#' to avoid some unnecessary steps is the node charge (see below) is zero.
#'
#' graphopt uses physical analogies for defining attracting and repelling
#' forces among the vertices and then the physical system is simulated until it
#' reaches an equilibrium. (There is no simulated annealing or anything like
#' that, so a stable fixed point is not guaranteed.)
#'
#' @param graph The input graph.
#' @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.
#' @param niter Integer scalar, the number of iterations to perform. Should be
#' a couple of hundred in general. If you have a large graph then you might
#' want to only do a few iterations and then check the result. If it is not
#' good enough you can feed it in again in the `start` argument. The
#' default value is 500.
#' @param charge The charge of the vertices, used to calculate electric
#' repulsion. The default is 0.001.
#' @param mass The mass of the vertices, used for the spring forces. The
#' default is 30.
#' @param spring.length The length of the springs, an integer number. The
#' default value is zero.
#' @param spring.constant The spring constant, the default value is one.
#' @param 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.
#' @return A numeric matrix with two columns, and a row for each vertex.
#' @author Michael Schmuhl for the original graphopt code, rewritten and
#' wrapped by Gabor Csardi \email{csardi.gabor@@gmail.com}.
#' @keywords graphs
#' @export
#' @family graph layouts
layout_with_graphopt <- function(
graph,
start = NULL,
niter = 500,
charge = 0.001,
mass = 30,
spring.length = 0,
spring.constant = 1,
max.sa.movement = 5
) {
ensure_igraph(graph)
start[] <- as.numeric(start)
niter <- as.double(niter)
charge <- as.double(charge)
mass <- as.double(mass)
spring.length <- as.double(spring.length)
spring.constant <- as.double(spring.constant)
max.sa.movement <- as.double(max.sa.movement)
on.exit(.Call(Rx_igraph_finalizer))
.Call(
Rx_igraph_layout_graphopt,
graph,
niter,
charge,
mass,
spring.length,
spring.constant,
max.sa.movement,
start
)
}
#' @rdname layout_with_graphopt
#' @param ... Passed to `layout_with_graphopt()`.
#' @export
with_graphopt <- function(...) layout_spec(layout_with_graphopt, ...)
## ----------------------------------------------------------------
#' The Kamada-Kawai layout algorithm
#'
#' Place the vertices on the plane, or in 3D space, based on a physical
#' model of springs.
#'
#' See the referenced paper below for the details of the algorithm.
#'
#' This function was rewritten from scratch in igraph version 0.8.0 and it
#' follows truthfully the original publication by Kamada and Kawai now.
#'
#' @param graph The input graph. Edge directions are ignored.
#' @param coords If not `NULL`, then the starting coordinates should be
#' given here, in a two or three column matrix, depending on the `dim`
#' argument.
#' @param dim Integer scalar, 2 or 3, the dimension of the layout. Two
#' dimensional layouts are places on a plane, three dimensional ones in the 3d
#' space.
#' @param maxiter The maximum number of iterations to perform. The algorithm
#' might terminate earlier, see the `epsilon` argument.
#' @param epsilon Numeric scalar, the algorithm terminates, if the maximal
#' delta is less than this. (See the reference below for what delta means.) If
#' you set this to zero, then the function always performs `maxiter`
#' iterations.
#' @param kkconst Numeric scalar, the Kamada-Kawai vertex attraction constant.
#' Typical (and default) value is the number of vertices.
#' @param weights Edge weights, larger values will result in longer edges.
#' Note that this is the opposite of [layout_with_fr()], which produces
#' shorter edges for larger weights. Weights must be positive.
#' @param minx If not `NULL`, then it must be a numeric vector that gives
#' lower boundaries for the \sQuote{x} coordinates of the vertices. The length
#' of the vector must match the number of vertices in the graph.
#' @param maxx Similar to `minx`, but gives the upper boundaries.
#' @param miny Similar to `minx`, but gives the lower boundaries of the
#' \sQuote{y} coordinates.
#' @param maxy Similar to `minx`, but gives the upper boundaries of the
#' \sQuote{y} coordinates.
#' @param minz Similar to `minx`, but gives the lower boundaries of the
#' \sQuote{z} coordinates.
#' @param maxz Similar to `minx`, but gives the upper boundaries of the
#' \sQuote{z} coordinates.
#' @param niter,sigma,initemp,coolexp `r lifecycle::badge("deprecated")` These
#' arguments are not supported from igraph version 0.8.0 and are ignored (with a warning).
#' @param start Deprecated synonym for `coords`, for compatibility.
#' @return A numeric matrix with two (dim=2) or three (dim=3) columns, and as
#' many rows as the number of vertices, the x, y and potentially z coordinates
#' of the vertices.
#' @author Gabor Csardi \email{csardi.gabor@@gmail.com}
#' @seealso [layout_with_drl()], [plot.igraph()],
#' [tkplot()]
#' @references Kamada, T. and Kawai, S.: An Algorithm for Drawing General
#' Undirected Graphs. *Information Processing Letters*, 31/1, 7--15, 1989.
#' @export
#' @family graph layouts
#' @keywords graphs
#' @examples
#'
#' g <- make_ring(10)
#' E(g)$weight <- rep(1:2, length.out = ecount(g))
#' plot(g, layout = layout_with_kk, edge.label = E(g)$weight)
#'
layout_with_kk <- 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()
) {
# Argument checks
if (!missing(coords) && !missing(start)) {
cli::cli_abort(c(
"{.arg coords} and {.arg start} must not be specified at the same time.",
i = "Specify only {.arg coords}, {.arg start} is deprecated."
))
}
if (!missing(start)) {
coords <- start
}
ensure_igraph(graph)
coords[] <- as.numeric(coords)
dim <- igraph_match_arg(dim)
maxiter <- as.numeric(maxiter)
epsilon <- as.numeric(epsilon)
kkconst <- as.numeric(kkconst)
if (is.null(weights) && "weight" %in% edge_attr_names(graph)) {
weights <- E(graph)$weight
}
if (!is.null(weights) && any(!is.na(weights))) {
weights <- as.numeric(weights)
} else {
weights <- NULL
}
if (!is.null(minx)) {
minx <- as.numeric(minx)
}
if (!is.null(maxx)) {
maxx <- as.numeric(maxx)
}
if (!is.null(miny)) {
miny <- as.numeric(miny)
}
if (!is.null(maxy)) {
maxy <- as.numeric(maxy)
}
if (!is.null(minz)) {
minz <- as.numeric(minz)
}
if (!is.null(maxz)) {
maxz <- as.numeric(maxz)
}
if (lifecycle::is_present(niter)) {
lifecycle::deprecate_stop("0.8.0", "layout_with_kk(niter = )")
}
if (lifecycle::is_present(sigma)) {
lifecycle::deprecate_stop("0.8.0", "layout_with_kk(sigma = )")
}
if (lifecycle::is_present(initemp)) {
lifecycle::deprecate_stop("0.8.0", "layout_with_kk(initemp = )")
}
if (lifecycle::is_present(coolexp)) {
lifecycle::deprecate_stop("0.8.0", "layout_with_kk(coolexp = )")
}
on.exit(.Call(Rx_igraph_finalizer))
# Function call
if (dim == 2) {
res <- .Call(
Rx_igraph_layout_kamada_kawai,
graph,
coords,
maxiter,
epsilon,
kkconst,
weights,
minx,
maxx,
miny,
maxy
)
} else {
res <- .Call(
Rx_igraph_layout_kamada_kawai_3d,
graph,
coords,
maxiter,
epsilon,
kkconst,
weights,
minx,
maxx,
miny,
maxy,
minz,
maxz
)
}
res
}
#' @rdname layout_with_kk
#' @param ... Passed to `layout_with_kk()`.
#' @export
#'
with_kk <- function(...) layout_spec(layout_with_kk, ...)
#' The Kamada-Kawai layout algorithm
#' @description
#' `r lifecycle::badge("deprecated")`
#'
#' `layout.kamada.kawai()` was renamed to [layout_with_kk()] to create a more
#' consistent API.
#' @param ... Passed to the new layout functions.
#' @param params Passed to the new layout functions as arguments.
#' @keywords internal
#' @export
layout.kamada.kawai <- function(..., params = list()) {
lifecycle::deprecate_soft(
"2.1.0",
"layout.kamada.kawai()",
"layout_with_kk()"
)
do_call(layout_with_kk, .args = c(list(...), params))
}
## ----------------------------------------------------------------
#' Large Graph Layout
#'
#' A layout generator for larger graphs.
#'
#' `layout_with_lgl()` is for large connected graphs, it is similar to the layout
#' generator of the Large Graph Layout software
#' (<https://lgl.sourceforge.net/>).
#'
#' @param graph The input graph
#' @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).
#' @param area The area of the surface on which the vertices are placed (square
#' of the number of vertices).
#' @param coolexp The cooling exponent of the simulated annealing (1.5).
#' @param repulserad Cancellation radius for the repulsion (the `area`
#' times the number of vertices).
#' @param cellsize The size of the cells for the grid. When calculating the
#' repulsion forces between vertices only vertices in the same or neighboring
#' grid cells are taken into account (the fourth root of the number of
#' `area`.
#' @param 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.
#' @return A numeric matrix with two columns and as many rows as vertices.
#' @author Gabor Csardi \email{csardi.gabor@@gmail.com}
#' @keywords graphs
#' @export
#' @family graph layouts
layout_with_lgl <- function(
graph,
maxiter = 150,
maxdelta = vcount(graph),
area = vcount(graph)^2,
coolexp = 1.5,
repulserad = area * vcount(graph),
cellsize = sqrt(sqrt(area)),
root = NULL
) {
ensure_igraph(graph)
if (is.null(root)) {
root <- -1
} else {
root <- as_igraph_vs(graph, root) - 1
}
on.exit(.Call(Rx_igraph_finalizer))
.Call(
Rx_igraph_layout_lgl,
graph,
as.double(maxiter),
as.double(maxdelta),
as.double(area),
as.double(coolexp),
as.double(repulserad),
as.double(cellsize),
root
)
}
#' @rdname layout_with_lgl
#' @param ... Passed to `layout_with_lgl()`.
#' @export
with_lgl <- function(...) layout_spec(layout_with_lgl, ...)
#' Large Graph Layout
#' @description
#' `r lifecycle::badge("deprecated")`
#'
#' `layout.lgl()` was renamed to [layout_with_lgl()] to create a more
#' consistent API.
#' @param ... Passed to the new layout functions.
#' @param params Passed to the new layout functions as arguments.
#' @keywords internal
#' @export
layout.lgl <- function(..., params = list()) {
lifecycle::deprecate_soft("2.1.0", "layout.lgl()", "layout_with_lgl()")
do_call(layout_with_lgl, .args = c(list(...), params))
}
## ----------------------------------------------------------------
#' Graph layout by multidimensional scaling
#'
#' Multidimensional scaling of some distance matrix defined on the vertices of
#' a graph.
#'
#' `layout_with_mds()` uses classical multidimensional scaling (Torgerson scaling)
#' for generating the coordinates. Multidimensional scaling aims to place points
#' from a higher dimensional space in a (typically) 2 dimensional plane, so that
#' the distances between the points are kept as much as this is possible.
#'
#' By default igraph uses the shortest path matrix as the distances between the
#' nodes, but the user can override this via the `dist` argument.
#'
#' Warning: If the graph is symmetric to the exchange of two vertices (as is the
#' case with leaves of a tree connecting to the same parent), classical
#' multidimensional scaling may assign the same coordinates to these vertices.
#'
#' This function generates the layout separately for each graph component and
#' then merges them via [merge_coords()].
#'
#' @param graph The input graph.
#' @param dist The distance matrix for the multidimensional scaling. If
#' `NULL` (the default), then the unweighted shortest path matrix is used.
#' @param dim `layout_with_mds()` supports dimensions up to the number of nodes
#' minus one, but only if the graph is connected; for unconnected graphs, the
#' only possible value is 2. This is because `merge_coords()` only works in
#' 2D.
#' @param options This is currently ignored, as ARPACK is not used any more for
#' solving the eigenproblem
#' @return A numeric matrix with `dim` columns.
#' @author Tamas Nepusz \email{ntamas@@gmail.com} and Gabor Csardi
#' \email{csardi.gabor@@gmail.com}
#' @seealso [layout()], [plot.igraph()]
#' @references Cox, T. F. and Cox, M. A. A. (2001) *Multidimensional
#' Scaling*. Second edition. Chapman and Hall.
#' @export
#' @family graph layouts
#' @keywords graphs
#' @examples
#'
#' g <- sample_gnp(100, 2 / 100)
#' l <- layout_with_mds(g)
#' plot(g, layout = l, vertex.label = NA, vertex.size = 3)
layout_with_mds <- function(
graph,
dist = NULL,
dim = 2,
options = arpack_defaults()
) {
if (is.function(options)) {
lifecycle::deprecate_soft(
"1.6.0",
"layout_with_mds(options = 'must be a list')",
details = c(
"`arpack_defaults()` is now a function, use `options = arpack_defaults()` instead of `options = arpack_defaults`."
)
)
options <- options()
}
# Argument checks
ensure_igraph(graph)
dist[] <- as.numeric(dist)
dim <- as.numeric(dim)
# Function call
layout_mds_impl(
graph = graph,
dist = dist,
dim = dim
)
}
#' @rdname layout_with_mds
#' @param ... Passed to `layout_with_mds()`.
#' @export
with_mds <- function(...) layout_spec(layout_with_mds, ...)
## ----------------------------------------------------------------
#' The Sugiyama graph layout generator
#'
#' Sugiyama layout algorithm for layered directed acyclic graphs. The algorithm
#' minimized edge crossings.
#'
#' This layout algorithm is designed for directed acyclic graphs where each
#' vertex is assigned to a layer. Layers are indexed from zero, and vertices of
#' the same layer will be placed on the same horizontal line. The X coordinates
#' of vertices within each layer are decided by the heuristic proposed by
#' Sugiyama et al. to minimize edge crossings.
#'
#' You can also try to lay out undirected graphs, graphs containing cycles, or
#' graphs without an a priori layered assignment with this algorithm. igraph
#' will try to eliminate cycles and assign vertices to layers, but there is no
#' guarantee on the quality of the layout in such cases.
#'
#' The Sugiyama layout may introduce \dQuote{bends} on the edges in order to
#' obtain a visually more pleasing layout. This is achieved by adding dummy
#' nodes to edges spanning more than one layer. The resulting layout assigns
#' coordinates not only to the nodes of the original graph but also to the
#' dummy nodes. The layout algorithm will also return the extended graph with
#' the dummy nodes.
#'
#' For more details, see the reference below.
#'
#' @param graph The input graph.
#' @param layers A numeric vector or `NULL`. If not `NULL`, then it
#' should specify the layer index of the vertices. Layers are numbered from
#' one. If `NULL`, then igraph calculates the layers automatically.
#' @param hgap Real scalar, the minimum horizontal gap between vertices in the
#' same layer.
#' @param vgap Real scalar, the distance between layers.
#' @param 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.
#' @param weights Optional edge weight vector. If `NULL`, then the
#' 'weight' edge attribute is used, if there is one. Supply `NA` here and
#' igraph ignores the edge weights. These are used only if the graph
#' contains cycles; igraph will tend to reverse edges with smaller weights
#' when breaking the cycles.
#' @param attributes Which graph/vertex/edge attributes to keep in the extended
#' graph. \sQuote{default} keeps the \sQuote{size}, \sQuote{size2},
#' \sQuote{shape}, \sQuote{label} and \sQuote{color} vertex attributes and the
#' \sQuote{arrow.mode} and \sQuote{arrow.size} edge attributes. \sQuote{all}
#' keep all graph, vertex and edge attributes, \sQuote{none} keeps none of
#' them.
#' @return A list with the components:
#' \describe{
#' \item{layout}{
#' The layout, a two-column matrix, for the original graph vertices.
#' }
#' \item{layout.dummy}{
#' The layout for the dummy vertices, a two column matrix.
#' }
#' \item{extd_graph}{
#' The original graph, extended with dummy vertices.
#' The \sQuote{dummy} vertex attribute is set on this graph,
#' it is a logical attributes, and it tells you whether the vertex is a dummy vertex.
#' The \sQuote{layout} graph attribute is also set,
#' and it is the layout matrix for all (original and dummy) vertices.
#' }
#' }
#' @author Tamas Nepusz \email{ntamas@@gmail.com}
#' @references K. Sugiyama, S. Tagawa and M. Toda, "Methods for Visual
#' Understanding of Hierarchical Systems". IEEE Transactions on Systems, Man
#' and Cybernetics 11(2):109-125, 1981.
#' @export
#' @importFrom utils head
#' @family graph layouts
#' @keywords graphs
#' @examples
#'
#' ## Data taken from http://tehnick-8.narod.ru/dc_clients/
#' DC <- graph_from_literal(
#' "DC++" -+ "LinuxDC++":"BCDC++":"EiskaltDC++":"StrongDC++":"DiCe!++",
#' "LinuxDC++" -+ "FreeDC++", "BCDC++" -+ "StrongDC++",
#' "FreeDC++" -+ "BMDC++":"EiskaltDC++",
#' "StrongDC++" -+ "AirDC++":"zK++":"ApexDC++":"TkDC++",
#' "StrongDC++" -+ "StrongDC++ SQLite":"RSX++",
#' "ApexDC++" -+ "FlylinkDC++ ver <= 4xx",
#' "ApexDC++" -+ "ApexDC++ Speed-Mod":"DiCe!++",
#' "StrongDC++ SQLite" -+ "FlylinkDC++ ver >= 5xx",
#' "ApexDC++ Speed-Mod" -+ "FlylinkDC++ ver <= 4xx",
#' "ApexDC++ Speed-Mod" -+ "GreylinkDC++",
#' "FlylinkDC++ ver <= 4xx" -+ "FlylinkDC++ ver >= 5xx",
#' "FlylinkDC++ ver <= 4xx" -+ AvaLink,
#' "GreylinkDC++" -+ AvaLink:"RayLinkDC++":"SparkDC++":PeLink
#' )
#'
#' ## Use edge types
#' E(DC)$lty <- 1
#' E(DC)["BCDC++" %->% "StrongDC++"]$lty <- 2
#' E(DC)["FreeDC++" %->% "EiskaltDC++"]$lty <- 2
#' E(DC)["ApexDC++" %->% "FlylinkDC++ ver <= 4xx"]$lty <- 2
#' E(DC)["ApexDC++" %->% "DiCe!++"]$lty <- 2
#' E(DC)["StrongDC++ SQLite" %->% "FlylinkDC++ ver >= 5xx"]$lty <- 2
#' E(DC)["GreylinkDC++" %->% "AvaLink"]$lty <- 2
#'
#' ## Layers, as on the plot
#' layers <- list(
#' c("DC++"),
#' c("LinuxDC++", "BCDC++"),
#' c("FreeDC++", "StrongDC++"),
#' c(
#' "BMDC++", "EiskaltDC++", "AirDC++", "zK++", "ApexDC++",
#' "TkDC++", "RSX++"
#' ),
#' c("StrongDC++ SQLite", "ApexDC++ Speed-Mod", "DiCe!++"),
#' c("FlylinkDC++ ver <= 4xx", "GreylinkDC++"),
#' c(
#' "FlylinkDC++ ver >= 5xx", "AvaLink", "RayLinkDC++",
#' "SparkDC++", "PeLink"
#' )
#' )
#'
#' ## Check that we have all nodes
#' all(sort(unlist(layers)) == sort(V(DC)$name))
#'
#' ## Add some graphical parameters
#' V(DC)$color <- "white"
#' V(DC)$shape <- "rectangle"
#' V(DC)$size <- 20
#' V(DC)$size2 <- 10
#' V(DC)$label <- lapply(V(DC)$name, function(x) {
#' paste(strwrap(x, 12), collapse = "\n")
#' })
#' E(DC)$arrow.size <- 0.5
#'
#' ## Create a similar layout using the predefined layers
#' lay1 <- layout_with_sugiyama(DC, layers = apply(sapply(
#' layers,
#' function(x) V(DC)$name %in% x
#' ), 1, which))
#'
#' ## Simple plot, not very nice
#' par(mar = rep(.1, 4))
#' plot(DC, layout = lay1$layout, vertex.label.cex = 0.5)
#'
#' ## Sugiyama plot
#' plot(lay1$extd_graph, vertex.label.cex = 0.5)
#'
#' ## The same with automatic layer calculation
#' ## Keep vertex/edge attributes in the extended graph
#' lay2 <- layout_with_sugiyama(DC, attributes = "all")
#' plot(lay2$extd_graph, vertex.label.cex = 0.5)
#'
#' ## Another example, from the following paper:
#' ## Markus Eiglsperger, Martin Siebenhaller, Michael Kaufmann:
#' ## An Efficient Implementation of Sugiyama's Algorithm for
#' ## Layered Graph Drawing, Journal of Graph Algorithms and
#' ## Applications 9, 305--325 (2005).
#'
#' ex <- graph_from_literal(
#' 0 -+ 29:6:5:20:4,
#' 1 -+ 12,
#' 2 -+ 23:8,
#' 3 -+ 4,
#' 4,
#' 5 -+ 2:10:14:26:4:3,
#' 6 -+ 9:29:25:21:13,
#' 7,
#' 8 -+ 20:16,
#' 9 -+ 28:4,
#' 10 -+ 27,
#' 11 -+ 9:16,
#' 12 -+ 9:19,
#' 13 -+ 20,
#' 14 -+ 10,
#' 15 -+ 16:27,
#' 16 -+ 27,
#' 17 -+ 3,
#' 18 -+ 13,
#' 19 -+ 9,
#' 20 -+ 4,
#' 21 -+ 22,
#' 22 -+ 8:9,
#' 23 -+ 9:24,
#' 24 -+ 12:15:28,
#' 25 -+ 11,
#' 26 -+ 18,
#' 27 -+ 13:19,
#' 28 -+ 7,
#' 29 -+ 25
#' )
#'
#' layers <- list(
#' 0, c(5, 17), c(2, 14, 26, 3), c(23, 10, 18), c(1, 24),
#' 12, 6, c(29, 21), c(25, 22), c(11, 8, 15), 16, 27, c(13, 19),
#' c(9, 20), c(4, 28), 7
#' )
#'
#' layex <- layout_with_sugiyama(ex, layers = apply(
#' sapply(
#' layers,
#' function(x) V(ex)$name %in% as.character(x)
#' ),
#' 1, which
#' ))
#'
#' origvert <- c(rep(TRUE, vcount(ex)), rep(FALSE, nrow(layex$layout.dummy)))
#' realedge <- as_edgelist(layex$extd_graph)[, 2] <= vcount(ex)
#' plot(layex$extd_graph,
#' vertex.label.cex = 0.5,
#' edge.arrow.size = .5,
#' vertex.size = ifelse(origvert, 5, 0),
#' vertex.shape = ifelse(origvert, "square", "none"),
#' vertex.label = ifelse(origvert, V(ex)$name, ""),
#' edge.arrow.mode = ifelse(realedge, 2, 0)
#' )
#'
layout_with_sugiyama <- function(
graph,
layers = NULL,
hgap = 1,
vgap = 1,
maxiter = 100,
weights = NULL,
attributes = c("default", "all", "none")
) {
attributes <- igraph_match_arg(attributes)
res <- layout_sugiyama_impl(
graph = graph,
layers = layers,
hgap = hgap,
vgap = vgap,
maxiter = maxiter,
weights = weights
)
# Flip the y coordinates, more natural this way
vc <- vcount(graph)
if (vc > 0) {
res$res[, 2] <- max(res$res[, 2]) - res$res[, 2] + 1
}
# Separate real and dummy vertices
if (nrow(res$res) == vc) {
res$layout <- res$res
res$layout.dummy <- matrix(NA_real_, nrow = 0, ncol = 2)
} else {
res$layout <- res$res[seq_len(vc), ]
res$layout.dummy <- res$res[(vc + 1):nrow(res$res), , drop = FALSE]
}
# Add some attributes to the extended graph
E(res$extd_graph)$orig <- res$extd_to_orig_eids
res$extd_to_orig_eids <- NULL
res$extd_graph <- set_vertex_attr(
res$extd_graph,
"dummy",
value = c(
rep(FALSE, vc),
rep(TRUE, nrow(res$res) - vc)
)
)
res$extd_graph$layout <- rbind(res$layout, res$layout.dummy)
if (attributes == "default" || attributes == "all") {
if ("size" %in% vertex_attr_names(graph)) {
V(res$extd_graph)$size <- 0
V(res$extd_graph)$size[!V(res$extd_graph)$dummy] <- V(graph)$size
}
if ("size2" %in% vertex_attr_names(graph)) {
V(res$extd_graph)$size2 <- 0
V(res$extd_graph)$size2[!V(res$extd_graph)$dummy] <- V(graph)$size2
}
if ("shape" %in% vertex_attr_names(graph)) {
V(res$extd_graph)$shape <- "none"
V(res$extd_graph)$shape[!V(res$extd_graph)$dummy] <- V(graph)$shape
}
if ("label" %in% vertex_attr_names(graph)) {
V(res$extd_graph)$label <- ""
V(res$extd_graph)$label[!V(res$extd_graph)$dummy] <- V(graph)$label
}
if ("color" %in% vertex_attr_names(graph)) {
V(res$extd_graph)$color <- head(V(graph)$color, 1)
V(res$extd_graph)$color[!V(res$extd_graph)$dummy] <- V(graph)$color
}
eetar <- as_edgelist(res$extd_graph, names = FALSE)[, 2]
E(res$extd_graph)$arrow.mode <- 0
if ("arrow.mode" %in% edge_attr_names(graph)) {
E(res$extd_graph)$arrow.mode[eetar <= vc] <- E(graph)$arrow.mode
} else {
E(res$extd_graph)$arrow.mode[eetar <= vc] <- is_directed(graph) * 2
}
if ("arrow.size" %in% edge_attr_names(graph)) {
E(res$extd_graph)$arrow.size <- 0
E(res$extd_graph)$arrow.size[eetar <= vc] <- E(graph)$arrow.size
}
}
if (attributes == "all") {
gatt <- setdiff(graph_attr_names(graph), "layout")
vatt <- setdiff(
vertex_attr_names(graph),
c("size", "size2", "shape", "label", "color")
)
eatt <- setdiff(
edge_attr_names(graph),
c("arrow.mode", "arrow.size")
)
for (ga in gatt) {
res$extd_graph <- set_graph_attr(
res$extd_graph,
ga,
graph_attr(graph, ga)
)
}
for (va in vatt) {
notdummy <- which(!V(res$extd_graph)$dummy)
res$extd_graph <- set_vertex_attr(
res$extd_graph,
va,
notdummy,
vertex_attr(graph, va)
)
}
for (ea in eatt) {
eanew <- edge_attr(graph, ea)[E(res$extd_graph)$orig]
res$extd_graph <- set_edge_attr(res$extd_graph, ea, value = eanew)
}
}
res$res <- NULL
res
}
#' @rdname layout_with_sugiyama
#' @param ... Passed to `layout_with_sugiyama()`.
#' @export
with_sugiyama <- function(...) layout_spec(layout_with_sugiyama, ...)
## ----------------------------------------------------------------
#' Merging graph layouts
#'
#' Place several graphs on the same layout
#'
#' `merge_coords()` takes a list of graphs and a list of coordinates and
#' places the graphs in a common layout. The method to use is chosen via the
#' `method` parameter, although right now only the `dla` method is
#' implemented.
#'
#' The `dla` method covers the graph with circles. Then it sorts the
#' graphs based on the number of vertices first and places the largest graph at
#' the center of the layout. Then the other graphs are placed in decreasing
#' order via a DLA (diffision limited aggregation) algorithm: the graph is
#' placed randomly on a circle far away from the center and a random walk is
#' conducted until the graph walks into the larger graphs already placed or
#' walks too far from the center of the layout.
#'
#' The `layout_components()` function disassembles the graph first into
#' maximal connected components and calls the supplied `layout` function
#' for each component separately. Finally it merges the layouts via calling
#' `merge_coords()`.
#'
#' @param graphs A list of graph objects.
#' @param layouts A list of two-column matrices.
#' @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.
#' @param \dots Additional arguments to pass to the `layout` layout
#' function.
#' @return A matrix with two columns and as many lines as the total number of
#' vertices in the graphs.
#' @author Gabor Csardi \email{csardi.gabor@@gmail.com}
#' @seealso [plot.igraph()], [tkplot()],
#' [layout()], [disjoint_union()]
#' @export
#' @family graph layouts
#' @keywords graphs
#' @examples
#'
#' # create 20 scale-free graphs and place them in a common layout
#' graphs <- lapply(sample(5:20, 20, replace = TRUE),
#' barabasi.game,
#' directed = FALSE
#' )
#' layouts <- lapply(graphs, layout_with_kk)
#' 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") {
lapply(graphs, ensure_igraph)
if (method != "dla") {
cli::cli_abort("{.arg method} must be {.str dla}, not {.str {method}}.")
}
on.exit(.Call(Rx_igraph_finalizer))
.Call(
Rx_igraph_layout_merge_dla,
graphs,
layouts
)
}
#' Normalize coordinates for plotting graphs
#'
#' Rescale coordinates linearly to be within given bounds.
#'
#' `norm_coords()` normalizes a layout, it linearly transforms each
#' coordinate separately to fit into the given limits.
#'
#' @param layout A matrix with two or three columns, the layout to normalize.
#' @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
#' both are `NULL` then no normalization is performed along this
#' direction.
#' @param zmin,zmax The limits for the third coordinate, if one of them or both
#' are `NULL` then no normalization is performed along this direction.
#' @return A numeric matrix with at the same dimension as `layout`.
#' @author Gabor Csardi \email{csardi.gabor@@gmail.com}
#' @export
#' @family graph layouts
#' @keywords graphs
norm_coords <- function(
layout,
xmin = -1,
xmax = 1,
ymin = -1,
ymax = 1,
zmin = -1,
zmax = 1
) {
if (!is.matrix(layout)) {
cli::cli_abort(
"{.arg layout} must be a matrix, not {.obj_type_friendly {layout}}."
)
}
if (!(ncol(layout) %in% c(2, 3))) {
cli::cli_abort(
"{.arg layout} should have 2 or 3 columns, not {ncol(layout)}."
)
}
if (!is.null(xmin) && !is.null(xmax)) {
layout[, 1] <- .layout.norm.col(layout[, 1], xmin, xmax)
}
if (!is.null(ymin) && !is.null(ymax)) {
layout[, 2] <- .layout.norm.col(layout[, 2], ymin, ymax)
}
if (ncol(layout) == 3 && !is.null(zmin) && !is.null(zmax)) {
layout[, 3] <- .layout.norm.col(layout[, 3], zmin, zmax)
}
layout
}
.layout.norm.col <- function(v, min, max) {
# Likely cause: division by zero upstream
if (all(is.nan(v))) {
diff <- 0
} else {
vr <- range(v)
diff <- diff(vr)
}
if (diff == 0) {
return(rep((min + max) / 2, length(v)))
}
fac <- (max - min) / diff
(v - vr[[1]]) * fac + min
}
#' @rdname merge_coords
#' @param graph The input graph.
#' @export
layout_components <- function(graph, layout = layout_with_kk, ...) {
ensure_igraph(graph)
V(graph)$id <- seq(vcount(graph))
gl <- decompose(graph)
ll <- lapply(gl, layout, ...)
l <- merge_coords(gl, ll)
l[unlist(sapply(gl, vertex_attr, "id")), ] <- l[]
l
}
#' Spring layout, this was removed from igraph
#' @description
#' `r lifecycle::badge("deprecated")`
#'
#' Now it calls the Fruchterman-Reingold layout [layout_with_fr()].
#'
#' @param graph Input graph.
#' @param ... Extra arguments are ignored.
#' @return Layout coordinates, a two column matrix.
#'
#' @export
#' @keywords internal
layout.spring <- function(graph, ...) {
lifecycle::deprecate_warn("2.1.0", "layout.spring()", "layout_with_fr()")
layout_with_fr(graph)
}
#' SVD layout, this was removed from igraph
#'
#' @description
#' `r lifecycle::badge("deprecated")`
#'
#' Now it calls the Fruchterman-Reingold layout [layout_with_fr()].
#'
#' @param graph Input graph.
#' @param ... Extra arguments are ignored.
#' @return Layout coordinates, a two column matrix.
#'
#' @keywords internal
#' @export
layout.svd <- function(graph, ...) {
lifecycle::deprecate_warn("2.1.0", "layout.svd()", "layout_with_fr()")
layout_with_fr(graph)
}
#' Grid Fruchterman-Reingold layout, this was removed from igraph
#'
#' @description
#' `r lifecycle::badge("deprecated")`
#'
#'
#' Now it calls the Fruchterman-Reingold layout [layout_with_fr()].
#'
#' @param graph Input graph.
#' @param ... Extra arguments are ignored.
#' @return Layout coordinates, a two column matrix.
#'
#' @keywords internal
#' @export
layout.fruchterman.reingold.grid <- function(graph, ...) {
lifecycle::deprecate_warn(
"2.1.0",
"layout.fruchterman.reingold.grid()",
"layout_with_fr()"
)
layout_with_fr(graph)
}
#' The DrL graph layout generator
#'
#' @description
#' `r lifecycle::badge("deprecated")`
#'
#' `layout.drl()` was renamed to [layout_with_drl()] to create a more
#' consistent API.
#' @inheritParams layout_with_drl
#' @keywords internal
#' @export
layout.drl <- function(
graph,
use.seed = FALSE,
seed = matrix(runif(vcount(graph) * 2), ncol = 2),
options = drl_defaults$default,
weights = NULL,
dim = 2
) {
# nocov start
lifecycle::deprecate_soft("2.0.0", "layout.drl()", "layout_with_drl()")
layout_with_drl(
graph = graph,
use.seed = use.seed,
seed = seed,
options = options,
weights = weights,
dim = dim
)
} # nocov end
#' The DrL graph layout generator
#'
#' DrL is a force-directed graph layout toolbox focused on real-world
#' large-scale graphs, developed by Shawn Martin and colleagues at Sandia
#' National Laboratories.
#'
#' This function implements the force-directed DrL layout generator.
#'
#' The generator has the following parameters:
#' \describe{
#' \item{edge.cut}{
#' Edge cutting is done in the late stages of the algorithm in order to achieve less dense layouts. Edges are cut if there is a lot of stress on them (a large value in the objective function sum). The edge cutting parameter is a value between 0 and 1 with 0 representing no edge cutting and 1 representing maximal edge cutting.
#' }
#' \item{init.iterations}{
#' Number of iterations in the first phase.
#' }
#' \item{init.temperature}{
#' Start temperature, first phase.
#' }
#' \item{init.attraction}{
#' Attraction, first phase.
#' }
#' \item{init.damping.mult}{
#' Damping, first phase.
#' }
#' \item{liquid.iterations}{
#' Number of iterations, liquid phase.
#' }
#' \item{liquid.temperature}{
#' Start temperature, liquid phase.
#' }
#' \item{liquid.attraction}{
#' Attraction, liquid phase.
#' }
#' \item{liquid.damping.mult}{
#' Damping, liquid phase.
#' }
#' \item{expansion.iterations}{
#' Number of iterations, expansion phase.
#' }
#' \item{expansion.temperature}{
#' Start temperature, expansion phase.
#' }
#' \item{expansion.attraction}{
#' Attraction, expansion phase.
#' }
#' \item{expansion.damping.mult}{
#' Damping, expansion phase.
#' }
#' \item{cooldown.iterations}{
#' Number of iterations, cooldown phase.
#' }
#' \item{cooldown.temperature}{
#' Start temperature, cooldown phase.
#' }
#' \item{cooldown.attraction}{
#' Attraction, cooldown phase.
#' }
#' \item{cooldown.damping.mult}{
#' Damping, cooldown phase.
#' }
#' \item{crunch.iterations}{
#' Number of iterations, crunch phase.
#' }
#' \item{crunch.temperature}{
#' Start temperature, crunch phase.
#' }
#' \item{crunch.attraction}{
#' Attraction, crunch phase.
#' }
#' \item{crunch.damping.mult}{
#' Damping, crunch phase.
#' }
#' \item{simmer.iterations}{
#' Number of iterations, simmer phase.
#' }
#' \item{simmer.temperature}{
#' Start temperature, simmer phase.
#' }
#' \item{simmer.attraction}{
#' Attraction, simmer phase.
#' }
#' \item{simmer.damping.mult}{
#' Damping, simmer phase.
#' }
#' }
#'
#' There are five pre-defined parameter settings as well, these are called
#' `drl_defaults$default`, `drl_defaults$coarsen`,
#' `drl_defaults$coarsest`, `drl_defaults$refine` and
#' `drl_defaults$final`.
#'
#' @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.
#' @param use.seed Logical scalar, 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
#' vertices is `use.seed` is `TRUE`. It is ignored otherwise.
#' @param options Options for the layout generator, a named list. See details
#' below.
#' @param weights The weights of the edges. It must be a positive numeric vector,
#' `NULL` or `NA`. If it is `NULL` and the input graph has a
#' \sQuote{weight} edge attribute, then that attribute will be used. If
#' `NULL` and no such attribute is present, then the edges will have equal
#' weights. Set this to `NA` if the graph was a \sQuote{weight} edge
#' attribute, but you don't want to use it for the layout. Larger edge weights
#' correspond to stronger connections.
#' @param dim Either \sQuote{2} or \sQuote{3}, it specifies whether we want a
#' 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.
#' @return A numeric matrix with two columns.
#' @author Shawn Martin (<https://www.cs.otago.ac.nz/homepages/smartin/>)
#' and Gabor Csardi \email{csardi.gabor@@gmail.com} for the R/igraph interface
#' and the three dimensional version.
#' @seealso [layout()] for other layout generators.
#' @references See the following technical report: Martin, S., Brown, W.M.,
#' Klavans, R., Boyack, K.W., DrL: Distributed Recursive (Graph) Layout. SAND
#' Reports, 2008. 2936: p. 1-10.
#' @family layout_drl
#' @export
#' @importFrom stats runif
#' @keywords graphs
#' @examples
#'
#' g <- as_undirected(sample_pa(100, m = 1))
#' l <- layout_with_drl(g, options = list(simmer.attraction = 0))
#' plot(g, layout = l, vertex.size = 3, vertex.label = NA)
#'
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)
) {
ensure_igraph(graph)
dim <- igraph_match_arg(dim)
use.seed <- as.logical(use.seed)
seed <- as.matrix(seed)
options <- modify_list(drl_defaults$default, options)
if (is.null(weights) && "weight" %in% edge_attr_names(graph)) {
weights <- E(graph)$weight
}
if (!is.null(weights) && !any(is.na(weights))) {
weights <- as.numeric(weights)
} else {
weights <- NULL
}
on.exit(.Call(Rx_igraph_finalizer))
if (dim == 2) {
res <- .Call(
Rx_igraph_layout_drl,
graph,
seed,
use.seed,
options,
weights
)
} else {
res <- .Call(
Rx_igraph_layout_drl_3d,
graph,
seed,
use.seed,
options,
weights
)
}
res
}
#' @rdname layout_with_drl
#' @param ... Passed to `layout_with_drl()`.
#' @family layout_drl
#' @export
with_drl <- function(...) layout_spec(layout_with_drl, ...)
#' @family layout_drl
#' @export
igraph.drl.default <- list(
edge.cut = 32 / 40,
init.iterations = 0,
init.temperature = 2000,
init.attraction = 10,
init.damping.mult = 1.0,
liquid.iterations = 200,
liquid.temperature = 2000,
liquid.attraction = 10,
liquid.damping.mult = 1.0,
expansion.iterations = 200,
expansion.temperature = 2000,
expansion.attraction = 2,
expansion.damping.mult = 1.0,
cooldown.iterations = 200,
cooldown.temperature = 2000,
cooldown.attraction = 1,
cooldown.damping.mult = .1,
crunch.iterations = 50,
crunch.temperature = 250,
crunch.attraction = 1,
crunch.damping.mult = 0.25,
simmer.iterations = 100,
simmer.temperature = 250,
simmer.attraction = .5,
simmer.damping.mult = 0
)
#' @family layout_drl
#' @export
igraph.drl.coarsen <- list(
edge.cut = 32 / 40,
init.iterations = 0,
init.temperature = 2000,
init.attraction = 10,
init.damping.mult = 1.0,
liquid.iterations = 200,
liquid.temperature = 2000,
liquid.attraction = 2,
liquid.damping.mult = 1.0,
expansion.iterations = 200,
expansion.temperature = 2000,
expansion.attraction = 10,
expansion.damping.mult = 1.0,
cooldown.iterations = 200,
cooldown.temperature = 2000,
cooldown.attraction = 1,
cooldown.damping.mult = .1,
crunch.iterations = 50,
crunch.temperature = 250,
crunch.attraction = 1,
crunch.damping.mult = 0.25,
simmer.iterations = 100,
simmer.temperature = 250,
simmer.attraction = .5,
simmer.damping.mult = 0
)
#' @family layout_drl
#' @export
igraph.drl.coarsest <- list(
edge.cut = 32 / 40,
init.iterations = 0,
init.temperature = 2000,
init.attraction = 10,
init.damping.mult = 1.0,
liquid.iterations = 200,
liquid.temperature = 2000,
liquid.attraction = 2,
liquid.damping.mult = 1.0,
expansion.iterations = 200,
expansion.temperature = 2000,
expansion.attraction = 10,
expansion.damping.mult = 1.0,
cooldown.iterations = 200,
cooldown.temperature = 2000,
cooldown.attraction = 1,
cooldown.damping.mult = .1,
crunch.iterations = 200,
crunch.temperature = 250,
crunch.attraction = 1,
crunch.damping.mult = 0.25,
simmer.iterations = 100,
simmer.temperature = 250,
simmer.attraction = .5,
simmer.damping.mult = 0
)
#' @family layout_drl
#' @export
igraph.drl.refine <- list(
edge.cut = 32 / 40,
init.iterations = 0,
init.temperature = 50,
init.attraction = .5,
init.damping.mult = 1.0,
liquid.iterations = 0,
liquid.temperature = 2000,
liquid.attraction = 2,
liquid.damping.mult = 1.0,
expansion.iterations = 50,
expansion.temperature = 500,
expansion.attraction = .1,
expansion.damping.mult = .25,
cooldown.iterations = 50,
cooldown.temperature = 250,
cooldown.attraction = 1,
cooldown.damping.mult = .1,
crunch.iterations = 50,
crunch.temperature = 250,
crunch.attraction = 1,
crunch.damping.mult = 0.25,
simmer.iterations = 0,
simmer.temperature = 250,
simmer.attraction = .5,
simmer.damping.mult = 0
)
#' @family layout_drl
#' @export
igraph.drl.final <- list(
edge.cut = 32 / 40,
init.iterations = 0,
init.temperature = 50,
init.attraction = .5,
init.damping.mult = 0,
liquid.iterations = 0,
liquid.temperature = 2000,
liquid.attraction = 2,
liquid.damping.mult = 1.0,
expansion.iterations = 50,
expansion.temperature = 2000,
expansion.attraction = 2,
expansion.damping.mult = 1.0,
cooldown.iterations = 50,
cooldown.temperature = 200,
cooldown.attraction = 1,
cooldown.damping.mult = .1,
crunch.iterations = 50,
crunch.temperature = 250,
crunch.attraction = 1,
crunch.damping.mult = 0.25,
simmer.iterations = 25,
simmer.temperature = 250,
simmer.attraction = .5,
simmer.damping.mult = 0
)
#' @family layout_drl
#' @export
drl_defaults <- list(
coarsen = igraph.drl.coarsen,
coarsest = igraph.drl.coarsest,
default = igraph.drl.default,
final = igraph.drl.final,
refine = igraph.drl.refine
)
#' Align a vertex layout
#' This function centers a vertex layout on the coordinate system origin and
#' rotates the layout to achieve a visually pleasing alignment with the coordinate
#' axes. Doing this is particularly useful with force-directed layouts such as [layout_with_fr()].
#' @param graph The graph whose layout is to be aligned.
#' @param layout A matrix whose rows are the coordinates of vertices.
#' @return modified layout matrix
#' @export
#' @examples
#' g <- make_lattice(c(3, 3))
#' l1 <- layout_with_fr(g)
#' l2 <- align_layout(g,l1)
#' plot(g, layout = l1)
#' plot(g, layout = l2)
align_layout <- function(graph, layout) {
layout_align_impl(
graph = graph,
layout = layout
)
}
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.