M. Wood
M. Wood

Reputation: 587

ggraph transpose graph coordinates

I have a tree diagram which I want to display horizontally. I had been using igraph and inspiration from this solution, but I am updating my code to {ggraph} hoping for a cleaner aesthetic and better control over edge labels.

In {igraph} I am able to manipulate the l_igraph matrix directly to transpose x and y coordinates. However, when I do this to the dataframe produced by tidygraph::create_layout() and plot it I get an error to do about breaking some expectation of object class.

Thanks in advance for your recommendations.

# dependencies
library(dplyr)
#> 
#> Attaching package: 'dplyr'
#> The following objects are masked from 'package:stats':
#> 
#>     filter, lag
#> The following objects are masked from 'package:base':
#> 
#>     intersect, setdiff, setequal, union
library(stringr)
library(ggplot2)
library(igraph)
#> 
#> Attaching package: 'igraph'
#> The following objects are masked from 'package:dplyr':
#> 
#>     as_data_frame, groups, union
#> The following objects are masked from 'package:stats':
#> 
#>     decompose, spectrum
#> The following object is masked from 'package:base':
#> 
#>     union
library(tidygraph)
#> 
#> Attaching package: 'tidygraph'
#> The following object is masked from 'package:igraph':
#> 
#>     groups
#> The following object is masked from 'package:stats':
#> 
#>     filter
library(ggraph)

# edgelist
df_graph <- tibble::tibble(from = rep("parent", 8),
                           to = stringr::str_c("child ", letters[1:8]))


# igraph
g_igraph <- igraph::graph_from_data_frame(df_graph, directed = FALSE)

l_igraph <-
  igraph::layout_as_tree(
    g_igraph,
    root = igraph::get.vertex.attribute(g_igraph, "name") %>% stringr::str_detect(., "parent") %>% which(.)) %>%
  .[,2:1]
  

igraph::plot.igraph(g_igraph, layout = l_igraph,
                    vertex.size = 8,
                    vertex.color = "grey90",
                    # vertex label attrs
                    vertex.label.cex = 0.7,
                    vertex.label.font = 2,
                    vertex.label.color = "#000000",
                    vertex.label.bg.color = "#FFFFFF",
                    # edge label attrs
                    edge.label.font = 2,
                    edge.label.color = "#000000",
                    # edge arrow attrs
                    edge.arrow.size = 1.5,
                    edge.arrow.width = 0.5)


# tidygraph / ggraph
g_tidy <- tidygraph::as_tbl_graph(g_igraph)

# ggraph top-down tree
l_tidy <- ggraph::create_layout(
  g_tidy,
  layout = 'tree',
  root = igraph::get.vertex.attribute(g_tidy, "name") %>% stringr::str_detect(., "parent") %>% which(.)
) 

ggraph::ggraph(l_tidy) +
  ggraph::geom_edge_link() +
  ggraph::geom_node_point() 


# ggraph horizontal tree, attempted to modify xy coords like igraph example above
xy <- dplyr::select(l_tidy, x:y)
l_tidy_revised <- l_tidy %>% 
  tidygraph::mutate(y = xy$x,
                    x = xy$y)

ggraph::ggraph(l_tidy_revised) +
  ggraph::geom_edge_link() +
  ggraph::geom_node_point() 
#> Error in `.register_graph_context()`:
#> ! `graph` must be a <tbl_graph> object

