R/plotWF.R

Defines functions .change_branch .make_plot .find_long_branch .plotWF

## Internal funcs for plotting Workflow


# Graphviz plot workflow
# df_wf: data frame, the standard df generated from subsetRmd function
# out_type: choose from 'html', 'svg', 'png'
# out_path: string, path of output plot, only apply from svg or png
# plot_style:  one of 'detect', 'none', 'linear'
# default is detect, ignoring level but detecting the the longest branch
# linking first and last step as main branch.
# height: int, height of svg or png in pixels, default NULL, automatic
# width: int, width of svg or png in pixels, default NULL, automatic
# only selected steps will be plotted, make sure at least some steps are
# TRUE in 'selected' column in df_wf

#' @importFrom assertthat is.count
# @importFrom rsvg rsvg_svg rsvg_png
#' @noRd
.plotWF <- function(df_wf, plot_style="detect", out_type='html',
                   out_path='default', height=NULL, width=NULL){
    # pre checks
    assert_that(out_type %in% c('html', 'png', 'svg', 'shiny'),
                msg = "output type needs to be one of 'html', 'png', 'svg'")
    assert_that(plot_style %in% c('detect', 'none', 'linear'),
                msg="plot style needs to be one of 'detect', 'none', 'linear'")
    assert_that(is.data.frame(df_wf))
    all(c("t_lvl", "t_number", "t_text","selected",
          "no_run", "no_success", "link_to") %in% names(df_wf)) %>%
        assert_that(msg=glue('One of following columns is missing: "t_lvl"',
                             '"t_number" "t_text" "selected"',
                             '"no_run" "no_success" "link_to"'))
    if (out_path == 'default' & !out_type %in% c('html', 'shiny')){
        assert_that(is.writeable(out_path))
        assert_that(is.count(height) | is.null(height))
            assert_that(is.count(width) | is.null(width))
        out_path = switch(out_type,
                          'svg' = paste0('wfplot',
                                         format(Sys.time(), "%Y%m%d%H%M%S"),
                                         '.svg'),
                          'png' = paste0('wfplot',
                                         format(Sys.time(), "%Y%m%d%H%M%S"),
                                         '.png')
        )
    }
    df_wf <- df_wf[df_wf$selected == TRUE, ]
    if (nrow(df_wf) == 0) return(cat("no step is selected"))

    wf <- .make_plot(df_wf, plot_style, is_main_branch=FALSE)
    wf <- append(wf, .make_plot(df_wf, plot_style, is_main_branch=TRUE),
                 after = length(wf) - 1)
    # special case for detection style plotting, need to move unneeded
    # nodes out of main branch
    if (plot_style == "detect") wf <- .change_branch(df_wf, wf)
    # collapse entire graph
    # return(wf)
    wf <- paste0(wf, collapse = "")
    # plot
    plot <- switch(out_type,
           'shiny' = DOT::dot(wf, return = "verbatim"),
           'html' = DOT::dot(wf)
    )
    return(plot)
}

.find_long_branch <- function(t_number, link_to){
    track_back <- function(t_number, link_to, track_list){
        for (each_track_n in seq_len(length(track_list))){
            each_track = track_list[[each_track_n]] %>% unlist()
            previous_t_number <-
                names(link_to_list[
                    which(vapply(link_to_list,
                           function(x) any(x == t_number[each_track[1]]),
                           logical(1))
                          )
                    ]
                )
            for (each_num in seq_len(length(previous_t_number))) {
                previous_link <- which(t_number == previous_t_number[each_num])
                newtrack = append(previous_link, each_track)
                if (each_num < 2){
                    track_list[[each_track_n]] <- newtrack
                } else {
                    track_list[[each_track_n + each_num - 1]] <- newtrack
                }
            }
        }
        if (length(previous_t_number) == 0) return(track_list)
        track_list <- track_back(t_number, link_to, track_list)
        return(track_list)
    }
    link_to_list <- str_split(link_to, ",") %>%
        lapply(function(x) str_remove_all(x, " "))
    names(link_to_list) <- t_number
    last_step <- list(length(t_number))
            track_list <- track_back(t_number, link_to, last_step)
    long <- vapply(track_list,
                   function(x) all(c(1, length(t_number)) %in% x),
                   logical(1)) %>%
        track_list[.] %>%
        vapply(length, FUN.VALUE = 1L) %>%
        which.max() %>% track_list[.] %>%
        unlist()
    return(long)
}

