DiagrammeR:render_graph() 产生不准确的节点字体颜色

DiagrammeR: render_graph() produces inaccurate node font color

我一直在探索 R 中的 DiagrammeR 包制作流程图和图形的可能性。看起来很不错,但我 运行 遇到了一个奇怪的情况:render_graph() 函数在节点上产生了错误的字体颜色,我不明白为什么。

在下面的可重现示例中:

节点字体颜色默认设置为“gray50”(下方全局图形属性中的第 12 行),但使用 render_graph() 函数渲染时字体颜色为黑色(见下图 1) .

然而,当使用 generate_dot() |> grViz() 函数序列渲染时,它可以正确显示(见第二张图片)。

我还没有发现任何其他不准确之处,但我不能排除它们。

我想知道,这是错误还是功能?这是否意味着我不应该信任 render_graph()?有谁知道为什么会出现这种差异?

谢谢!

# Create a minimal graph with just 2 nodes and no edges
nodes <- DiagrammeR::create_node_df(2,label=c("a","b"))
graph <- DiagrammeR::create_graph(nodes_df=nodes,edges_df=NULL)

# Show its global attributes
graph |> DiagrammeR::get_global_graph_attr_info()
attr value attr_type
1 layout neato graph
2 outputorder edgesfirst graph
3 bgcolor white graph
4 fontname Helvetica node
5 fontsize 10 node
6 shape circle node
7 fixedsize true node
8 width 0.5 node
9 style filled node
10 fillcolor aliceblue node
11 color gray70 node
12 fontcolor gray50 node
13 fontname Helvetica edge
14 fontsize 8 edge
15 len 1.5 edge
16 color gray80 edge
17 arrowsize 0.5 edge
# Render it with one method
graph |> DiagrammeR::render_graph()

Graph rendered with render_graph()

# Render it with another method
graph |> DiagrammeR::generate_dot() |> DiagrammeR::grViz()

Graph rendered with generate_dot() |> grViz()

如果没有明确说明,

render_graph 尝试设置对比文本颜色。函数的相关部分是

# pseudocode
...
if (!("fontcolor" %in% colnames(graph$nodes_df)) &
      "fillcolor" %in% colnames(graph$nodes_df)) {
     graph$nodes_df$fontcolor <- tibble::tibble(value = graph$nodes_df$fillcolor) %>%
      dplyr::mutate(value_x = DiagrammeR:::contrasting_text_color(background_color = value)) %>%
     dplyr::pull(value_x)
    }
...

要生成与 post 中替代方法相同的图表,您可以删除此部分,覆盖函数。使用 :::.