#> Backtrace:
#>      ▆
#>   1. ├─base::tryCatch(...)
#>   2. │ └─base (local) tryCatchList(expr, classes, parentenv, handlers)
#>   3. │   ├─base (local) tryCatchOne(...)
#>   4. │   │ └─base (local) doTryCatch(return(expr), name, parentenv, handler)
#>   5. │   └─base (local) tryCatchList(expr, names[-nh], parentenv, handlers[-nh])
#>   6. │     └─base (local) tryCatchOne(expr, names, parentenv, handlers[[1L]])
#>   7. │       └─base (local) doTryCatch(return(expr), name, parentenv, handler)
#>   8. ├─base::withCallingHandlers(...)
#>   9. ├─base::saveRDS(...)
#>  10. ├─base::do.call(...)
#>  11. ├─base (local) `<fn>`(...)
#>  12. └─global `<fn>`(input = base::quote("fussy-agama_reprex.R"))
#>  13.   └─rmarkdown::render(input, quiet = TRUE, envir = globalenv(), encoding = "UTF-8")
#>  14.     └─knitr::knit(knit_input, knit_output, envir = envir, quiet = quiet)
#>  15.       └─knitr:::process_file(text, output)
#>  16.         ├─base::withCallingHandlers(...)
#>  17.         ├─knitr:::process_group(group)
#>  18.         └─knitr:::process_group.block(group)
#>  19.           └─knitr:::call_block(x)
#>  20.             └─knitr:::block_exec(params)
#>  21.               └─knitr:::eng_r(options)
#>  22.                 ├─knitr:::in_input_dir(...)
#>  23.                 │ └─knitr:::in_dir(input_dir(), expr)
#>  24.                 └─knitr (local) evaluate(...)
#>  25.                   └─evaluate::evaluate(...)
#>  26.                     └─evaluate:::evaluate_call(...)
#>  27.                       ├─evaluate (local) handle(...)
#>  28.                       │ └─base::try(f, silent = TRUE)
#>  29.                       │   └─base::tryCatch(...)
#>  30.                       │     └─base (local) tryCatchList(expr, classes, parentenv, handlers)
#>  31.                       │       └─base (local) tryCatchOne(expr, names, parentenv, handlers[[1L]])
#>  32.                       │         └─base (local) doTryCatch(return(expr), name, parentenv, handler)
#>  33.                       ├─base::withCallingHandlers(...)
#>  34.                       ├─base::withVisible(value_fun(ev$value, ev$visible))
#>  35.                       └─knitr (local) value_fun(ev$value, ev$visible)
#>  36.                         └─knitr (local) fun(x, options = options)
#>  37.                           ├─base::withVisible(knit_print(x, ...))
#>  38.                           ├─knitr::knit_print(x, ...)
#>  39.                           └─knitr:::knit_print.default(x, ...)
#>  40.                             └─evaluate (local) normal_print(x)
#>  41.                               ├─base::print(x)
#>  42.                               └─ggplot2:::print.ggplot(x)
#>  43.                                 ├─ggplot2::ggplot_build(x)
#>  44.                                 └─ggraph:::ggplot_build.ggraph(x)
#>  45.                                   └─tidygraph::.register_graph_context(attr(plot$data, "graph"), free = TRUE)
#>  46.                                     └─cli::cli_abort("{.arg graph} must be a {.cls tbl_graph} object")
#>  47.                                       └─rlang::abort(...)

# check class for layout object(s)
# l_tidy is not a tbl_graph object, but still plots
tidygraph::is.tbl_graph(l_tidy)
#> [1] FALSE
class(l_tidy)
#> [1] "layout_tbl_graph" "layout_ggraph"    "data.frame"

# l_tidy_revised has same classes as l_tidy, does not plot
tidygraph::is.tbl_graph(l_tidy_revised)
#> [1] FALSE
class(l_tidy_revised)
#> [1] "layout_tbl_graph" "layout_ggraph"    "data.frame"

Created on 2023-02-08 with reprex v2.0.2

Upvotes: 1

Views: 112

Answers (1)

M. Wood
M. Wood

Reputation: 587

It turns out that {ggraph} will accept an igraph layout in its initial call, giving me the desired layout transpose.

Graph & layout objects are copied from OP.

# dependencies
library(dplyr)
#> 
#> Attaching package: 'dplyr'
#> The following objects are masked from 'package:stats':
#> 
#>     filter, lag
#> The following objects are masked from 'package:base':
#> 
#>     intersect, setdiff, setequal, union
library(stringr)
library(ggplot2)
library(igraph)
#> 
#> Attaching package: 'igraph'
#> The following objects are masked from 'package:dplyr':
#> 
#>     as_data_frame, groups, union
#> The following objects are masked from 'package:stats':
#> 
#>     decompose, spectrum
#> The following object is masked from 'package:base':
#> 
#>     union
library(tidygraph)
#> 
#> Attaching package: 'tidygraph'
#> The following object is masked from 'package:igraph':
#> 
#>     groups
#> The following object is masked from 'package:stats':
#> 
#>     filter
library(ggraph)

# edgelist
df_graph <- tibble::tibble(from = rep("parent", 8),
                           to = stringr::str_c("child ", letters[1:8]))


# igraph
g_igraph <-
  igraph::graph_from_data_frame(df_graph, directed = FALSE)

l_igraph <-
  igraph::layout_as_tree(
    g_igraph,
    root = igraph::get.vertex.attribute(g_igraph, "name") %>% stringr::str_detect(., "parent") %>% which(.)
  ) %>%
  .[, 2:1]

# tidygraph / ggraph
g_tidy <- tidygraph::as_tbl_graph(g_igraph)

# ggraph top-down tree
l_tidy <- ggraph::create_layout(
  g_tidy,
  layout = 'tree',
  root = igraph::get.vertex.attribute(g_tidy, "name") %>% stringr::str_detect(., "parent") %>% which(.)
)

# undesired top-down layout
ggraph::ggraph(l_tidy) +
  ggraph::geom_edge_link() +
  ggraph::geom_node_point()


# desired transpose layout
ggraph::ggraph(graph = g_tidy, layout = l_igraph) +
  ggraph::geom_edge_link() +
  ggraph::geom_node_point()

Created on 2023-02-08 with reprex v2.0.2

Upvotes: 1

Related Questions