11

I have generated a graph:

library(DiagrammeR)
grViz("
digraph boxes_and_circles {

  # a 'graph' statement
  graph [layout = neato, overlap = true, fontsize = 10, outputorder = edgesfirst]

  # several 'node' statements
  node [shape = circle,
  fontname = Helvetica]
  A [pos = '1,1!']; 
  B [pos = '0,2!']; 
  C [pos = '1.5,3!']; 
  D [pos = '2.5,1!']; 
  E [pos = '4,1!']; 
  F [pos = '4,2!']; 
  G [pos = '5,1!']; 
  H [pos = '6,2!']; 
  I [pos = '1.5,-0.1!'];

  # several 'edge' statements
  A->B B->C
  D->E D->F E->F E->G F->G G->H F->H
  }
  ")

Which produces:

enter image description here

Now I would like to draw a box with dotted lines around the nodes A, B, and C.

How can I accomplish this in R? A key requirement of the solution is that it is reproducible, i.e. that I can run the script multiple times and get the same result.

Jaap
  • 81,064
  • 34
  • 182
  • 193
histelheim
  • 4,938
  • 6
  • 33
  • 63
  • 1
    do you want it to be a widget or just an image – rawr Aug 03 '15 at 15:50
  • @rawr: what is the difference? In the end I need to output an image to put in a Word document. – histelheim Aug 03 '15 at 15:53
  • @histelheim Maybe you could try to manually position a rectangle as a node, using `engine = "neato"` (instead of circo) and the `pos` attribute: `a [pos = '-3,4!', width = 3.5, height = 0.5, shape = rectangle, label = '']`. Take a look at http://www.graphviz.org/content/attrs#dpos http://stackoverflow.com/questions/5343899/ and http://stackoverflow.com/questions/29208525/ – Steven Beaupré Aug 03 '15 at 17:37
  • For your original question,you can add a box around the nodes using a `subgraph` - but im unsure if this is what you need, as the text in your bounty suggests you are wanting something for different types of figures (as in not specific to your questions problem?). – user20650 Sep 04 '15 at 00:25

3 Answers3

6

Here's another approach based on igraph. It is inspired by this igraph code sample.

I'm assuming that using igraph instead of DiagrammeR is an option - maybe that is not the case...

We leave positioning of the vertices to a standard layout algorithm and query it for the resulting vertex positions. These positions are then used to draw a dotted rectangle around an arbitrary set of "selected" vertices. No user interaction is needed.

We start with the graph topology.

library(igraph)

set.seed(42)

df <- data.frame(from = c('A', 'B', 'I', 'D', 'D', 'E', 'E', 'F', 'F', 'G'),
                 to = c('B', 'C', 'I', 'E', 'F', 'G', 'F', 'H', 'G', 'H'))

g <- graph.data.frame(df, directed = TRUE)

The size of the vertices and arrows in the graph can be set freely, according to taste.

vertexsize <- 50
arrowsize <- 0.2

We ask the Fruchterman-Reingold layout engine to calculate the coordinates of the vertices.

coords <- layout_with_fr(g)

Then plot the graph.

plot(g,
     layout = coords,
     vertex.size = vertexsize,
     edge.arrow.size = arrowsize,
     rescale = FALSE,
     xlim = range(coords[,1]),
     ylim = range(coords[,2]))

If we like to see what's going on, we can add coordinate axes and print the vertex coordinates:

axis(1)
axis(2)

V(g) # ordered vertex list
coords # coordinates of the vertices (in the same coordinate system as our dotted rectangle)

We now figure out the bounding box of the vertices that we want a rectangle around.

selectedVertices = c("A", "B", "C")
vertexIndices <- sapply(selectedVertices, FUN = function(x) { return(as.numeric(V(g)[x])) } )
llx <- min(coords[vertexIndices, 1])
lly <- min(coords[vertexIndices, 2])
urx <- max(coords[vertexIndices, 1])
ury <- max(coords[vertexIndices, 2])

Almost there. We already have the coordinates of the vertex centers in coords[], but we also need the size of the vertices in the coordinate system of plot(). From the plot.igraph source code we can see that the vertex.size option for plot() gets divided by 200 and then used as radius for drawing the vertex. We use a 50% bigger value as the margin around the bounding box of the vertex coordinates when drawing the dotted rectangle.

margin <- (vertexsize / 200) * 1.5
rect(llx - margin, lly - margin, urx + margin, ury + margin, lty = 'dotted')

This is the result we get:

enter image description here

WhiteViking
  • 3,146
  • 1
  • 19
  • 22
5

You could use @StevenBeaupre's solution for the widget, but there are a few packages for graphing networks using R's graphics. One is igraph if you are open to using other solutions.

This will make the graph

library('igraph')
set.seed(11)
g <- data.frame(from = c('A', 'B', 'I', 'D', 'D', 'E', 'E', 'F', 'F', 'G'),
                to = c('B', 'C', 'I', 'E', 'F', 'G', 'F', 'H', 'G', 'H'))
(gg <- graph.data.frame(g, directed = TRUE))
plot(gg, vertex.color = 'white')

And there are many ways to add a box to r graphics; here is one where you can click the plot to add the box without having to calculate anything

rekt <- function(...) {
  coords <- c(unlist(locator(1)), unlist(locator(1)))
  rect(coords[1], coords[2], coords[3], coords[4], ..., xpd = NA)
}

rekt(border = 'red', lty = 'dotted', lwd = 2)

I get this

enter image description here

rawr
  • 20,481
  • 4
  • 44
  • 78
  • Is there a way to make this reproducible, i.e. not needing the manual clicking? – histelheim Aug 31 '15 at 15:01
  • @histelheim I can't think of a nice way. `igraph` has some built-in layouts `?igraph::layout` that determine where everything goes instead of random. you could specify a matrix for the layout so that you know the coordinates beforehand – rawr Aug 31 '15 at 16:22
  • I can specify where the nodes go, I just can't specify where the rectangle goes... – histelheim Aug 31 '15 at 16:23
  • @histelheim can you make a new question with the code? – rawr Aug 31 '15 at 16:29
  • Isn't the essence of the question the same? Your solution is nice, it just doesn't conform to the (at that point unarticulated) assumption of reproducibility. – histelheim Aug 31 '15 at 16:30
2

An easy solution with DiagrammR would be to use dot rather than neato. You mostly lose the ability to manually position the nodes (attribute pos doesn't work anymore), but you gain the ability to use cluster and subgraph to draw lines around sets of nodes.

library(DiagrammeR)
grViz("
      digraph boxes_and_circles {

      # a 'graph' statement
      graph [ fontsize = 10,rankdir=LR]

      # several 'node' statements
      node [shape = circle,
      fontname = Helvetica]

      # several 'edge' statements

      subgraph cluster_1 {
            style=dotted
            A->B->C
        }

      D->E D->F E->F E->G F->G G->H F->H
      I
      }

      ")

enter image description here

scoa
  • 19,359
  • 5
  • 65
  • 80
  • if you use the `fdp` layout, you can use `pos` - but the positions are local to the cluster (i tihnk). You can probably, tweak the positions by adding invisible nodes / edges. – user20650 Sep 06 '15 at 00:06