在需要的地方插入内部 DiagrammeR 函数
render_graph <- function (graph, layout = NULL, output = NULL, as_svg = FALSE, 
          title = NULL, width = NULL, height = NULL) 
{
  fcn_name <- DiagrammeR:::get_calling_fcn()
  if (DiagrammeR:::graph_object_valid(graph) == FALSE) {
    emit_error(fcn_name = fcn_name, reasons = "The graph object is not valid")
  }
  if (is.null(output)) {
    output <- "graph"
  }
  if (output == "graph") {
    if (!is.null(title)) {
      graph <- DiagrammeR:::add_global_graph_attrs(graph, "label", 
                                      title, "graph")
      graph <- DiagrammeR:::add_global_graph_attrs(graph, "labelloc", 
                                      "t", "graph")
      graph <- DiagrammeR:::add_global_graph_attrs(graph, "labeljust", 
                                      "c", "graph")
      graph <- DiagrammeR:::add_global_graph_attrs(graph, "fontname", 
                                      "Helvetica", "graph")
      graph <- DiagrammeR:::add_global_graph_attrs(graph, "fontcolor", 
                                      "gray30", "graph")
    }
    if (nrow(graph$nodes_df) > 0) {
      if (!("fillcolor" %in% colnames(graph$nodes_df))) {
        if ("fillcolor" %in% graph$global_attrs$attr) {
          graph$nodes_df$fillcolor <- graph$global_attrs %>% 
            dplyr::filter(attr == "fillcolor" & 
                            attr_type == "node") %>% dplyr::select(value) %>% 
            purrr::flatten_chr()
        }
        else {
          graph$nodes_df$fillcolor <- "white"
        }
      }
    }
    if (nrow(graph$nodes_df) > 0) {
      if ("fillcolor" %in% colnames(graph$nodes_df)) {
        if ("fillcolor" %in% graph$global_attrs$attr) {
          graph$nodes_df$fillcolor[which(is.na(graph$nodes_df$fillcolor))] <- graph$global_attrs[which(graph$global_attrs$attr == 
                                                                                                         "fillcolor"), 2]
        }
      }
    }
    if ("fillcolor" %in% colnames(graph$nodes_df)) {
      graph$nodes_df <- graph$nodes_df %>% dplyr::left_join(x11_hex() %>% 
                                                              dplyr::as_tibble() %>% dplyr::mutate(hex = toupper(hex)), 
                                                            by = c(fillcolor = "x11_name")) %>% dplyr::mutate(new_fillcolor = dplyr::coalesce(hex, 
                                                                                                                                              fillcolor)) %>% dplyr::select(-fillcolor, -hex) %>% 
        dplyr::rename(fillcolor = new_fillcolor)
    }
    if (!is.null(layout)) {
      if (layout %in% c("circle", "tree", "kk", 
                        "fr", "nicely")) {
        graph <- graph %>% add_global_graph_attrs(attr = "layout", 
                                                  value = "neato", attr_type = "graph")
        if ("x" %in% colnames(graph$nodes_df)) {
          graph$nodes_df <- graph$nodes_df %>% dplyr::select(-x)
        }
        if ("y" %in% colnames(graph$nodes_df)) {
          graph$nodes_df <- graph$nodes_df %>% dplyr::select(-y)
        }
        if (layout == "circle") {
          coords <- graph %>% to_igraph() %>% igraph::layout_in_circle() %>% 
            dplyr::as_tibble() %>% dplyr::rename(x = V1, 
                                                 y = V2) %>% dplyr::mutate(x = x * (((count_nodes(graph) + 
                                                                                        (0.25 * count_nodes(graph))))/count_nodes(graph))) %>% 
            dplyr::mutate(y = y * (((count_nodes(graph) + 
                                       (0.25 * count_nodes(graph))))/count_nodes(graph)))
        }
        if (layout == "tree") {
          coords <- (graph %>% to_igraph() %>% igraph::layout_with_sugiyama())[[2]] %>% 
            dplyr::as_tibble() %>% dplyr::rename(x = V1, 
                                                 y = V2)
        }
        if (layout == "kk") {
          coords <- graph %>% to_igraph() %>% igraph::layout_with_kk() %>% 
            dplyr::as_tibble() %>% dplyr::rename(x = V1, 
                                                 y = V2)
        }
        if (layout == "fr") {
          coords <- graph %>% to_igraph() %>% igraph::layout_with_fr() %>% 
            dplyr::as_tibble() %>% dplyr::rename(x = V1, 
                                                 y = V2)
        }
        if (layout == "nicely") {
          coords <- graph %>% to_igraph() %>% igraph::layout_nicely() %>% 
            dplyr::as_tibble() %>% dplyr::rename(x = V1, 
                                                 y = V2)
        }
        graph$nodes_df <- graph$nodes_df %>% dplyr::bind_cols(coords)
      }
    }
    if (("image" %in% colnames(graph %>% get_node_df()) || 
         "fa_icon" %in% colnames(graph %>% get_node_df()) || 
         as_svg) & requireNamespace("DiagrammeRsvg", 
                                    quietly = TRUE)) {
      if (!("DiagrammeRsvg" %in% rownames(utils::installed.packages()))) {
        emit_error(fcn_name = fcn_name, reasons = c("Cannot currently render the graph to an SVG", 
                                                    "please install the `DiagrammeRsvg` package and retry", 
                                                    "pkg installed using `install.packages('DiagrammeRsvg')`", 
                                                    "otherwise, set `as_svg = FALSE`"))
      }
      dot_code <- generate_dot(graph)
      svg_vec <- strsplit(DiagrammeRsvg::export_svg(grViz(diagram = dot_code)), 
                          "\n") %>% unlist()
      svg_tbl <- get_svg_tbl(svg_vec)
      svg_lines <- "<svg display=\"block\" margin=\"0 auto\" position=\"absolute\" width=\"100%\" height=\"100%\""
      svg_line_no <- svg_tbl %>% dplyr::filter(type == 
                                                 "svg") %>% dplyr::pull(index)
      svg_vec[svg_line_no] <- svg_lines
      if ("image" %in% colnames(graph %>% get_node_df())) {
        node_id_images <- graph %>% get_node_df() %>% 
          dplyr::select(id, image) %>% dplyr::filter(image != 
                                                       "") %>% dplyr::pull(id)
        filter_lines <- graph %>% get_node_df() %>% dplyr::select(id, 
                                                                  image) %>% dplyr::filter(image != "") %>% 
          dplyr::mutate(filter_lines = as.character(glue::glue("<filter id=\"{id}\" x=\"0%\" y=\"0%\" width=\"100%\" height=\"100%\"><feImage xlink:href=\"{image}\"/></filter>"))) %>% 
          dplyr::pull(filter_lines) %>% paste(collapse = "\n")
        filter_shape_refs <- as.character(glue::glue(" filter=\"url(#{node_id_images})\" "))
        svg_shape_nos <- svg_tbl %>% dplyr::filter(node_id %in% 
                                                     node_id_images) %>% dplyr::filter(type == "node_block") %>% 
          dplyr::pull(index)
        svg_shape_nos <- svg_shape_nos + 3
        svg_text_nos <- svg_shape_nos + 1
        for (i in seq(node_id_images)) {
          svg_vec[svg_shape_nos[i]] <- sub(" ", 
                                           paste0(filter_shape_refs[i]), svg_vec[svg_shape_nos[i]])
          svg_vec[svg_text_nos[i]] <- ""
        }
        svg_vec[svg_line_no + 1] <- paste0(svg_vec[svg_line_no + 
                                                     1], "\n\n", filter_lines, "\n")
      }
    }
    else {
      dot_code <- generate_dot(graph)
      grVizObject <- grViz(diagram = dot_code, width = width, 
                           height = height)
      display <- grVizObject
    }
    display
  }
  else if (output == "visNetwork") {
    visnetwork(graph)
  }
}

然后,两个函数给出相同的结果。

graph |> render_graph()