Nothing
#' Layout nodes using a text-based schematic
#'
#' @description
#'
#' Layout one or several groups of nodes using a text-based schematic. The
#' option is available to apply sorting to each of the groups.
#'
#' @inheritParams render_graph
#' @param layout A layout character string that provides a schematic for the
#' layout. This consists of a rectangular collection of `-` characters (for no
#' node placement), and numbers from `1` to `9` (representing different
#' groupings of nodes, further described in the `nodes` argument).
#' @param nodes A named vector of the form: `c("1" = "[node_attr]:[value]",
#' ...)`. The LHS corresponds to the numbers used in the `layout` schematic.
#' The RHS provides a shorthand for the node attribute and a value for
#' grouping together nodes (separated by a colon). For instance, with
#' `"type:a"` in the RHS (and `"1"` in the LHS) we would target all nodes with
#' a `type` attribute equal to `a` for positioning in the graph as described
#' by the `1`s in the `layout`.
#' @param sort An optional sorting method to apply to the collection of nodes
#' before assigning positional information. Like `nodes`, this is a named
#' vector of the form: `c("1" = "[node_attr]:asc|desc", ...)`. The `node_attr`
#' in this case should be different than that used in `nodes`. Ideally, this
#' node attribute should have unique values. Choose either `asc` or `desc`
#' right of the colon for ascending or descending sorts.
#' @param width The width of the `layout` diagram.
#' @param height The height of the `layout` diagram.
#' @param ll A vector describing the the lower-left coordinates of the `layout`
#'
#' @return A graph object of class `dgr_graph`.
#'
#' @examples
#' # Create a graph with unique labels and
#' # several node `type` groupings
#' graph <-
#' create_graph() %>%
#' add_node(type = "a", label = "a") %>%
#' add_node(type = "a", label = "b") %>%
#' add_node(type = "b", label = "c") %>%
#' add_node(type = "b", label = "d") %>%
#' add_node(type = "b", label = "e") %>%
#' add_node(type = "c", label = "f") %>%
#' add_node(type = "c", label = "g")
#'
#' # Define a 'layout' for groups of nodes
#' # using a text string (dashes are empty
#' # grid cells, numbers--representing
#' # ad-hoc groupings--correspond to
#' # individual nodes); here, define a layout
#' # with 3 groups of nodes
#' layout <-
#' "
#' 1--------
#' 1--------
#' ---222---
#' --------3
#' --------3
#' "
#'
#' # Use the `layout` along with what nodes
#' # the numbers correspond to in the graph
#' # with the `nodes` named vectors; the
#' # optional `sort` vector describes how
#' # we should sort the collection of node
#' # before adding position information
#' graph <-
#' graph %>%
#' layout_nodes_w_string(
#' layout = layout,
#' nodes = c("1" = "type:a",
#' "2" = "type:b",
#' "3" = "type:c"),
#' sort = c("1" = "label:asc",
#' "2" = "label:desc",
#' "3" = "label:desc"))
#'
#' # Show the graph's node data frame
#' # to confirm that `x` and `y` values
#' # were added to each of the nodes
#' graph %>% get_node_df()
#' @family node creation and removal
#' @export
layout_nodes_w_string <- function(
graph,
layout,
nodes,
sort = NULL,
width = 8,
height = 8,
ll = c(0, 0)
) {
# Get the time of function start
time_function_start <- Sys.time()
# Validation: Graph object is valid
check_graph_valid(graph)
# Get the graph's internal node data frame
# (ndf) into a separate object
ndf <- graph$nodes_df
# Get the total number of groups described
# in the layout
node_group_count <- length(nodes)
# Parse the `layout` object to get a vector
# of rows
layout <-
gsub(" ", "", layout) %>%
stringr::str_split("\n") %>%
unlist()
layout <-
layout[which(nzchar(layout))]
# Determine the row length from the layout text
layout_row_length <- vector(mode = "numeric")
for (i in seq_along(layout)) {
layout_row_length <-
c(layout_row_length, nchar(layout[i]))
}
# Stop function if not all rows are of equal length
if (mean(layout_row_length) != layout_row_length[1]) {
cli::cli_abort(
"Each row must have the same length ({layout_row_length[1]}).")
}
layout_column_number <- layout_row_length <- layout_row_length[1]
layout_row_number <- layout_column_length <- length(layout)
# Define the exact `x` and `y` positions in which
# the nodes could be placed
x_pts <- seq(0, width, width / (layout_column_number - 1)) + ll[1]
y_pts <- rev(seq(0, height, height / (layout_row_number - 1))) + ll[2]
# Create a tibble called `ndf_parts`
ndf_parts <- dplyr::tibble()
for (k in seq_len(node_group_count)) {
# Create empty table with position and node ID
position_table <- dplyr::tibble(x = numeric(), y = numeric())
node_group <- names(nodes)[k]
node_select <- nodes[[k]]
node_attr <- unlist(stringr::str_split(node_select, ":"))[1]
node_attr_val <- unlist(stringr::str_split(node_select, ":"))[2]
sort_attr <- unlist(stringr::str_split(sort[k], ":"))[1]
sort_dir <- unlist(stringr::str_split(sort[k], ":"))[2]
for (i in seq_len(layout_row_number)) {
for (j in seq_len(layout_column_number)) {
if (unlist(stringr::str_split(layout[i], ""))[j] == k) {
item_table <-
dplyr::tibble(
x = x_pts[j],
y = y_pts[i])
position_table <-
position_table %>%
dplyr::bind_rows(item_table)
}
}
}
# Filter the graph `ndf`
ndf_part <-
ndf %>%
dplyr::filter(!!rlang::parse_expr(paste0(node_attr, " == '", node_attr_val, "'")))
# Optionally apply sorting
if (!is.null(sort)) {
if (sort_dir == "desc") {
ndf_part <-
ndf_part %>%
dplyr::arrange(dplyr::desc(!!sym(sort_attr)))
} else {
ndf_part <-
ndf_part %>%
dplyr::arrange(!!sym(sort_attr))
}
}
# Trim rows from the position table if its
# row count is greater than that of `ndf_part`
if (nrow(position_table) > nrow(ndf_part)) {
position_table <- position_table[seq_len(nrow(ndf_part)), ]
}
# Bind `position_table` to the split `ndf_part`
ndf_part <-
dplyr::bind_cols(position_table, ndf_part)
# Bind `ndf_part` to `ndf_parts`
ndf_parts <-
dplyr::bind_rows(ndf_parts, ndf_part)
}
# Join the `ndf_parts` to the main `ndf`
ndf <-
ndf %>%
dplyr::left_join(
ndf_parts,
by = c("id", "type", "label"))
# Replace the graph's ndf with the revised version
graph$nodes_df <- ndf
# Get the name of the function
fcn_name <- get_calling_fcn()
# Update the `graph_log` df with an action
graph$graph_log <-
add_action_to_log(
graph_log = graph$graph_log,
version_id = nrow(graph$graph_log) + 1L,
function_used = fcn_name,
time_modified = time_function_start,
duration = graph_function_duration(time_function_start),
nodes = nrow(graph$nodes_df),
edges = nrow(graph$edges_df))
# Write graph backup if the option is set
if (graph$graph_info$write_backups) {
save_graph_as_rds(graph = graph)
}
graph
}
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.