#' @title Show an interactive visual network representation
#' of your drake project.
#' `r lifecycle::badge("stable")`
#' @description It is good practice to visualize the dependency graph
#' before running the targets.
#' @details For enhanced interactivity in the graph, see the `mandrake`
#' package.
#' @export
#' @seealso [render_drake_graph()], [sankey_drake_graph()],
#' [drake_ggraph()], [text_drake_graph()]
#' @return A `visNetwork` graph.
#' @inheritParams drake_graph_info
#' @inheritParams render_drake_graph
#' @param ... Arguments to [make()], such as `plan` and `targets`.
#' @examples
#' \dontrun{
#' isolate_example("Quarantine side effects.", {
#' if (suppressWarnings(require("knitr"))) {
#' load_mtcars_example() # Get the code with drake_example("mtcars").
#' # Plot the network graph representation of the workflow.
#' if (requireNamespace("visNetwork", quietly = TRUE)) {
#' vis_drake_graph(my_plan)
#' make(my_plan) # Run the project, build the targets.
#' vis_drake_graph(my_plan) # The red nodes from before are now green.
#' # Plot a subgraph of the workflow.
#' vis_drake_graph(
#' my_plan,
#' from = c("small", "reg2")
#' )
#' }
#' }
#' })
#' }
vis_drake_graph <- function(
...,
file = character(0),
selfcontained = FALSE,
build_times = "build",
digits = 3,
targets_only = FALSE,
font_size = 20,
layout = NULL,
main = NULL,
direction = NULL,
hover = FALSE,
navigationButtons = TRUE, # nolint
from = NULL,
mode = c("out", "in", "all"),
order = NULL,
subset = NULL,
ncol_legend = 1,
full_legend = FALSE,
make_imports = TRUE,
from_scratch = FALSE,
group = NULL,
clusters = NULL,
show_output_files = TRUE,
collapse = TRUE,
on_select_col = NULL,
on_select = NULL,
level_separation = NULL,
config = NULL
) {
}
#' @title Internal function with a drake_config() argument
#' @export
#' @keywords internal
#' @description Not a user-side function.
#' @param config A [drake_config()] object.
vis_drake_graph_impl <- function(
config,
file = character(0),
selfcontained = FALSE,
build_times = "build",
digits = 3,
targets_only = FALSE,
font_size = 20,
layout = NULL,
main = NULL,
direction = NULL,
hover = FALSE,
navigationButtons = TRUE, # nolint
from = NULL, mode = c("out", "in", "all"),
order = NULL,
subset = NULL,
ncol_legend = 1,
full_legend = FALSE,
make_imports = TRUE,
from_scratch = FALSE,
group = NULL,
clusters = NULL,
show_output_files = TRUE,
collapse = TRUE,
on_select_col = NULL,
on_select = NULL,
level_separation = NULL
) {
assert_pkg("visNetwork")
graph_info <- drake_graph_info_impl(
config = config,
from = from,
mode = mode,
order = order,
subset = subset,
build_times = build_times,
digits = digits,
targets_only = targets_only,
font_size = font_size,
make_imports = make_imports,
from_scratch = from_scratch,
full_legend = full_legend,
group = group,
clusters = clusters,
show_output_files = show_output_files,
hover = hover,
on_select_col = on_select_col
)
if (is.null(main)) {
main <- graph_info$default_title
}
render_drake_graph(
graph_info,
file = file,
selfcontained = selfcontained,
layout = layout,
direction = direction,
navigationButtons = navigationButtons, # nolint
hover = hover,
main = main,
ncol_legend = ncol_legend,
full_legend = full_legend,
collapse = collapse,
on_select = on_select,
level_separation = level_separation
)
}
body(vis_drake_graph) <- config_util_body(vis_drake_graph_impl)
#' @title Render a visualization using the data frames
#' generated by [drake_graph_info()].
#' `r lifecycle::badge("stable")`
#' @description This function is called inside
#' [vis_drake_graph()], which typical users
#' call more often.
#' @details For enhanced interactivity in the graph, see the `mandrake`
#' package.
#' @export
#' @seealso [vis_drake_graph()], [sankey_drake_graph()],
#' [drake_ggraph()]
#' @return A `visNetwork` graph.
#'
#' @param graph_info List of data frames generated by
#' [drake_graph_info()].
#' There should be 3 data frames: `nodes`, `edges`,
#' and `legend_nodes`.
#'
#' @param file Name of a file to save the graph.
#' If `NULL` or `character(0)`, no file is saved and
#' the graph is rendered and displayed within R.
#' If the file ends in a `.png`, `.jpg`, `.jpeg`, or `.pdf` extension,
#' then a static image will be saved. In this case,
#' the webshot package and PhantomJS are required:
#' `install.packages("webshot"); webshot::install_phantomjs()`.
#' If the file does not end in a `.png`, `.jpg`, `.jpeg`, or `.pdf`
#' extension, an HTML file will be saved, and you can open the
#' interactive graph using a web browser.
#'
#' @param layout Deprecated.
#'
#' @param selfcontained Logical, whether
#' to save the `file` as a self-contained
#' HTML file (with external resources base64 encoded) or a file with
#' external resources placed in an adjacent directory. If `TRUE`,
#' pandoc is required. The `selfcontained` argument only applies
#' to HTML files. In other words, if `file` is a
#' PNG, PDF, or JPEG file, for instance,
#' the point is moot.
#'
#' @param direction Deprecated.
#'
#' @param navigationButtons Logical, whether to add navigation buttons with
#' `visNetwork::visInteraction(navigationButtons = TRUE)`
#'
#' @param hover Logical, whether to show the command that generated the target
#' when you hover over a node with the mouse. For imports, the label does not
#' change with hovering.
#'
#' @param main Character string, title of the graph.
#'
#' @param ncol_legend Number of columns in the legend nodes.
#' To remove the legend entirely, set `ncol_legend` to `NULL` or `0`.
#'
#' @param collapse Logical, whether to allow nodes to collapse
#' if you double click on them.
#' Analogous to `visNetwork::visOptions(collapse = TRUE)`.
#'
#' @param on_select defines node selection event handling.
#' Either a string of valid JavaScript that may be passed to
#' `visNetwork::visEvents()`, or one of the following:
#' `TRUE`, `NULL`/`FALSE`. If `TRUE` , enables the default behavior of
#' opening the link specified by the `on_select_col` given to
#' `drake_graph_info()`. `NULL`/`FALSE` disables the behavior.
#'
#' @param level_separation Numeric, `levelSeparation` argument to
#' `visNetwork::visHierarchicalLayout()`. Controls the distance
#' between hierarchical levels. Consider setting if the
#' aspect ratio of the graph is far from 1.
#' Defaults to 150 through `visNetwork`.
#'
#' @param ... Arguments passed to `visNetwork()`.
#'
#' @examples
#' \dontrun{
#' isolate_example("Quarantine side effects.", {
#' if (suppressWarnings(require("knitr"))) {
#' load_mtcars_example() # Get the code with drake_example("mtcars").
#' if (requireNamespace("visNetwork", quietly = TRUE)) {
#' # Instead of jumping right to vis_drake_graph(), get the data frames
#' # of nodes, edges, and legend nodes.
#' vis_drake_graph(my_plan) # Jump straight to the interactive graph.
#' # Get the node and edge info that vis_drake_graph() just plotted:
#' graph <- drake_graph_info(my_plan)
#' # You can pass the data frames right to render_drake_graph()
#' # (as in vis_drake_graph()) or you can create
#' # your own custom visNewtork graph.
#' render_drake_graph(graph)
#' }
#' }
#' })
#' }
render_drake_graph <- function(
graph_info, file = character(0),
layout = NULL,
direction = NULL,
hover = TRUE,
main = graph_info$default_title,
selfcontained = FALSE,
navigationButtons = TRUE, # nolint
ncol_legend = 1,
collapse = TRUE,
on_select = NULL,
level_separation = NULL,
...
) {
assert_pkg("visNetwork")
if (!hover) {
graph_info$nodes$title <- NULL
}
deprecate_arg(direction, "direction") # 2019-04-16 # nolint
deprecate_arg(layout, "layout") # 2019-04-16 # nolint
out <- initialize_vis_network(
graph_info = graph_info,
collapse = collapse,
ncol_legend = ncol_legend,
main = main,
navigationButtons = navigationButtons,
...
)
out <- visNetwork::visHierarchicalLayout(
graph = out,
direction = "LR",
levelSeparation = level_separation
)
out <- vis_add_on_select(graph = out, on_select = on_select)
out$x$nodes$x <- graph_info$nodes$x
out$x$nodes$y <- graph_info$nodes$y
vis_render_webshot(graph = out, file = file, selfcontained = selfcontained)
}
initialize_vis_network <- function(
graph_info,
collapse,
ncol_legend,
main,
navigationButtons,
...
) {
out <- visNetwork::visNetwork(
nodes = graph_info$nodes,
edges = graph_info$edges,
main = main,
...
)
out <- visNetwork::visNodes(out, physics = FALSE)
out <- visNetwork::visEdges(
out,
smooth = list(type = "cubicBezier", forceDirection = "horizontal")
)
if (collapse) {
out <- visNetwork::visOptions(out, collapse = TRUE)
}
if (length(ncol_legend) && ncol_legend > 0) {
out <- visNetwork::visLegend(
graph = out,
useGroups = FALSE,
addNodes = graph_info$legend_nodes,
ncol = ncol_legend
)
}
if (navigationButtons) { # nolint
out <- visNetwork::visInteraction(out, navigationButtons = TRUE) # nolint
}
out
}
vis_add_on_select <- function(graph, on_select) {
out <- graph
# Add on_select action
if (is.null(on_select)) return(out)
if (is.logical(on_select)) {
if (!on_select) return(out)
on_select <- on_select_default()
}
# Ideally showing a warning here would be nice
# If on_select is enabled, there should have been a corresponding
# on_select_col given
if (is.null(graph$x$nodes$on_select_col)) return(out)
out <- visNetwork::visEvents(out, selectNode = on_select)
out
}
vis_render_webshot <- function(graph, file, selfcontained) {
if (!length(file)) {
return(graph)
}
file <- path.expand(file)
if (is_image_filename(file)) {
assert_pkg("webshot")
dir <- tempfile()
dir.create(dir)
url <- file.path(dir, "tmp.html")
visNetwork::visSave(graph = graph, file = url, selfcontained = FALSE)
webshot::webshot(url = url, file = file)
} else {
visNetwork::visSave(
graph = graph,
file = file,
selfcontained = selfcontained
)
}
return(invisible())
}
is_image_filename <- function(x) {
tolower(file_extn(x)) %in% c("jpg", "jpeg", "pdf", "png")
}
file_extn <- function(x) {
x <- basename(x)
x <- strsplit(x, split = ".", fixed = TRUE)
x <- unlist(x)
x <- rev(x)
x[1]
}
on_select_default <- function() {
js <- "
function(props) {
window.open(
this.body.data.nodes.get(props.nodes[0]).on_select_col,
'_blank');
}"
}
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.