.make_plot <- function(df_wf, plot_style, is_main_branch=TRUE){
    if (is_main_branch){
        # graph start
        wf <- "subgraph { rank=same;\n"
        df_wf <- switch(plot_style,
            "detect"=df_wf[.find_long_branch(df_wf$t_number, df_wf$link_to), ],
            "none"  =df_wf[0,],
            "linear"={
                df_wf$link_to[seq_len(nrow(df_wf) - 1)] <-
                    df_wf$t_number[seq_len(nrow(df_wf))[-1]]; df_wf
            }
        )
    } else{
        wf <- switch(plot_style,
                     "detect" = "digraph { rankdir=LR;\n",
                     "none"   = "digraph { rankdir=TB;\n",
                     "linear" = "digraph { rankdir=LR;\n")
        df_wf <- switch(plot_style,
            "detect"=df_wf[-.find_long_branch(df_wf$t_number, df_wf$link_to), ],
            "none"  =df_wf,
            "linear"=df_wf[0,]
        )
    }
    if (nrow(df_wf) == 0) return(c(wf, "}"))

    steps <- df_wf$t_number
    step_text <- str_replace_all(df_wf$t_text, '[\'\"]', "\\\\'")
    link_to <- df_wf$link_to
    # reslove 1 to n links
    link_to <- str_split(link_to, ",") %>%
        lapply(function(x) str_remove_all(x, " "))
    # set up colors
    step_color <- ifelse(
        df_wf$no_run == 0,
        'gray',
        ifelse(
            is.na(df_wf$no_run) | is.na(df_wf$no_success),
            "black",
            ifelse(df_wf$no_run != df_wf$no_success, 'red', 'green')
        )
    )
    # dot language
    # add steps
    for (t in seq_along(steps)){
        if (!is.na(link_to[t]) & t < length(steps)){
            for (nlink in link_to[t]){
                wf <- append(
                    wf,
                    paste0('  n', str_replace_all(steps[t], "\\.", "_"), " -> ",
                           '  n', str_replace_all(nlink, "\\.", "_"), ";\n")
                )
            }
        }
    }
    # add color, text
    wf <- c(wf, paste0('  n', str_replace_all(steps, "\\.", "_"),
                       ' [label=\"',
                       steps, step_text, ' ',
                       ifelse(
                           df_wf$no_run == 0, '',
                           paste0(df_wf$no_success, '/', df_wf$no_run)
                       ),
                       '\"', 'fontcolor=', step_color,
                       ' color=white',
                       '];\n'))
    # end graph
    wf <- paste0(c(wf, "}"))
    return(wf)
}

.change_branch <- function(df_wf, wf){
    long <- .find_long_branch(df_wf$t_number, df_wf$link_to)
                    plot_start <- wf %>% str_which("digraph")
    sub_start <- wf %>% str_which("subgraph ")
    sub_steps_lines <- wf %>% str_which(" -> [^\\[]") %>% .[. > sub_start]
    sub_number <- wf[sub_steps_lines] %>%
        str_remove_all("[->;\n]") %>% str_remove("^.*[ ]+") %>%
                str_remove("n") %>% str_replace_all("_", "\\.") %>%
        lapply(function(x) df_wf$t_number[df_wf$t_number == x]) %>%
            unlist()
    move_line_num <- sub_steps_lines[!sub_number %in% df_wf$t_number[long]]
    if (length(move_line_num) == 0) return(wf)
    move_lines <- wf[move_line_num]
    wf <- wf %>% .[-move_line_num] %>% append(move_lines, after = plot_start)
    return(wf)
}
## Function used on this code:
# importFrom(magrittr,"%>%")
# importFrom("assertthat", "assert_that", "is.count")
# importFrom("stringr", "str_replace_all", "str_split",
#            "str_remove", "str_which", "str_remove_all")
# importFrom("DOT", "dot")
# importFrom(assertthat,"is.writeable")
# importFrom("rsvg,"rsvg_svg", "rsvg_png")

# #test code
# source("subsetRmd.R")
# df_wf = subsetRmd("YOUR.Rmd")
# df_wf$no_success[3:8] = 1
# df_wf$no_run[3:5] = 10
# df_wf$no_run[6:8] = 1
# df_wf$selected[1:35] = TRUE
# df_wf$link_to = NA
# df_wf$link_to[1:(nrow(df_wf) - 1)] = df_wf$t_number[2:nrow(df_wf)]
# df_wf$link_to[3] = NA
# df_wf$link_to[1] = "1.1, 2"
# df_wf$link_to[4] = "2.1, 3"
# df_wf$link_to[14] = NA
# df_wf = df_wf[1:17,]
# df_wf$link_to[8] = "3, 2.5"
# df_wf$selected = TRUE
# .plotWF(df_wf, plot_style = "linear")

Try the systemPipeShiny package in your browser

Any scripts or data that you put into this service are public.

systemPipeShiny documentation built on March 16, 2021, 6:01 p.m.