#' Extract a Branch from a Panicle Graph
#'
#' This function returns a `igraph` with the branch
#' developing from a selected node.
#'
#' @param panicle A graph of class `igraph`
#' @param vert A number, the vertex of `panicle`` from which to start
pull_branch <- function(panicle,
vert)
{
# The secondary branch is composed by all vertexes
# downstream the branch node
sub_verts <-
panicle %>%
igraph::subcomponent(vert,
mode = "out")
sub_graph <-
panicle %>%
igraph::induced_subgraph(sub_verts)
}
#' Make a Tibble with the Vertex ID of the Longest Path
#'
#' This function takes a branch `igraph` as object and
#' a starting `vertex` as object, finds the longest path
#' that starts from that vertex.
#'
#' The longest path is assumend to be the main branch axis
#'
#' This function returns a character vector that
#' stores `type` of the vertexes (Secondary, Spikelet, etc.)
#' along that axis ordered from the starting vertex
#' (supplied in `vert_rank`) going outward.
#'
#' @param branch an `igraph` object storing data for a
#' panicle branch.
#' @param vert_rank a number indicating the `rank` attribute
#' that identifies the staring vertex.
make_idline <- function(branch,
vert_rank)
{
# since this is a new graph, I have to identify
# the generating vertex by the attribute rank.
# Because that attribute is carried in from the
# original panicle graph
start_node <-
branch %>%
igraph::vertex_attr() %>%
{which(.$rank == vert_rank)}
# I assume and that the end of the branch axis
# correspond to the furthest vertex, and use this
# trick to identify it
top_node <-
branch %>%
igraph::distances(v = start_node,
mode = "out") %>%
which.max()
# and use that node to estimate the path that
# contains all the nodes on the main axis
main_path <-
branch %>%
igraph::shortest_paths(from = start_node,
to = top_node,
mode = "out")
# I am interested in the types of the nodes in the
# branch...
# and in their original rank
node_types <-
branch %>%
igraph::vertex_attr() %>%
{dplyr::tibble(type = .$type, original_rank = .$rank)}
# ...ranked along the nodes in the main axis of the
# branch
# this returns a character vector of ordered nodes
out <-
main_path$vpath %>%
purrr::flatten_int() %>%
node_types[., ] %>%
dplyr::mutate(branchwise_rank = main_path$vpath %>%
purrr::flatten_int())
# get the nodes that are downstream
# secondary branches and not in the
# main path
get_down_secondary <- function(vert_id) {
# the ids of vertexes direcltly downstream
down_vert_ids <-
igraph::neighbors(graph = branch,
v = vert_id,
mode = "out")
# the number of vertexes downstream
verts_down <- 0
for(i in down_vert_ids) {
if(! i %in% vpath){
subcomps <-
branch %>%
igraph::subcomponent(i,
mode = "out") %>%
length()
verts_down <- verts_down + subcomps
}
}
return(verts_down)
}
out <-
out %>%
dplyr::mutate(nodes_downstream = list(
igraph::neighbors(
graph = branch,
v = .data$branchwise_rank,
mode = "out")
))
# scaffold?
vpath <- main_path$vpath %>%
purrr::flatten_int()
# Save nodes downstream in the output of out
out <-
out %>%
dplyr::mutate(nodes_downstream = .data$branchwise_rank %>%
purrr::map_dbl(get_down_secondary))
return(out)
}
#' Identify Start and End Generating node
#'
#' This function select the start generating node
#' as the one with the highest x coordinate and the
#' end as the node with the lowest
#'
#' This approach is prone to error is the panicle
#' is not orientated from right to left in the
#' P-TRAP picture.
#'
#' This function is used and improved by `get_base()`,
#' which select the base node as the generating
#' node with no parents
#'
#' @param panicle a panicle graph.
get_generating <- function(panicle) {
panicle %>%
igraph::as_long_data_frame() %>%
dplyr::filter(.data$from_type == "Generating") %>%
dplyr::select(.data$from_x, .data$from_rank) %>%
dplyr::distinct() %>%
dplyr::arrange(
dplyr::desc(.data$from_x)
) %>%
dplyr::pull(.data$from_rank)
}
#' Get the node at the base of the panicle
#'
#' This function returns the rank of the node (vertex) at the
#' base of the panicle. We assume that the base node
#' is the node with type: `Generating` with no
#' children
#'
#' @param panicle a panicle graph
get_base <- function(panicle) {
# the base node is one of the generating
gens <- get_generating(panicle)
# that have no parent nodes in its neighbors
parents <-
gens %>%
purrr::map(
~igraph::neighbors(panicle,
v = .,
mode = "in")
)
gens[which(
parents %>% purrr::map_int(length) == 0
)]
}
#' Turn a Panicle Graph in a Tibble useful for a dotplot
#'
#' @param panicle a panicle graph.
#' @param silently logical, if `FALSE` prints intermediate data
#' while producing the panicle tibble. Defaults to `TRUE`.
#'
#' @export
panicle_tibble <- function(panicle,
silently = TRUE)
{
# get the generating node
gens <- get_generating(panicle)
# the base node is the one with no parents
start <- get_base(panicle)
# the other is the top node
to <- gens[which(gens != start)]
# we assume that the main axis of the rachis
# is the shortest (and only!) path between
# the two generating nodes
main_path <-
panicle %>%
igraph::shortest_paths(from = start,
to = to,
mode = "out") %>%
.$vpath %>%
purrr::flatten_int()
# I need a way to select neighbours
# with any other attribute than primary
not_primary <- function(v) {
#
# get the neighborough nodes
nb <- igraph::neighbors(graph = panicle,
v = v,
mode = "out")
# and its attributes
nb_attr <- igraph::vertex_attr(graph = panicle,
index = nb)
keep <- nb$type != "Primary" & nb$type != "Generating"
nb[keep] %>% as.numeric()
}
# All nodes where a primary branch starts
branch_starts <-
main_path %>%
purrr::map(not_primary)
# sometimes two branches start from the same node
# in case, First, print a note
if(!silently) {
double_branches <-
branch_starts %>%
purrr::map_dbl(length) %>%
{which(. > 1)}
if(length(double_branches > 0))
paste("Multiple branches detected on node:", double_branches,
"\n This node was split in multiple nodes") %>%
message()
}
# Then solve it by arbitrarily splitting that node
# in two
branch_starts <-
branch_starts %>%
purrr::flatten_dbl()
# pull a subgraph from every primary node
tb <- tibble::tibble(vert_rank = branch_starts,
branch = branch_starts %>%
purrr::map(
~pull_branch(panicle = panicle,
vert = .))
)
if(!silently) {print(tb)}
# new version
tb_list <-
tb %>%
purrr::pmap(make_idline) %>%
purrr::map(
~dplyr::mutate(., node_rank = 1:dplyr::n())
)
tb_list <-
1:length(tb_list) %>%
purrr::map(~dplyr::mutate(tb_list[[.]], primary_rank = .)) %>%
purrr::reduce(dplyr::bind_rows)
# Two columns: original_rank and branchwise_rank are confusing
# and are useful only for internal calculations:
# remove them from the final output
tb_list <-
tb_list %>%
dplyr::select(-.data$original_rank, -.data$branchwise_rank) %>%
# and rename one in a clearer way
dplyr::rename(secondary_rank = "node_rank")
}
#' Plot the Output of `panicle_tibble()`
#'
#' Returns a `ggplot2` object.
#'
#' This is a small utility plot function. You can use it
#' on the output of `panicle_tibble()` to represent it as a
#' tileplot.
#'
#' Although we provide this function as utility, we suggest that you design
#' your own plotting function, because only in this way you will reach the
#' versatlity required for exploratory data analysis. We provide ideas
#' on how to achieve this in the vignettes.
#'
#' This tileplot is inspired by the plots in
#' https://www.nature.com/articles/s41598-018-30395-9
#'
#' @param pan_tbl A tibble, the output of `panicle_tibble()`
#' @param draw.plot Logical. Should the function draw a plot on the graphic device?
#' Defaults to `FALSE`.
#'
#' @export
panicle_tileplot <- function(pan_tbl, draw.plot = FALSE)
{
p <-
pan_tbl %>%
ggplot2::ggplot(
ggplot2::aes_string(
x = "secondary_rank",
y = "primary_rank",
fill = "type")) +
ggplot2::geom_tile(
colour = "grey80",
size = 2) +
ggplot2::geom_text(data = . %>%
dplyr::filter(.data$type == "Seconday"),
ggplot2::aes_string(label = "nodes_downstream"),
colour = "grey20")
if(draw.plot) print(p)
return(p)
}
#' Plot the Output of `panicle_tibble()`
#'
#' This function is the same as `panicle_tileplot()`, with extra customization
#' of the output plot.
#'
#' Returns a `ggplot2` object.
#'
#' This is a small utility plot function. You can use it
#' on the output of `panicle_tibble()` to represent it as a
#' tileplot.
#'
#' Although we provide this function as utility, we suggest that you design
#' your own plotting function, because only in this way you will reach the
#' versatlity required for exploratory data analysis. We provide ideas
#' on how to achieve this in the vignettes.
#'
#' This tileplot is inspired by the plots in
#' https://www.nature.com/articles/s41598-018-30395-9
#'
#' @param pan_tbl A tibble, the output of `panicle_tibble()`
#' @param draw.plot Logical. Should the function draw a plot on the graphic device?
#' Defaults to `FALSE`.
#'
#' @export
panicle_tileplot2 <- function(pan_tbl, draw.plot = FALSE)
{
p <-
pan_tbl %>%
ggplot2::ggplot(
ggplot2::aes(
x = secondary_rank %>% as.character() %>% forcats::as_factor(),
y = primary_rank %>% as.character() %>% forcats::as_factor(),
fill = nodes_downstream)) + # nodes on 2ndary branches to fill colours +
ggplot2::geom_tile(colour = "grey80",
size = 1.5) +
ggplot2::geom_text(data = . %>%
dplyr::filter(type == "Seconday"), # Show the number for secondary nodes
ggplot2::aes(label = nodes_downstream),
colour = "grey30",
fontface = "bold",
size = 5) +
# fixed xy ratio, each tile is a square
ggplot2::coord_fixed() +
# set scale of colours and
# proper labels for the colour scales
ggplot2::scale_fill_viridis_c(
breaks = function(limits) c(0, 2:max(limits)),
labels = function(breaks) dplyr::case_when(breaks == 0 ~ "1\n[Spikelet]",
TRUE ~ as.character(breaks)),
guide = ggplot2::guide_legend(nrow = 1,
# keyheight = unit(7, units = "mm"),
# keywidth = unit(7, units = "mm"),
override.aes = list(size = 0))) +
# fill guide on top
ggplot2::theme(legend.position = "top") +
# Clear axis names
ggplot2::labs(x = "Nodes rank along primary branches",
y = "Rank along the rachis",
fill = "Nodes on\n secondary branch")
if(draw.plot) print(p)
return(p)
}
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.