Node location in DiagrammeR

I have used DiagrammeR for the visualization below. However, I would like to have the different node types organized under each other in columns, which only automatically happens with the yellow ones.

I tried using set_node_position for this purpose, but this does not have an effect on the rendered graph.

Here is my code:

graph <- create_graph() 
  
  graph <- add_nodes_from_table(graph = graph,
                                table = concepts_df,
                                label_col = name_english,
                                type_col = type)
  
  graph <- add_edges_from_table(graph = graph,
              table = links_df,
              from_col = "sender",
              to_col = "receiver",
              from_to_map = "label")

graph <-
    graph %>%
    set_node_position(
      node = 1,
      x = 1, y = 1,
      use_labels = TRUE) %>%
    set_node_position(
      node = 2,
      x = 2, y = 2,
      use_labels = TRUE)

graph_rendering <- graph
  graph_rendering$nodes_df$fillcolor <- unlist(lapply(concepts_df$type, 
                                                      function(type) switch(type,
                                                                            "Target" = "#A0B7B4",
                                                                            "Direct threat" = "#FCF2E1",
                                                                            "Indirect factor" = "#EEE5CF",
                                                                            "Biophysical stressor" = "#C7DDDA",
                                                                            "Policy issue" = "#ECD2A4" 
                                                      )))

 
  graph_rendering$edges_df$color <- unlist(lapply(links_df$sign,
                                                  function(type) switch(type,
                                                                            "+" = "darkseagreen", 
                                                                            "-" = "tomato",
                                                                            "+/-" = "grey"
                                                                        
                                                      )))

                                                      
  graph_rendering$nodes_df$shape <- ifelse(concepts_df$type == "Target", "square","circle")
  graph_rendering$nodes_df$fontcolor <- "black"
  
  graph_rendering$global_attrs <- rbind(graph_rendering$global_attrs,
                                        c("rankdir","LR","graph"),
                                        c("ranksep","2 equally","graph"),
                                        c("fontsize","14","node"),
                                        c("arrowsize",1,"edge"))
                                          
  graph_rendering$global_attrs[graph_rendering$global_attrs == "layout", 2] <- "dot"
  
  graph_rendering$global_attrs[graph_rendering$global_attrs == "layout = neato"]
    
  render_graph(graph_rendering)

How can I set the position of the nodes?

I’m not a DiagrammeR user myself, so if you could extract the DOT source code and post here, it would make it easier to help you.

The pos attribute is only supported by neato and fdp layout engines, not by dot:

In neato and fdp, pos can be used to set the initial position of a node

Typically, you would use the rank attribute to control node placement the way you like.

Many thanks for your help! I am not sure exactly how to extract the DOT code, I tried this which runs but does not seem to output anything:

render_graph(graph_rendering, output = “DOT”)

I am a little confused with the different languages, sorry. But it seems like I should try the rank attribute. However, when trying to implement it using this code

graph {
rankdir=LR;
{rank = same; 1; 2;};
}

I get this error code:

Error: object ‘same’ not found

}
Error: unexpected ‘}’ in " }"

Is my code written incorrectly?

Now I tried implementing rank like this, but it does not seem to work either:

graph_rendering$global_attrs <- rbind(graph_rendering$global_attrs,
c(“rankdir”,“LR”,“graph”),
c(“ranksep”,“2 equally”,“graph”),
c(“fontsize”,“14”,“node”),
c(“arrowsize”,1,“edge”),
c(“rank”,“same”))

Your DOT code renders fine here:
[dot verbose=true]
graph {
rankdir=LR;
{rank = same; 1; 2;};
}
[/dot]

My guess is that you’re using it wrong in DiagrammeR, but I don’t know.

You are right, my implementation in Diagramme R is probably the issue. I thought the last line here would do it:

graph_rendering$global_attrs <- rbind(graph_rendering$global_attrs,
c(“rankdir”,“LR”,“graph”),
c(“ranksep”,“2 equally”,“graph”),
c(“fontsize”,“14”,“node”),
c(“arrowsize”,1,“edge”),
c(“rank”,“same”,“graph”))

It runs fine but does not render.

Here’s an example of using rank in plaing DOT language:

[dot verbose=true]

digraph {
    rankdir=LR
    
    {
        rank=same
        node [style=filled fillcolor=DeepPink]
        a1 a2 a3 a4
    }

    {
        rank=same
        node [style=filled fillcolor=DeepSkyBlue]
        b1 b2 b3 b4
    }
    
    
    {
        rank=same
        node [style=filled fillcolor=ForestGreen]
        c1 c2 c3 c4
    }
    
    {
        rank=same
        node [style=filled fillcolor=Gold]
        d1 d2
    }

    b1 -> c1
    a1 -> b4
    a2 -> b3
    a3 -> b2
    a4 -> b3
    b2 -> c2
    b1 -> c3
    b3 -> c2
    c1 -> d2
    c2 -> d2
    c4 -> d1
}

