rggplot2visualizationigraphggraph

How to Center Align a Graph in R using ggraph with a Manual Layout?


I'm attempting to create a centred skill tree visualization using ggraph in R, but my graph persists in being left-aligned within the plotting area. The tree is built from a JSON data source specifying each node's row and col, which I use to create a manual layout for the nodes.

Here's how I set up the graph:

library(jsonlite)
library(igraph)
library(ggraph)

file_path <- "./nodes.json"
mastery_tree_data <- fromJSON(file_path)

graph <- make_empty_graph(directed = TRUE)

# Add nodes
for (node_id in names(mastery_tree_data)) {
  node_info <- mastery_tree_data[[node_id]]
  label_text <- paste(node_info$name, "\nLvl", node_info$current_level, "/", node_info$max_level)
  graph <- add_vertices(graph, 1, name = node_id, label = label_text)
}

# Add edges
for (node_id in names(mastery_tree_data)) {
  node_info <- mastery_tree_data[[node_id]]
  if (!is.null(node_info$children)) {
    for (child_id in node_info$children) {
      if (child_id %in% names(mastery_tree_data)) {
        graph <- add_edges(graph, c(node_id, child_id))
      }
    }
  }
}

# Manual layout based on the 'row' and 'col' values from JSON
manual_layout <- data.frame(
  name = names(mastery_tree_data),
  x = sapply(mastery_tree_data, function(x) x$col),
  y = sapply(mastery_tree_data, function(x) x$row),
  stringsAsFactors = FALSE
)

This is what the ggraph skill tree currently looks like: ggraph skill tree

It appears that despite the positions I've specified, the graph is not centred as intended. I am struggling to find a solution within the functionalities of ggraph to correct this.

To address the alignment issue, I calculated the midpoint of the x coordinates and attempted to shift the graph so that it would be centred in the plotting area.

Here's the specific code I used for that:

manual_layout$name <- as.character(manual_layout$name)
current_midpoint_x <- mean(manual_layout$x)
desired_midpoint_x <- 0 # We want to center the graph at x = 0
shift_x <- desired_midpoint_x - current_midpoint_x
manual_layout$x <- manual_layout$x + shift_x

# Set x limits for some padding around the nodes
x_range <- max(manual_layout$x) - min(manual_layout$x)
x_padding <- 0.1 * x_range
x_lim <- c(min(manual_layout$x) - x_padding, max(manual_layout$x) + x_padding)

plot <- ggraph(graph, layout = 'manual', node.positions = manual_layout) +
 geom_edge_link() +
 geom_node_point(size = 3) +
 geom_node_text(aes(label = name), repel = TRUE, size = 3) +
 expand_limits(x = x_lim) +
 theme(plot.margin = unit(c(1, 1, 1, 1), "cm"))

print(plot)

I expected this to reposition my graph so that it's centred in the plotting area. Instead, the graph remains left-aligned, with no visible changes to its position. I am not sure why the expand_limits and theme adjustments are not centring the graph as intended.

I've tried several approaches to alter the layout, but the graph still doesn't align to the center. Any guidance on how to properly center a graph in ggraph with a manual layout would be highly appreciated.

I don't really know how to use dput(), so here's a shortened and compacted version of my JSON instead, I hope this is alright:

{"Node1":{"name":"Attack","parents":[],"children":["Node2_1","Node2_2"],"current_level":0,"max_level":3,"row":1,"col":1},"Node2_1":{"name":"Gold Loot","parents":["Node1"],"children":["Node3_1","Node3_2"],"current_level":0,"max_level":3,"row":2,"col":1},"Node2_2":{"name":"Health","parents":["Node1"],"children":["Node3_3"],"current_level":0,"max_level":3,"row":2,"col":2},"Node3_1":{"name":"Health","parents":["Node2_1"],"children":["Node4"],"current_level":0,"max_level":3,"row":3,"col":1},"Node3_2":{"name":"Idle Earnings","parents":["Node2_1"],"children":["Node4"],"current_level":0,"max_level":4,"row":3,"col":2},"Node3_3":{"name":"Gold Loot","parents":["Node2_2"],"children":["Node4"],"current_level":0,"max_level":3,"row":3,"col":3},"Node4":{"name":"Attack","parents":["Node3_1","Node3_2","Node3_3"],"children":[],"current_level":0,"max_level":3,"row":4,"col":1}}

I've also tossed the whole script on GitHub Gists: mastery_tree_visualiser.r

And here's is the whole JSON file on PrivateBin, just in case (unused key:values are artifacts from when I was doing this in PHP): nodes.json

(PrivateBin is an open source pastebin alternative)


Solution

  • I think you are essentially looking for your nodes to be 'centre justified', which requires recalculating the x positions. I would perhaps simplify the graph creation a bit:

    library(tidygraph)
    library(ggraph)
    
    edges_out <- lapply(names(mastery_tree_data), function(x) {
      kids <- mastery_tree_data[[x]]$children
      if(length(kids) > 0) {
        return(data.frame(from = x, to = kids))
      }
    })
    
    edges_in <- edges_out <- lapply(names(mastery_tree_data), function(x) {
      mum <- mastery_tree_data[[x]]$parents
      if(length(mum) > 0) {
        return(data.frame(from = mum, to = x))
      }
    })
    
    g <- do.call('rbind', c(edges_in, edges_out)) |>
      igraph::graph.data.frame() 
    

    Then, manipulate the x positions and plot:

    as_tbl_graph(g) %>%
      mutate(value = sapply(name, \(x) paste0(mastery_tree_data[[x]]$name, '\n',
                                              mastery_tree_data[[x]]$current_level,
                                              '/',
                                              mastery_tree_data[[x]]$max_level))
                            ) %>%
      mutate(col = sapply(name, \(x) mastery_tree_data[[x]]$col),
             row = as.integer(sapply(name, \(x) mastery_tree_data[[x]]$row))) %>%
      group_by(row) %>%
      mutate(col = row_number()) %>%
      mutate(col = (col - median(col))/n()) %>%
      ggraph(layout = 'manual', x = col, y = row) +
      geom_edge_diagonal(color = 'gray') +
      geom_node_point(color = 'steelblue', size = 5) +
      geom_node_text(aes(label = value), size = 3, repel = TRUE) +
      theme_void()
    

    enter image description here