Nothing
#' Fast version of graph plotting.
#'
#' This is a fast, deterministic and stand-alone function for visualizing the
#' admixture graph. Has the bad habit if sometimes drawing several nodes at the
#' exact same coordinates; for clearer reasults try \code{\link{plot.agraph}}
#' (which, on the other hand, relies on numerical optimising of a compicated cost
#' function and might be unpredictable).
#'
#' @param x The admixture graph.
#' @param ordered_leaves The leaf-nodes in the left to right order they
#' should be drawn.
#' @param show_admixture_labels A flag determining if the plot should include
#' the names of admixture proportions.
#' @param show_inner_node_labels A flag determining if the plot should include
#' the names of inner nodes.
#' @param ... Additional plotting options.
#'
#' @return A plot.
#'
#' @seealso \code{\link{plot.agraph}}
#'
#' @examples
#' # taken from the collection of all the admixture graphs with four leaves and at
#' # most two admixture events:
#'
#' fast_plot(four_leaves_graphs[[24]](c("A", "B", "C", "D")))
#'
#' # To be fair, here is a graph that looks all right:
#'
#' fast_plot(four_leaves_graphs[[25]](c("A", "B", "C", "D")))
#'
#' @export
fast_plot <- function(x,
ordered_leaves = NULL,
show_admixture_labels = FALSE,
show_inner_node_labels = FALSE,
...) {
graph <- x
if (is.null(ordered_leaves))
ordered_leaves <- graph$leaves
dfs <- function(node, basis, step) {
result <- rep(NA, length(graph$nodes))
names(result) <- graph$nodes
dfs_ <- function(node) {
children <- which(graph$children[node,])
if (length(children) == 0) {
result[node] <<- basis(node)
} else {
result[node] <<- step(vapply(children, dfs_, numeric(1)))
}
}
dfs_(node)
result
}
no_parents <- function(node) length(which(graph$parents[node, ]))
roots <- which(Map(no_parents, graph$nodes) == 0)
if (length(roots) > 1) stop("Don't know how to handle more than one root")
root <- roots[1]
ypos <- dfs(root, basis = function(x) 0.0, step = function(x) max(x) + 1.0)
leaf_index <- function(n) {
result <- which(graph$nodes[n] == ordered_leaves)
if (length(result) != 1) stop("Unexpected number of matching nodes")
result
}
left_x <- dfs(root, basis = leaf_index, step = min)
right_x <- dfs(root, basis = leaf_index, step = max)
xpos <- left_x + (right_x - left_x) / 2.0
# Start the actual drawing of the graph...
graphics::plot(xpos, ypos, type = "n", axes = FALSE, frame.plot = FALSE,
xlab = "", ylab = "", ylim = c(-1, max(ypos) + 0.5), ...)
for (node in graph$nodes) {
parents <- graph$nodes[graph$parents[node, ]]
if (length(parents) == 1) {
graphics::lines(c(xpos[node],xpos[parents]), c(ypos[node], ypos[parents]))
} else if (length(parents) == 2) {
break_y <- ypos[node]
break_x_left <- xpos[node] - 0.3
break_x_right <- xpos[node] + 0.3
if (xpos[parents[1]] < xpos[parents[2]]) {
graphics::lines(c(xpos[parents[1]], break_x_left), c(ypos[parents[1]], break_y))
graphics::lines(c(xpos[parents[2]], break_x_right), c(ypos[parents[2]], break_y))
} else {
graphics::lines(c(xpos[parents[2]], break_x_left), c(ypos[parents[2]], break_y))
graphics::lines(c(xpos[parents[1]], break_x_right), c(ypos[parents[1]], break_y))
}
graphics::segments(break_x_left, break_y, xpos[node], ypos[node], col = "red")
graphics::segments(break_x_right, break_y, xpos[node], ypos[node], col = "red")
if (show_admixture_labels) {
if (xpos[parents[1]] < xpos[parents[2]]) {
graphics::text(break_x_left, break_y, graph$probs[parents[[1]], node],
cex = 0.5, pos = 1, col = "red", offset = 0.1)
graphics::text(break_x_right, break_y, graph$probs[parents[[2]], node],
cex = 0.5, pos = 1, col = "red", offset = 0.1)
} else {
graphics::text(break_x_left, break_y, graph$probs[parents[[2]], node],
cex = 0.5, pos = 1, col = "red", offset = 0.1)
graphics::text(break_x_right, break_y, graph$probs[parents[[1]], node],
cex = 0.5, pos = 1, col = "red", offset = 0.1)
}
}
}
}
is_inner <- Vectorize(function(n) sum(graph$children[n, ]) > 0)
inner_nodes <- which(is_inner(graph$nodes))
leaves <- which(!is_inner(graph$nodes))
if (show_inner_node_labels) {
graphics::text(xpos[inner_nodes], ypos[inner_nodes],
labels = graph$nodes[inner_nodes], cex = 0.6, col = "blue", pos = 3)
}
graphics::text(xpos[leaves], ypos[leaves], labels = graph$nodes[leaves],
cex = 0.7, col = "black", pos = 1)
invisible()
}
#' Plot an admixture graph.
#'
#' This is a basic drawing routine for visualising the graph. Uses Nelder-Mead
#' algorithm and complicated heuristic approach to find aestethic node coordinates,
#' but due to bad luck or an oversight in the heuristics, especially with larger
#' graphs, might sometimes produce a weird looking result. Usually plotting again
#' helps and if not, use the optional parameters to help the algorithm or try the
#' faster and deterministic function \code{\link{fast_plot}} (which unfortunately is
#' not very good at handling multiple admixture events).
#'
#' @param x The admixture graph.
#' @param show_leaf_labels A flag determining if leaf names are shown.
#' @param draw_leaves A flag determining if leaf nodes are drawn as
#' little circles.
#' @param color Color of all drawn nodes unless overriden by
#' \code{inner_node_color}.
#' @param show_inner_node_labels A flag determining if the plot should include
#' the names of inner nodes.
#' @param draw_inner_nodes A flag determining if inner nodes are drawn as
#' little circles.
#' @param inner_node_color Color of inner node circles, if drawn.
#' @param show_admixture_labels A flag determining if the plot should include
#' the names of admixture proportions.
#' @param parent_order An optional list of instuctions on which order
#' from left to right to draw the parents of nodes.
#' The list should contain character vectors of parents
#' with the name of the child, \emph{e.g.}
#' \code{child = c("left_parent", "right_parent")}.
#' Using automated heuristics for nodes not specified.
#' @param child_order An optional list of instuctions on which order
#' from left to right to draw the children of nodes.
#' The list should contain character vectors of children
#' with the name of the parent, \emph{e.g.}
#' \code{parent = c("left_child", "right_child")}.
#' Using automated heuristics for nodes not specified.
#' @param leaf_order An optional vector describing in which order should
#' the leaves be drawn. Using automated heuristic
#' depending on \code{parent_order} and \code{child_order}
#' if not specified. Accepts both a character vector of
#' the leaves or a numeric vector interpreted as a
#' permutation of the default order.
#' @param fix If nothing else helps, the list \code{fix} can be used to
#' correct the inner node coordinates given by the heuristics.
#' Should contain numeric vectors of length 2 with the name
#' of an inner node, \emph{e.g.} \code{inner_node = c(0, 10)},
#' moving \code{inner_node} to the right 10 units where 100 is
#' the plot width. Non-specified inner nodes are left in peace.
#' @param platform By default admixture nodes are drawn with a horizontal
#' platform for proportion labels, the width of which is
#' half the distance between any two leaves. The number
#' \code{platform} tells how many default platform widths
#' should the platforms be wide, \emph{i. e.} zero means no
#' platform.
#' @param title Optional title for the plot.
#' @param ... Additional plotting options.
#'
#' @return A plot.
#'
#' @seealso \code{\link{agraph}}
#' @seealso \code{\link{fast_plot}}
#'
#' @examples
#' \donttest{
#' leaves <- c("salmon", "sea horse", "mermaid", "horse", "human", "lobster")
#' inner_nodes <- c("R", "s", "t", "u", "v", "w", "x", "y", "z")
#' edges <- parent_edges(c(edge("salmon", "t"),
#' edge("sea horse", "y"),
#' edge("mermaid", "z"),
#' edge("horse", "w"),
#' edge("human", "x"),
#' edge("lobster", "R"),
#' edge("s", "R"),
#' edge("t", "s"),
#' edge("u", "t"),
#' edge("v", "s"),
#' edge("w", "v"),
#' edge("x", "v"),
#' admixture_edge("y", "u", "w"),
#' admixture_edge("z", "u", "x")))
#' admixtures <- admixture_proportions(c(admix_props("y", "u", "w", "a"),
#' admix_props("z", "u", "x", "b")))
#' graph <- agraph(leaves, inner_nodes, edges, admixtures)
#' plot(graph, show_inner_node_labels = TRUE)
#'
#' # Suppose now that we prefer to have the outgroup "lobster" on the right side.
#' # This is achieved by telling the algorithm that the children of "R" should be in
#' # the order ("s", "lobster"), from left to right:
#'
#' plot(graph, child_order = list(R = c("s", "lobster")))
#'
#' # Suppose further that we prefer to have "mermaid" and "human" next to each other,
#' # as well as "sea horse" and "horse". This is easily achieved by rearranging the leaf
#' # order proposed by the algorithm. We can also fine-tune by moving "y" a little bit
#' # to the right, make the admixture platforms a bit shorter, color the nodes, show the
#' # admixture proportions and give the plot a title:
#'
#' plot(graph, child_order = list(R = c("s", "lobster")), leaf_order = c(1, 2, 4, 3, 5, 6),
#' fix = list(y = c(5, 0)), platform = 0.8, color = "deepskyblue",
#' inner_node_color = "skyblue", show_admixture_labels = TRUE,
#' title = "Evolution of fish/mammal hybrids")
#' }
#'
#' @export
plot.agraph <- function(x,
show_leaf_labels = TRUE,
draw_leaves = TRUE,
color = "yellowgreen",
show_inner_node_labels = FALSE,
draw_inner_nodes = FALSE,
inner_node_color = color,
show_admixture_labels = FALSE,
parent_order = list(),
child_order = list(),
leaf_order = NULL,
fix = list(),
platform = 1,
title = NULL,
...) {
# Combine the user instructions and automated heuristics about the graph orderings.
graph <- x
arranged <- arrange_graph(graph)
parent_order <- utils::modifyList(arranged$parent_order, parent_order)
child_order <- utils::modifyList(arranged$child_order, child_order)
if (is.null(leaf_order) == TRUE) {
leaf_order <- leaf_order(graph, parent_order, child_order)
} else if (typeof(leaf_order) != "character") {
machine_order <- leaf_order(graph, parent_order, child_order)
human_order <- leaf_order
leaf_order <- rep("", length(leaf_order))
for (i in seq(1, length(human_order))) {
leaf_order[i] <- machine_order[human_order[i]]
}
}
# Assign initial coordinates for all nodes.
leaves <- list()
if (length(leaf_order) > 1) {
for (i in seq(1, length(leaf_order))) {
leaves[[i]] <- c(100*(i - 1)/(length(leaf_order) - 1), 0)
}
} else {leaves[[1]] <- c(50, 0)}
names(leaves) <- leaf_order
parents <- graph$parents
for (i in seq(1, length(graph$inner_nodes))) {
candidate <- graph$inner_nodes[i]
abandon <- FALSE
for (j in seq(1, NCOL(parents))) {
if (parents[candidate, j] == TRUE) {
abandon <- TRUE
}
}
if (abandon == FALSE) {
root <- list(c(50, 100))
names(root) <- c(candidate)
delete <- i
}
}
root_removed <- graph$inner_nodes[-delete]
inner <- list()
if (length(root_removed) > 0) {
for (i in seq(1, length(root_removed))) {
inner[[i]] <- c(0, 0)
}
}
names(inner) <- root_removed
# Assign the y-coordinates according to my arbitrary principles.
refined_graph <- refined_graph(graph)
heights <- rep(0, length(inner))
names(heights) <- names(inner)
global_longest <- 0
for (inner_node in names(inner)) {
paths <- all_paths_to_leaves(refined_graph, inner_node)
longest <- 0
for (path in paths) {
if (length(path) > longest) {
longest <- length(path)
}
}
heights[inner_node] <- longest - 1
if (longest > global_longest) {
global_longest <- longest
}
}
for (inner_node in names(inner)) {
heights[inner_node] <- global_longest - heights[inner_node]
inner[[inner_node]][2] <- 100*(1 - heights[inner_node]/global_longest)
}
# Perform Nelder-Mead to optimize the x-coordinates of the non-root inner nodes.
if (length(inner) > 0) {
x0 <- rep(50, length(inner))
min <- rep(0, length(inner))
max <- rep(100, length(inner))
cfunc <- drawing_cost(graph, leaves, root, inner, child_order, parent_order, platform)
opti <- suppressWarnings(neldermead::fminbnd(cfunc, x0 = x0, xmin = min, xmax = max))
x <- neldermead::neldermead.get(opti, "xopt")
for (i in seq(1, length(inner))) {
inner[[i]][1] <- x[i]
}
}
# Plot everything asked for.
xpd <- graphics::par()$xpd
graphics::par(xpd = NA)
level <- platform*25/(max(2, length(leaves)) - 1)
for (inner_node in names(inner)) {
inner[[inner_node]][2] <- 100*(1 - heights[inner_node]/global_longest)
}
for (fixed in names(fix)) {
inner[[fixed]] <- inner[[fixed]] + fix[[fixed]]
}
nodes <- graph$nodes
coordinates <- c(leaves, root, inner)
graphics::plot(c(-level, 100 + level), c(0, 100), type = "n", axes = FALSE,
frame.plot = FALSE, xlab = "", ylab = "", main = title, ...)
for (i in nodes) {
for (j in nodes) {
if (parents[i, j] == TRUE) {
i_thing <- 0
if (length(parent_order[[i]]) == 2) {
if (parent_order[[i]][1] == j) {
i_thing <- -level
graphics::segments(coordinates[[i]][1] + i_thing, coordinates[[i]][2], coordinates[[i]][1] - i_thing,
coordinates[[i]][2], col = "black", lwd = 2)
}
if (parent_order[[i]][2] == j) {
i_thing <- level
}
if (show_admixture_labels == TRUE) {
label <- graph$probs[i, j]
if (substr(label, 1, 1) == "(") {
label <- substr(label, 2, nchar(label) - 1)
}
graphics::text(coordinates[[i]][1] + 0.75*i_thing, coordinates[[i]][2], label,
adj = c(0.5, 1.6), cex = 0.8)
}
}
graphics::segments(coordinates[[i]][1] + i_thing, coordinates[[i]][2], coordinates[[j]][1],
coordinates[[j]][2], col = "black", lwd = 2)
}
}
}
for (i in seq(1, length(leaves))) {
leaf <- leaves[[i]]
if (draw_leaves == TRUE) {
graphics::points(leaf[1], leaf[2], lwd = 2, pch = 21, col = "black", bg = color, cex = 2)
}
if (show_leaf_labels == TRUE) {
graphics::text(leaf[1], leaf[2], names(leaves)[i], adj = c(0.5, 2.6), cex = 0.8)
}
}
if (length(inner) > 0) {
for (i in seq(1, length(inner))) {
vertex <- inner[[i]]
if (draw_inner_nodes == TRUE) {
graphics::points(vertex[1], vertex[2], lwd = 2, pch = 21, col = "black", bg = inner_node_color, cex = 2)
}
if (show_inner_node_labels == TRUE) {
graphics::text(vertex[1], vertex[2], names(inner)[i], adj = c(0.5, -1.6), cex = 0.8)
}
}
}
juuri <- root[[1]]
if (draw_inner_nodes == TRUE) {
graphics::points(juuri[1], juuri[2], lwd = 2, pch = 21, col = "black", bg = inner_node_color, cex = 2)
}
if (show_inner_node_labels == TRUE) {
graphics::text(juuri[1], juuri[2], names(root)[1], adj = c(0.5, -1.6), cex = 0.8)
}
graphics::par(xpd = xpd)
}
drawing_cost <- function(graph, leaves, root, inner, child_order, parent_order, platform) {
function(x) {
# Put the variable x in place.
for (i in seq(1, length(inner))) {
inner[[i]][1] <- x[i]
}
# Calculate the cost of the input and default coordinates.
cost <- 0
new <- function(u, v) { # u and v are 2-dimensional vectors.
w <- u + v
U <- sqrt(u[1]^2 + u[2]^2)
V <- sqrt(v[1]^2 + v[2]^2)
W <- sqrt(w[1]^2 + w[2]^2)
kos <- u%*%v/(U*V + 1e-5)
if (v[1]*abs(u[2]) > u[1]*abs(v[2])) {
angle <- abs(kos)^4 # ARBITRARY CONSTANTS HERE!
if (angle > 0.95) {
angle <- angle + 5
}
} else {
angle <- 10 - kos
}
verticality <- abs(w%*%c(0, 1)/(W + 1e-5))
return((4 + 4*angle - 2*verticality)*(U + V)) # ARBITRARY CONSTANTS HERE!
}
new2 <- function(u) { # u is a two-dimensional vector.
U <- sqrt(u[1]^2 + u[2]^2)
verticality <- abs(u%*%c(0, 1)/(U + 1e-5))
return((4 - 2*verticality)*U) # ARBITRARY CONSTANTS HERE!
}
level <- platform*25/(max(2, length(leaves)) - 1)
kids <- c(leaves, inner)
mums <- c(root, inner)
all <- c(leaves, root, inner)
# First the cost of parents:
for (i in seq(1, length(kids))) {
p <- parent_order[[names(kids)[i]]]
c <- child_order[[names(kids)[i]]]
if (length(p) == 1) {
u <- c(all[[p[1]]][1] - kids[[i]][1], all[[p[1]]][2] - kids[[i]][2])
cost <- cost + new2(u)
}
if (length(p) == 2) {
u <- c(all[[p[1]]][1] - kids[[i]][1] - level, all[[p[1]]][2] - kids[[i]][2])
v <- c(all[[p[2]]][1] - kids[[i]][1] + level, all[[p[2]]][2] - kids[[i]][2])
cost <- cost + new(u, v)
}
}
# Then the cost of children:
for (i in seq(1, length(mums))) {
p <- parent_order[[names(mums)[i]]]
c <- child_order[[names(mums)[i]]]
if (length(c) == 1) {
if (length(parent_order[[c[1]]]) == 2) {
if (parent_order[[c[1]]][1] == names(mums)[[i]]) {
thing <- -level
}
if (parent_order[[c[1]]][2] == names(mums)[[i]]) {
thing <- level
}
} else {
thing <- 0
}
u <- c(all[[c[1]]][1] - mums[[i]][1] + thing, all[[c[1]]][2] - mums[[i]][2])
cost <- cost + new2(u)
}
if (length(c) > 1) {
# We handle more than 2 kids by looking at consecutive pairs.
for (j in seq(1, length(c) - 1)) {
if (length(parent_order[[c[j]]]) == 2) {
if (parent_order[[c[j]]][1] == names(mums)[[i]]) {
thing <- -level
}
if (parent_order[[c[j]]][2] == names(mums)[[i]]) {
thing <- level
}
} else {
thing <- 0
}
u <- c(all[[c[j]]][1] - mums[[i]][1] + thing, all[[c[j]]][2] - mums[[i]][2])
if (length(parent_order[[c[j + 1]]]) == 2) {
if (parent_order[[c[j + 1]]][1] == names(mums)[[i]]) {
thing <- -level
}
if (parent_order[[c[j + 1]]][2] == names(mums)[[i]]) {
thing <- level
}
} else {
thing <- 0
}
v <- c(all[[c[j + 1]]][1] - mums[[i]][1] + thing, all[[c[j + 1]]][2] - mums[[i]][2])
cost <- cost + new(u, v)
}
}
}
return(cost)
}
}
refined_graph <- function(graph) {
leaves <- graph$leaves
inner_nodes <- graph$inner_nodes
nodes <- graph$nodes
probs <- graph$probs
parents <- graph$parents
parent_of <- rep(NA, length(nodes))
names(parent_of) <- nodes
children <- graph$children
child_order <- list()
admix_nodes <- character()
parent_order <- list()
for (i in seq(1, length(nodes))) {
child_list <- character()
parent_list <- character()
parent_count <- 0
for (j in seq(1, length(nodes))) {
if (children[i, j] == TRUE) {
child_list <- c(child_list, nodes[j])
}
if (parents[i, j] == TRUE) {
parent_of[i] <- nodes[j]
parent_count <- parent_count + 1
parent_list <- c(parent_list, nodes[j])
}
}
child_order[[i]] <- child_list
names(child_order)[i] <- nodes[i]
if (parent_count == 0) {
root <- nodes[i]
}
if (parent_count == 1) {
parent_list <- character()
}
if (parent_count > 1) {
parent_of[i] <- NA
admix_nodes <- c(admix_nodes, nodes[i])
}
parent_order[[i]] <- parent_list
names(parent_order)[i] <- nodes[i]
}
structure(list(nodes = nodes,
leaves = leaves,
inner_nodes = inner_nodes,
admix_nodes = admix_nodes,
root = root,
probs = probs,
parents = parents,
parent_of = parent_of,
parent_order = parent_order,
children = children,
child_order = child_order),
class = "refined_graph")
}
arrange_graph <- function(graph) {
graph <- refined_graph(graph)
# Determining the crossing numbers of a graph is a difficult problem, and requiring
# edges to be straight lines makes it even worse. The following algorithm is by no
# means meant to be optimal. We only perform some clean-up on the cycles of the graph,
# using a heuristic of dealing with the worst cycles first.
# First determine cycles corresponding to admix nodes. We follow both branches until
# a common acestor is found. Unfortunately this is not unique if we hit more admix nodes
# on the way. Then our first tie-breaker is the age of the ancestor, if one ancestor is an
# ancestor of the other, choose the younger one, and the second tie-breaker is the branch
# count of the cycle, the number of branches deviating from it, up or down, not counting
# the admix node itself or the ancestor (doesn't matter when breaking ties of course but
# we will use the branch count later also).
cycles <- list()
if (length(graph$admix_nodes) > 0) {
for (mix in seq(1, length(graph$admix_nodes))) {
mix <- graph$admix_nodes[mix]
cycle_candidates <- list()
for (i in seq(1, length(all_paths_to_root(graph, mix)) - 1)) {
for (j in seq(i + 1, length(all_paths_to_root(graph, mix)))) {
# This is ok because mix is an admix variable and so there is at least 2 paths to root.
everything_OK <- TRUE
v1 <- all_paths_to_root(graph, mix)[[i]][-1]
v2 <- all_paths_to_root(graph, mix)[[j]][-1]
if (v1[1] == v2[1]) {
everything_OK <- FALSE
}
collision <- first_collision(v1, v2)
# Truncate for convenience.
v1 <- v1[1:match(collision, v1) - 1]
v2 <- v2[1:match(collision, v2) - 1]
branch_count <- branch_count(graph, v1, v2)
if (everything_OK == TRUE) {
temp <- list()
temp[["left"]] <- c(mix, v1, collision)
temp[["right"]] <- c(mix, v2, collision)
temp[["collision"]] <- collision
temp[["branch_count"]] <- branch_count
temp[["admix"]] <- mix
cycle_candidates[[length(cycle_candidates) + 1]] <- temp
}
}
}
# Now we choose the cycle candidate that has the least branch count among those cycles whose
# collision node is not a proper ancestor of the collision node of another candidate.
least_branch_count <- Inf
for (i in seq(1, length(cycle_candidates))) {
could_this_be_it <- TRUE
for (j in seq(1, length(cycle_candidates))) {
candidate <- cycle_candidates[[i]]$collision
comparison <- cycle_candidates[[j]]$collision
if (is_descendant_of(graph, comparison, candidate) == TRUE) {
could_this_be_it <- FALSE
}
}
if (cycle_candidates[[i]]$branch_count < least_branch_count && could_this_be_it == TRUE) {
cycles[[mix]] <- cycle_candidates[[i]]
least_branch_count <- cycle_candidates[[i]]$branch_count
}
}
}
# For now we don't care about parent ordering. Let's just let them be what they are and in the
# end change them to agree with the respective collision node child orderings.
# (As several cycles might share the same collision node but not the admix node, the collision
# node has the final say.)
# Before clearing the cycles we need to agree on the order on which to clear them, which is quite
# relevant. As clearing also might change the parent ordering of an older admixture node, we should
# at least make sure the cycles originating from younger admixture nodes are cleared first. It also
# looks like the cycles with large branch count should be cleared first. Again, this does not remove
# all the issues.
cleaning_order <- character()
# First a topological sort on the partial order of ancestry among admix nodes.
arrow_list <- list()
S <- graph$admix_nodes
for (i in seq(1, length(graph$admix_nodes))) {
for (j in seq(1, length(graph$admix_nodes))) {
if (is_descendant_of(graph, graph$admix_nodes[i], graph$admix_nodes[j]) == TRUE) {
key <- paste(graph$admix_nodes[i], graph$admix_nodes[j], sep = "_")
arrow_list[[key]] <- c(graph$admix_nodes[i], graph$admix_nodes[j])
if (is.na(match(graph$admix_nodes[j], S)) == FALSE) {
S <- S[-match(graph$admix_nodes[j], S)]
}
}
}
}
# S is non-empty.
while (length(S) > 0) {
n <- S[1]
S <- S[-1]
cleaning_order <- c(cleaning_order, n)
for (i in arrow_list) {
if (i[1] == n) {
key <- paste(i[1], i[2], sep = "_")
arrow_list[[key]] <- NULL # I don't want to talk about it.
should_add <- TRUE
for (j in arrow_list) {
if (j[2] == i[2]) {
should_add <- FALSE
}
}
if (should_add == TRUE) {
S <- c(S, i[2])
}
}
}
}
# Then the admix nodes with strictly larger brach count cut in line if not forbidden by ancestry.
if (length(cleaning_order) > 1) {
for (i in seq(2, length(cleaning_order))) {
for (j in seq(1, i - 1)) {
if (is_descendant_of(graph, cleaning_order[i - j], cleaning_order[i - j + 1]) == FALSE) {
if (cycles[[cleaning_order[i - j + 1]]]$branch_count > cycles[[cleaning_order[i - j]]]$branch_count) {
tempo <- cleaning_order[i - j]
cleaning_order[i - j] <- cleaning_order[i - j + 1]
cleaning_order[i - j + 1] <- tempo
}
}
}
}
}
cycles <- update_cycle_orientation(graph$child_order, cycles)
# Begin cleaning from the collision nodes.
# If the collision node has more than 2 children, we want to move them away from
# the cycle too. Let's say we move them half and half to the left and to the right,
# tie breaker being moving to the side where the cycle is shorter, and the second tie
# breaker being the alphabetical order of the next nodes of the cycles.
for (mix in cleaning_order) {
cycle <- cycles[[mix]]
collision <- cycle$collision
if (length(cycle$left) < length(cycle$right)) {
tie <- "left"
} else if (length(cycle$left) > length(cycle$right)) {
tie <- "right"
} else if (cycle$left[length(cycle$left) - 1] == sort(c(cycle$left[length(cycle$left) - 1],
cycle$right[length(cycle$right) - 1]))[1]) {
tie <- "left"
} else {
tie <- "right"
}
left_as_a_child <- match(cycle$left[length(cycle$left) - 1], graph$child_order[[collision]])
right_as_a_child <- match(cycle$right[length(cycle$right) - 1], graph$child_order[[collision]])
while (left_as_a_child < right_as_a_child - 1) {
if (tie == "left") {
graph$child_order[[collision]][left_as_a_child] <- graph$child_order[[collision]][left_as_a_child + 1]
graph$child_order[[collision]][left_as_a_child + 1] <- cycle$left[length(cycle$left) - 1]
left_as_a_child <- left_as_a_child + 1
}
if (tie == "right") {
graph$child_order[[collision]][right_as_a_child] <- graph$child_order[[collision]][right_as_a_child - 1]
graph$child_order[[collision]][right_as_a_child - 1] <- cycle$right[length(cycle$right) - 1]
right_as_a_child <- right_as_a_child - 1
}
if (tie == "left") {tie <- "right"} else {tie <- "left"}
}
}
cycles <- update_cycle_orientation(graph$child_order, cycles)
dealt_with_cycles <- list()
dealt_with_admixes <- character()
# For binary graphs, nothing has happened so far.
# Then accorning to the cleaning order, turn all branches away from the cycles.
# If we encounter other admix nodes or parts of other cycles that do not share the
# collision node, make sure the cycles have different orientations.
# If we encounter parts of other cycles that do share the collision node, it is
# probably better to draw the cycles intersecting (one crossing) than it is to
# draw one inside of the other (one or more crossings plus coordinate troubles), so
# some turnings need to be omitted.
for (mix in cleaning_order) {
cycle <- cycles[[mix]]
dealt_with_cycles[[length(dealt_with_cycles) + 1]] <- cycle
dealt_with_admixes[length(dealt_with_admixes) + 1] <- cycle$admix
# Clear the left side of the cycle:
# If two cycles share some parts but not the full triplet of the collision node and its
# two descendants, our aim should be that the shared part belongs to one left and one
# right side (not always possible).
for (i in seq(1, length(cycles))) {
other_cycle <- cycles[[i]]
if (recognize_forbidden_parellelness(cycle, other_cycle, "left") == TRUE &&
is.na(match(other_cycle$admix, dealt_with_admixes)) == TRUE) {
# Switch the other cycle around. The collision node requires extra care.
cycles[[i]]$left <- other_cycle$right
cycles[[i]]$right <- other_cycle$left
other_left <- match(other_cycle$left[length(other_cycle$left) - 1],
graph$child_order[[other_cycle$collision]])
other_right <- match(other_cycle$right[length(other_cycle$right) - 1],
graph$child_order[[other_cycle$collision]])
if (cycle$collision == other_cycle$collision) {
original_left <- match(cycle$left[length(cycle$left) - 1],
graph$child_order[[cycle$collision]])
original_right <- match(cycle$right[length(cycle$right) - 1],
graph$child_order[[cycle$collision]])
# It's possible that other_left = original_left, but otherwise these
# numbers are distinct.
# Move other_left to the left until it's no more right than original_left.
while (other_left > original_left) {
graph$child_order[[other_cycle$collision]][other_left] <-
graph$child_order[[other_cycle$collision]][other_left - 1]
graph$child_order[[other_cycle$collision]][other_left - 1] <-
other_cycle$left[length(other_cycle$left) - 1]
other_left <- other_left - 1
if (other_left == original_right) {
original_right <- original_right + 1
}
if (other_left == original_left) {
original_left <- original_left + 1
}
}
# Move other_right just to the left from other_left.
if (other_left > 1) {
beginning <- 1:(other_left - 1)
} else {beginning <- numeric(0)}
middle <- other_left:(other_right - 1)
if (other_right < length(graph$child_order[[other_cycle$collision]])) {
ending <- (other_right + 1):length(graph$child_order[[other_cycle$collision]])
} else {ending <- numeric(0)}
permutation <- c(beginning, other_right, middle, ending)
graph$child_order[[other_cycle$collision]] <-
graph$child_order[[other_cycle$collision]][permutation]
} else {
# In the typical case just switch the two places.
graph$child_order[[other_cycle$collision]][other_left] <-
other_cycle$right[length(other_cycle$right) - 1]
graph$child_order[[other_cycle$collision]][other_right] <-
other_cycle$left[length(other_cycle$left) - 1]
}
other_cycle <- cycles[[i]] # Update
# We mustn't perform a full clearing on the cycle we just switched around.
# Instead we clear only such child edges that are not part of any cycle
# already dealt with.
for (L in seq(1, length(other_cycle$left) - 1)) {
vanhempi <- other_cycle$left[[L]]
if (L > 1 && length(graph$child_order[[vanhempi]]) > 1) {
lapsi <- other_cycle$left[L - 1]
graph$child_order <-
clear_node(graph$child_order, vanhempi, lapsi, "left", dealt_with_cycles)
}
}
for (R in seq(1, length(other_cycle$right) - 1)) {
vanhempi <- other_cycle$right[[R]]
if (R > 1 && length(graph$child_order[[vanhempi]]) > 1) {
lapsi <- other_cycle$right[R - 1]
graph$child_order <-
clear_node(graph$child_order, vanhempi, lapsi, "right", dealt_with_cycles)
}
}
}
cycles <- update_cycle_orientation(graph$child_order, cycles)
}
for (l in seq(1, length(cycle$left) - 1)) {
parent <- cycle$left[l]
if (l > 1 && length(graph$child_order[[parent]]) > 1) {
# Turn most kids away from the cycle.
# To make complicated graphs like Kuratowski's K(3,3) look right, we let the edges
# belonging to an already dealt with cycle be.
child <- cycle$left[l - 1]
graph$child_order <- clear_node(graph$child_order, parent, child, "left",
dealt_with_cycles)
}
}
cycles <- update_cycle_orientation(graph$child_order, cycles)
# Clear the right side of the cycle:
# If two cycles share some parts but not the full triplet of the collision node and its
# two descendants, our aim should be that the shared part belongs to one left and one
# right side (not always possible).
for (i in seq(1, length(cycles))) {
other_cycle <- cycles[[i]]
if (recognize_forbidden_parellelness(cycle, other_cycle, "right") == TRUE &&
is.na(match(other_cycle$admix, dealt_with_admixes)) == TRUE) {
# Switch the other cycle around. The collision node requires extra care.
cycles[[i]]$left <- other_cycle$right
cycles[[i]]$right <- other_cycle$left
other_left <- match(other_cycle$left[length(other_cycle$left) - 1],
graph$child_order[[other_cycle$collision]])
other_right <- match(other_cycle$right[length(other_cycle$right) - 1],
graph$child_order[[other_cycle$collision]])
if (cycle$collision == other_cycle$collision) {
original_left <- match(cycle$left[length(cycle$left) - 1],
graph$child_order[[cycle$collision]])
original_right <- match(cycle$right[length(cycle$right) - 1],
graph$child_order[[cycle$collision]])
# It's possible that other_right = original_right, but otherwise these
# numbers are distinct.
# Move other_right to the right until it's no more left than original_right.
while (other_right < original_right) {
graph$child_order[[other_cycle$collision]][other_right] <-
graph$child_order[[other_cycle$collision]][other_right + 1]
graph$child_order[[other_cycle$collision]][other_right + 1] <-
other_cycle$right[length(other_cycle$right) - 1]
other_right <- other_right + 1
if (other_right == original_left) {
original_left <- original_left - 1
}
if (other_right == original_right) {
original_right <- original_right - 1
}
}
# Move other_left just to the right from other_right.
if (other_left > 1) {
beginning <- 1:(other_left - 1)
} else {beginning <- numeric(0)}
middle <- (other_left + 1):other_right
if (other_right < length(graph$child_order[[other_cycle$collision]])) {
ending <- (other_right + 1):length(graph$child_order[[other_cycle$collision]])
} else {ending <- numeric(0)}
permutation <- c(beginning, middle, other_left, ending)
graph$child_order[[other_cycle$collision]] <-
graph$child_order[[other_cycle$collision]][permutation]
} else {
# In the typical case just switch the two places.
graph$child_order[[other_cycle$collision]][other_left] <-
other_cycle$right[length(other_cycle$right) - 1]
graph$child_order[[other_cycle$collision]][other_right] <-
other_cycle$left[length(other_cycle$left) - 1]
}
other_cycle <- cycles[[i]] # Update
# We mustn't perform a full clearing on the cycle we just switched around.
# Instead we clear only such child edges that are not part of any cycle
# already dealt with.
for (L in seq(1, length(other_cycle$left) - 1)) {
vanhempi <- other_cycle$left[[L]]
if (L > 1 && length(graph$child_order[[vanhempi]]) > 1) {
lapsi <- other_cycle$left[L - 1]
graph$child_order <-
clear_node(graph$child_order, vanhempi, lapsi, "left", dealt_with_cycles)
}
}
for (R in seq(1, length(other_cycle$right) - 1)) {
vanhempi <- other_cycle$right[[R]]
if (R > 1 && length(graph$child_order[[vanhempi]]) > 1) {
lapsi <- other_cycle$right[R - 1]
graph$child_order <-
clear_node(graph$child_order, vanhempi, lapsi, "right", dealt_with_cycles)
}
}
}
cycles <- update_cycle_orientation(graph$child_order, cycles)
}
for (r in seq(1, length(cycle$right) - 1)) {
parent <- cycle$right[r]
if (r > 1 && length(graph$child_order[[parent]]) > 1) {
# Turn most kids away from the cycle.
# To make complicated graphs like Kuratowski's K(3,3) look right, we let the edges
# belonging to an already dealt with cycle be.
child <- cycle$right[r - 1]
graph$child_order <- clear_node(graph$child_order, parent, child, "right",
dealt_with_cycles)
}
}
# A little exception to cover graphs where two cycles share the collision node together
# with the two edges leading to it:
graph$child_order <- exceptional_behavior(graph$child_order, cycle, cycles)
cycles <- update_cycle_orientation(graph$child_order, cycles)
}
}
# Now we fix the parent orderings to make sure the cycles really are cycles and not "eights",
# that is, to change the parent order of the admix node to match the child order of the
# collision node.
remove_eightness <- remove_eightness(graph$parent_order, graph$child_order, cycles)
graph$parent_order <- remove_eightness$parent_order
cycles <- remove_eightness$cycles
# One last thing: we want to record a single parent in parent_order just likewe record a single
# child to child_order.
for (node in graph$nodes) {
if (is.na(graph$parent_of[node]) == FALSE) {
graph$parent_order[[node]] <- graph$parent_of[node]
}
}
return(list(parent_order = graph$parent_order, child_order = graph$child_order, cycles = cycles))
}
update_cycle_orientation <- function(child_order, cycles) {
if (length(cycles) > 0) {
for (i in seq(1, length(cycles))) {
cycle <- cycles[[i]]
order <- child_order[[cycle$collision]]
left_as_a_child <- match(cycle$left[length(cycle$left) - 1], order)
right_as_a_child <- match(cycle$right[length(cycle$right) - 1], order)
if (left_as_a_child > right_as_a_child) {
cycles[[i]]$left <- cycle$right
cycles[[i]]$right <- cycle$left
}
}
}
return(cycles)
}
exceptional_behavior <- function(child_order, cycle, cycles) {
for (other_cycle in cycles) {
if (cycle$collision == other_cycle$collision && cycle$admix != other_cycle$admix) {
# Search for the left meeting node:
left_meet <- cycle$left[match(TRUE, (cycle$left %in% other_cycle$left))]
# Search for the right meeting node:
right_meet <- cycle$right[match(TRUE, (cycle$right %in% other_cycle$right))]
# Compare child orders:
left_c <- match(cycle$left[match(left_meet, cycle$left) - 1],
child_order[[left_meet]])
right_c <- match(cycle$right[match(right_meet, cycle$right) - 1],
child_order[[right_meet]])
left_o <- match(other_cycle$left[match(left_meet, other_cycle$left) - 1],
child_order[[left_meet]])
right_o <- match(other_cycle$right[match(right_meet, other_cycle$right) - 1],
child_order[[right_meet]])
if (left_c > left_o && right_c < right_o) {
if (right_c > 1) {
beginning <- 1:(right_c - 1)
} else {beginning <- numeric(0)}
middle <- (right_c + 1):right_o
if (right_o < length(child_order[[right_meet]])) {
ending <- (right_o + 1):length(child_order[[right_meet]])
} else {ending <- numeric(0)}
permutation <- c(beginning, middle, right_c, ending)
child_order[[right_meet]] <- child_order[[right_meet]][permutation]
}
}
}
return(child_order)
}
remove_eightness <- function(parent_order, child_order, cycles) {
if (length(cycles) > 0) {
for (i in seq(1, length(cycles))) {
cycle <- cycles[[i]]
order <- child_order[[cycle$collision]]
left_as_a_child <- match(cycle$left[length(cycle$left) - 1], order)
right_as_a_child <- match(cycle$right[length(cycle$right) - 1], order)
if (left_as_a_child > right_as_a_child) {
cycles[[i]]$left <- cycle$right
cycles[[i]]$right <- cycle$left
}
parent_order[[cycle$admix]] <- c(cycles[[i]]$left[2], cycles[[i]]$right[2])
}
}
return(list(parent_order = parent_order, cycles = cycles))
}
recognize_forbidden_parellelness <- function(cycle1, cycle2, direction) {
problem <- FALSE
# Check if left sides touch:
if (direction == "left") {
for (i in seq(1, length(cycle1$left) - 1)) {
for (j in seq(1, length(cycle2$left) - 1)) {
if (cycle1$left[i] == cycle2$left[j] && i > 1 && j > 1) {
problem <- TRUE
}
if (cycle1$left[i] == cycle2$left[j] && cycle1$left[i + 1] == cycle2$left[j + 1]) {
problem <- TRUE
}
}
}
}
# Check if right sides touch:
if (direction == "right") {
for (i in seq(1, length(cycle1$right) - 1)) {
for (j in seq(1, length(cycle2$right) - 1)) {
if (cycle1$right[i] == cycle2$right[j] && i > 1 && j > 1) {
problem <- TRUE
}
if (cycle1$right[i] == cycle2$right[j] && cycle1$right[i + 1] == cycle2$right[j + 1]) {
problem <- TRUE
}
}
}
}
# Pardon if the cycles share the collision nodes and the two nodes right below it:
if (cycle1$collision == cycle2$collision &&
cycle1$left[length(cycle1$left) - 1] == cycle2$left[length(cycle2$left) - 1] &&
cycle1$right[length(cycle1$right) - 1] == cycle2$right[length(cycle2$right) - 1]) {
problem <- FALSE
}
return(problem)
}
clear_node <- function(child_order, parent, child, direction, cycles = NULL) {
total <- length(child_order[[parent]])
number <- match(child, child_order[[parent]])
skipped <- 0
if (direction == "left") {
while (number < total - skipped) {
mover <- child_order[[parent]][number + skipped + 1]
skip <- FALSE
for (cyc in cycles) {
if (parent %in% cyc$left && mover %in% cyc$left) {
skip <- TRUE
}
if (parent %in% cyc$right && mover %in% cyc$right) {
skip <- TRUE
}
}
if (skip == TRUE) {
skipped <- skipped + 1
} else {
if (number > 1) {
beginning <- 1:(number - 1)
} else {beginning <- numeric(0)}
middle <- number:(number + skipped)
if (number + skipped + 1 < total) {
ending <- (number + skipped + 2):total
} else {ending <- numeric(0)}
permutation <- c(beginning, number + skipped + 1, middle, ending)
child_order[[parent]] <- child_order[[parent]][permutation]
number <- number + 1
}
}
}
if (direction == "right") {
while (number > 1 + skipped) {
mover <- child_order[[parent]][number - skipped - 1]
skip <- FALSE
for (cyc in cycles) {
if (parent %in% cyc$left && mover %in% cyc$left) {
skip <- TRUE
}
if (parent %in% cyc$right && mover %in% cyc$right) {
skip <- TRUE
}
}
if (skip == TRUE) {
skipped <- skipped + 1
} else {
if (number - skipped - 1 > 1) {
beginning <- 1:(number - skipped - 2)
} else {beginning <- numeric(0)}
middle <- (number - skipped):number
if (number < total) {
ending <- (number + 1):total
} else {ending <- numeric(0)}
permutation <- c(beginning, middle, number - skipped - 1, ending)
child_order[[parent]] <- child_order[[parent]][permutation]
number <- number - 1
}
}
}
return(child_order)
}
leaf_order <- function(graph, parent_order, child_order) {
# We actually still need the refined graph object, so build it and fix the orderings it has by default.
graph <- refined_graph(graph)
graph$parent_order <- parent_order
graph$child_order <- child_order
# Give some arrangement for leaves. Doesn't really follow from parent and child orders, but almost.
# Assign the y-coordinates according to my arbitrary principles.
heights <- rep(0, length(graph$nodes))
names(heights) <- graph$nodes
for (node in graph$nodes) {
paths <- all_paths_to_root(graph, node)
longest <- 0
for (path in paths) {
if (length(path) > longest) {
longest <- length(path)
}
}
heights[node] <- longest - 1
}
# Calculate some tentative values for the x-coordinates, just to get a good guess for leaf ordering.
x_order <- graph$nodes[order(heights)]
silly_x <- rep(0, length(graph$nodes))
names(silly_x) <- graph$nodes
for (i in seq(2, length(x_order))) {
vertex <- x_order[i]
if (length(graph$parent_order[[vertex]]) < 2) {
parent <- graph$parent_order[[vertex]][1]
# Only child of a single parent:
if (length(graph$child_order[[parent]]) == 1) {
silly_x[vertex] <- silly_x[parent]
}
# One of many children of a single parent:
if (length(graph$child_order[[parent]]) > 1) {
index <- match(vertex, graph$child_order[[parent]])
step <- 2^(-heights[vertex] + 2) / (length(graph$child_order[[parent]]) - 1)
leftmost <- silly_x[parent] - 2^(-heights[vertex] + 1)
silly_x[vertex] <- leftmost + (index - 1) * step
}
} else {
# Child of two parents:
left_parent <- graph$parent_order[[vertex]][1]
right_parent <- graph$parent_order[[vertex]][2]
if (silly_x[left_parent] < silly_x[right_parent]) {
# Consistent parents:
# The starting point is the average.
silly_x[vertex] <- 0.5*(silly_x[left_parent] + silly_x[right_parent])
# Then we adjust according to ordering of step simblings.
# With strict boundaries enforced to keep the cousings and such away.
if (length(graph$child_order[[left_parent]]) > 1) {
index <- match(vertex, graph$child_order[[left_parent]])
step <- 2^(-heights[vertex] + 1) / (length(graph$child_order[[left_parent]]) - 1)
leftmost <- silly_x[vertex] - 2^(-heights[vertex])
silly_x[vertex] <- leftmost + (index - 1) * step
}
if (length(graph$child_order[[right_parent]]) > 1) {
index <- match(vertex, graph$child_order[[right_parent]])
step <- 2^(-heights[vertex] + 1) / (length(graph$child_order[[right_parent]]) - 1)
leftmost <- silly_x[vertex] - 2^(-heights[vertex])
silly_x[vertex] <- leftmost + (index - 1) * step
}
} else {
# Inconsistent parents:
decision <- 0
if (length(graph$child_order[[left_parent]]) > 1) {
index <- match(vertex, graph$child_order[[left_parent]])
change <- 2 * (index - 1) / (length(graph$child_order[[left_parent]]) - 1) - 2
decision <- decision + change
}
if (length(graph$child_order[[right_parent]]) > 1) {
index <- match(vertex, graph$child_order[[right_parent]])
change <- 2 * (index - 1) / (length(graph$child_order[[right_parent]]) - 1) - 2
decision <- decision + change
}
if (decision < 0) {
# Position like a child of the right parent only.
if (length(graph$child_order[[right_parent]]) == 1) {
silly_x[vertex] <- silly_x[right_parent]
}
if (length(graph$child_order[[right_parent]]) > 1) {
index <- match(vertex, graph$child_order[[right_parent]])
step <- 2^(-heights[vertex] + 2) / (length(graph$child_order[[right_parent]]) - 1)
leftmost <- silly_x[right_parent] - 2^(-heights[vertex] + 1)
silly_x[vertex] <- leftmost + (index - 1) * step
}
}
if (decision > 0) {
# Position like a child of the left parent only.
if (length(graph$child_order[[left_parent]]) == 1) {
silly_x[vertex] <- silly_x[left_parent]
}
if (length(graph$child_order[[left_parent]]) > 1) {
index <- match(vertex, graph$child_order[[left_parent]])
step <- 2^(-heights[vertex] + 2) / (length(graph$child_order[[left_parent]]) - 1)
leftmost <- silly_x[right_parent] - 2^(-heights[vertex] + 1)
silly_x[vertex] <- leftmost + (index - 1) * step
}
}
if (decision == 0) {
silly_x[vertex] <- 0.5*(silly_x[left_parent] + silly_x[right_parent])
}
}
}
}
leaf_order <- graph$leaves[order(silly_x)]
leaf_order <- leaf_order[!is.na(leaf_order)]
return(leaf_order)
}
all_paths_to_root <- function(graph, node) {
path_list <- list()
if (node == graph$root) {
path_list[[1]] <- c(node)
} else if (node %in% graph$admix_nodes) {
previous <- c(all_paths_to_root(graph, graph$parent_order[[node]][1]),
all_paths_to_root(graph, graph$parent_order[[node]][2]))
for (j in seq(1, length(previous))) {
path_list[[j]] <- c(node, previous[[j]])
}
} else {
previous <- all_paths_to_root(graph, graph$parent_of[node])
for (j in seq(1, length(previous))) {
path_list[[j]] <- c(node, previous[[j]])
}
}
path_list
}
all_paths_to_leaves <- function(graph, node) {
path_list <- list()
if (node %in% graph$leaves) {
path_list[[1]] <- c(node)
} else {
previous <- c(all_paths_to_leaves(graph, graph$child_order[[node]][1]))
if (length(graph$child_order[[node]]) > 1) {
for (k in seq(2, length(graph$child_order[[node]]))) {
previous <- c(previous, all_paths_to_leaves(graph, graph$child_order[[node]][k]))
}
}
for (j in seq(1, length(previous))) {
path_list[[j]] <- c(node, previous[[j]])
}
}
path_list
}
#' Is descendant of.
#'
#' Tells whether a given node is descendant of another given node in a graph.
#'
#' @param graph A graph.
#' @param offspring Potential offspring.
#' @param ancestor Potential ancestor.
#'
#' @return A truth value.
#'
#' @export
is_descendant_of <- function(graph, offspring, ancestor) {
if (methods::is(graph, "agraph") == TRUE) {
graph <- refined_graph(graph)
}
answer <- FALSE
all_ancestors <- all_paths_to_root(graph, offspring)
for (i in seq(1, length(all_ancestors))) {
for (j in seq(1, length(all_ancestors[[i]]))) {
if (all_ancestors[[i]][j] == ancestor) {
answer <- TRUE
}
}
}
# However, offspring does not count as a descendant of itself.
if (offspring == ancestor) {
answer <- FALSE
}
answer
}
first_collision <- function(v1, v2) {
# Assuming no-one is one's own ancestor.
answer <- NA
for (i in seq(1, length(v1))) {
for (j in seq(1, length(v2))) {
if (v1[i] == v2[j] && is.na(answer) == TRUE) {
answer <- v1[i]
}
}
}
answer
}
branch_count <- function(graph, v1, v2) {
# Assuming v1 and v2 don't share endpoints.
count <- 0
if (length(v1) > 0) {
for (i in seq(1, length(v1))) {
count <- count + length(graph$child_order[[v1[i]]]) - 1
# Not sure if we should but also count the admix branches.
if (v1[i] %in% graph$admix_nodes) {
count <- count + 1
}
}
}
if (length(v2) > 0) {
for (j in seq(1, length(v2))) {
count <- count + length(graph$child_order[[v2[j]]]) - 1
# Not sure if we should but also count the admix branches.
if (v2[j] %in% graph$admix_nodes) {
count <- count + 1
}
}
}
count
}
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.