[/dot]

You can play with it yourself through the Graphviz Visual Editor.

Thank you! It seems like this is possible for me to run, but to refer to the imported nodes in my data set, do I simply type label=“nodename” instead of a1, a2, etc?

No. Labels are just a way to show another text instead of the node ID. You should use the node ID’s which in your case seems to be 1, 2, …

Here’s a modified example:

[dot verbose=true]

digraph {
    rankdir=LR
    
    {
        rank=same
        node [style=filled fillcolor=DeepPink]
        1 [label="Node 1"]
        2 [label="Node 2"]
        3 [label="Node 3"]
        4 [label="Node 4"]
    }

    {
        rank=same
        node [style=filled fillcolor=DeepSkyBlue]
        5 [label="Node 5"]
        6 [label="Node 6"]
        7 [label="Node 7"]
        8 [label="Node 8"]
    }
    
    
    {
        rank=same
        node [style=filled fillcolor=ForestGreen]
        9 [label="Node 9"]
        10 [label="Node 10"]
        11 [label="Node 11"]
        12 [label="Node 12"]
    }
    
    {
        rank=same
        node [style=filled fillcolor=Gold]
        13 [label="Node 13"]
        14 [label="Node 14"]
    }

    5 -> 9
    1 -> 8
    2 -> 7
    3 -> 6
    4 -> 7
    6 -> 10
    5 -> 11
    7 -> 10
    9 -> 14
    10 -> 14
    12 -> 13
}

[/dot]

Play with it here

Thanks, I thought this was getting closer to a solution, but I cannot generate this structure in my produced graph (in the image above), created from my dataframe (concepts_df). I tried your code with no success, refering to the IDs and names of my nodes:

    1 [label="Non-native species"]
    2 [label="Water flow"]
    3 [label="Fish connectivity"]
    4 [label="Cultural heritage"]

You need to find a way to define subgraphs for each of your categories: Target, Direct threat, Indirect factor, Biophysical stressor and Policy issue through DiagrammeR. Then you need to place all nodes that belong to the same category in their own subgraph and put rank=same in each subgraph.

You should’t change the labels. They’re already implicitly defined through your dataset as I understand it.

I now have RStudio and DiagrammeR up and running. I sent you a private message about it. If you are allowed to share your dataset with me you can do that through a private message if you like.

I managed to output the DOT source from DiagrammeR with:

generate_dot(graph) %>% cat(file="concepts.dot")

Unfortunately the syntax is not correct. But after replacing ' with " it renders, but not at all correctly. I guess the output function in DiagrammeR is broken since it renders fine within RStudio.

[dot verbose=true]

digraph {

graph [layout = "neato",
       outputorder = "edgesfirst",
       bgcolor = "white"]

node [fontname = "Helvetica",
      fontsize = "10",
      shape = "circle",
      fixedsize = "true",
      width = "0.5",
      style = "filled",
      fillcolor = "aliceblue",
      color = "gray70",
      fontcolor = "gray50"]

edge [fontname = "Helvetica",
     fontsize = "8",
     len = "1.5",
     color = "gray80",
     arrowsize = "0.5"]

  "1" [label = "Non-native species"] 
  "2" [label = "Water flow"] 
  "3" [label = "Fish connectivity"] 
  "4" [label = "Cultural heritage"] 
  "5" [label = "Ecological restoration"] 
  "6" [label = "Climate change adaptation"] 
  "7" [label = "Construction of wetlands"] 
  "8" [label = "Storm water"] 
  "9" [label = "Phosphorus dams"] 
  "10" [label = "Buffer zones"] 
  "11" [label = "Lime treatment"] 
  "12" [label = "Private sewage"] 
  "13" [label = "Water quality"] 
  "14" [label = "Upstream regulation"] 
  "15" [label = "Bottom sediment"] 
  "16" [label = "Managing invasive species"] 
  "17" [label = "Recreational boats"] 
  "18" [label = "Trade with aquarium/dam species"] 
  "19" [label = "Shipping"] 
  "20" [label = "Climate change"] 
  "21" [label = "Drought"] 
  "22" [label = "Sea level rise"] 
  "23" [label = "Heavy rains"] 
  "24" [label = "Covering of land with impermeable material"] 
  "25" [label = "Snow landfills"] 
  "26" [label = "Winter salting"] 
  "27" [label = "Allocation of land for water measures"] 
  "28" [label = "Water filtering and treatment"] 
  "29" [label = "Increased yield"] 
  "30" [label = "Fertilisers"] 
  "31" [label = "Microplastics"] 
  "32" [label = "Changed behaviour in consumption"] 
  "33" [label = "Sludge from sewage treatment"] 
  "34" [label = "Pharmaceuticals"] 
  "35" [label = "Pollutants in soil from historical activities"] 
  "36" [label = "Water use"] 
  "37" [label = "Unintentional introduction of species"] 
  "38" [label = "Dredging"] 
  "39" [label = "River channeling and straightening of water courses"] 
  "40" [label = "Dam building"] 
  "41" [label = "Storm water runoff"] 
  "42" [label = "Heavy metals"] 
  "43" [label = "Leakage from land-based production"] 
  "44" [label = "Emissions from private sewage"] 
  "45" [label = "Emissions from treatment plants"] 
  "46" [label = "Spreading of invasive species"] 
  "47" [label = "Reduced flow velocity"] 
  "48" [label = "Salt water intrusion"] 
  "49" [label = "Flooding"] 
  "50" [label = "Eutrophication"] 
  "51" [label = "Environmental pollutants"] 
  "52" [label = "Coastal water quality"] 
  "53" [label = "Natural wetlands"] 
  "54" [label = "Water quality in lakes"] 
  "55" [label = "Running water courses"] 
  "56" [label = "Ground water sources"] 
  "57" [label = "Shallow coastal zones"] 
  "58" [label = "Fish"] 
  "59" [label = "Bottom habitats"] 
  "1"->"37" 
  "2"->"40" 
  "3"->"40" 
  "3"->"58" 
  "4"->"40" 
  "5"->"39" 
  "5"->"58" 
  "5"->"49" 
  "6"->"49" 
  "6"->"27" 
  "7"->"53" 
  "7"->"27" 
  "7"->"28" 
  "8"->"24" 
  "8"->"25" 
  "8"->"26" 
  "8"->"27" 
  "8"->"28" 
  "9"->"27" 
  "10"->"27" 
  "11"->"29" 
  "11"->"43" 
  "12"->"44" 
  "13"->"42" 
  "13"->"43" 
  "13"->"50" 
  "13"->"51" 
  "13"->"31" 
  "13"->"44" 
  "13"->"45" 
  "14"->"32" 
  "14"->"33" 
  "14"->"45" 
  "15"->"34" 
  "15"->"35" 
  "16"->"46" 
  "16"->"18" 
  "17"->"37" 
  "17"->"42" 
  "18"->"37" 
  "19"->"37" 
  "19"->"38" 
  "19"->"39" 
  "19"->"42" 
  "20"->"21" 
  "20"->"22" 
  "20"->"23" 
  "20"->"6" 
  "21"->"47" 
  "22"->"48" 
  "23"->"49" 
  "23"->"41" 
  "24"->"41" 
  "25"->"41" 
  "26"->"42" 
  "27"->"28" 
  "27"->"29" 
  "28"->"43" 
  "29"->"30" 
  "30"->"43" 
  "31"->"44" 
  "31"->"45" 
  "32"->"31" 
  "32"->"44" 
  "32"->"34" 
  "33"->"30" 
  "33"->"45" 
  "34"->"45" 
  "34"->"51" 
  "34"->"59" 
  "35"->"51" 
  "35"->"59" 
  "36"->"2" 
  "37"->"46" 
  "38"->"50" 
  "38"->"59" 
  "38"->"47" 
  "39"->"53" 
  "39"->"57" 
  "39"->"47" 
  "40"->"47" 
  "41"->"49" 
  "41"->"51" 
  "41"->"42" 
  "42"->"51" 
  "43"->"50" 
  "44"->"50" 
  "44"->"51" 
  "45"->"50" 
  "45"->"51" 
  "46"->"58" 
  "46"->"59" 
  "47"->"52" 
  "47"->"53" 
  "48"->"52" 
  "48"->"54" 
  "49"->"53" 
  "50"->"53" 
  "50"->"52" 
  "50"->"54" 
  "50"->"55" 
  "50"->"58" 
  "50"->"59" 
  "51"->"55" 
  "51"->"54" 
  "51"->"52" 
  "51"->"56" 
  "51"->"57" 
  "51"->"58" 
  "51"->"59" 
}

[/dot]

The solution is to add the Diagrammer rank attribute to all nodes and set it differently based on the node type:

graph_rendering$nodes_df$rank <- unlist(lapply(concepts_df$type,
                                                    function(type) switch(type,
                                                                          "Target" = "1",
                                                                          "Direct threat" = "2",
                                                                          "Indirect factor" = "3",
                                                                          "Biophysical stressor" = "4",
                                                                          "Policy issue" = "5"
                                                    )))

This seems to make DiagrammeR:

  • Create subgraphs
  • Put the nodes with the same rank in the same subgraph
  • Set the Graphviz rank attribute to same in those subgraphs

It renders:

I’m writing seems since:

  1. The generate_dot() function is broken (at least the one available in RStudio) and doesn’t output anything useful at all.
  2. RTFM does not give you anything to indicate that the rank attribute even exists. You have to RTFSC to find a reference to it (The funny thing is that this reference is in the same generate_dot() function that seems broken in RStudio. I just don’t get it).