Class: Graph

Inherits:
Object show all
Defined in:
lib/graph.rb

Overview

Graph models directed graphs and subgraphs and outputs in graphviz’s dot format.

Defined Under Namespace

Classes: Attribute, CompoundAttribute, Edge, Node, Thingy

Constant Summary collapse

VERSION =

:nodoc:

"2.11.1"
LIGHT_COLORS =

:stopdoc:

%w(gray lightblue lightcyan lightgray lightpink
lightslategray lightsteelblue white)
BOLD_COLORS =

WTF – can’t be %w() because of a bug in rcov

["black", "brown", "mediumblue", "blueviolet",
"orange", "magenta", "darkgreen", "maroon",
"violetred", "purple", "greenyellow", "deeppink",
"midnightblue", "firebrick", "darkturquoise",
"mediumspringgreen", "chartreuse", "navy",
"lightseagreen", "chocolate", "lawngreen", "green",
"indigo", "darkgoldenrod", "darkviolet", "red",
"springgreen", "saddlebrown", "mediumvioletred",
"goldenrod", "tomato", "cyan", "forestgreen",
"darkorchid", "crimson", "coral", "deepskyblue",
"seagreen", "peru", "turquoise", "orangered",
"dodgerblue", "sienna", "limegreen", "royalblue",
"darkorange", "blue"]
COLOR_SCHEME_MAX =

Defines the brewer color schemes and the maximum number of colors in each set.

{
  :accent   => 8,  :blues    => 9,  :brbg     => 11, :bugn     => 9,
  :dark2    => 8,  :gnbu     => 9,  :greens   => 9,  :greys    => 9,
  :oranges  => 9,  :orrd     => 9,  :paired   => 12, :pastel1  => 9,
  :pastel2  => 8,  :piyg     => 11, :prgn     => 11, :pubu     => 9,
  :pubugn   => 9,  :puor     => 11, :purd     => 9,  :purples  => 9,
  :rdbu     => 11, :rdgy     => 11, :rdylbu   => 11, :rdylgn   => 11,
  :reds     => 9,  :set1     => 9,  :set2     => 8,  :set3     => 12,
  :spectral => 11, :ylgn     => 9,  :ylgnbu   => 9,  :ylorbr   => 9,
  :ylorrd   => 9
}
SHAPES =
%w(Mcircle Mdiamond Msquare box box3d circle component
diamond doublecircle doubleoctagon egg ellipse folder
hexagon house invhouse invtrapezium invtriangle none
note octagon parallelogram pentagon plaintext point
polygon rect rectangle septagon square tab trapezium
triangle tripleoctagon)
STYLES =
%w(dashed dotted solid invis bold filled diagonals rounded)
ARROW_RE =
/(?:o?[lr]?(?:box|crow|diamond|dot|inv|none|normal|tee|vee)){1,4}/
ARROWS =
%w(box crow diamond dot inv none normal tee vee)

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name = nil, graph = nil, &block) ⇒ Graph

Creates a new graph object. Optional name and parent graph are available. Also takes an optional block for DSL-like use.



139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
# File 'lib/graph.rb', line 139

def initialize name = nil, graph = nil, &block
  @name = name
  @graph = graph
  graph << self if graph
  @nodes_order = []
  @nodes  = Hash.new { |h,k| @nodes_order << k; h[k] = Node.new self, k }
  @edges_order = []
  @edges  = Hash.new { |h,k|
    h[k] = Hash.new { |h2, k2|
      @edges_order << [k, k2]
      h2[k2] = Edge.new self, self[k], self[k2]
    }
  }
  @graph_attribs = []
  @node_attribs  = []
  @edge_attribs  = []
  @subgraphs     = []

  self.scheme = graph.scheme if graph
  node_attribs << scheme if scheme

  instance_eval(&block) if block
end

Instance Attribute Details

#edge_attribsObject (readonly)

Global attributes for edges in this graph.



104
105
106
# File 'lib/graph.rb', line 104

def edge_attribs
  @edge_attribs
end

#edgesObject (readonly)

The hash of hashes of edges in this graph. Use #[] or #node to create edges.



109
110
111
# File 'lib/graph.rb', line 109

def edges
  @edges
end

#edges_orderObject (readonly)

:nodoc:



133
134
135
# File 'lib/graph.rb', line 133

def edges_order
  @edges_order
end

#graphObject

A parent graph, if any. Only used for subgraphs.



93
94
95
# File 'lib/graph.rb', line 93

def graph
  @graph
end

#graph_attribsObject (readonly)

Global attributes for this graph.



114
115
116
# File 'lib/graph.rb', line 114

def graph_attribs
  @graph_attribs
end

#nameObject

The name of the graph. Optional for graphs and subgraphs. Prefix the name of a subgraph with “cluster” for subgraph that is boxed.



99
100
101
# File 'lib/graph.rb', line 99

def name
  @name
end

#node_attribsObject (readonly)

Global attributes for nodes in this graph.



119
120
121
# File 'lib/graph.rb', line 119

def node_attribs
  @node_attribs
end

#nodesObject (readonly)

The hash of nodes in this graph. Use #[] or #node to create nodes.



124
125
126
# File 'lib/graph.rb', line 124

def nodes
  @nodes
end

#nodes_orderObject (readonly)

TODO: remove if/when I drop 1.8 support.



132
133
134
# File 'lib/graph.rb', line 132

def nodes_order
  @nodes_order
end

#schemeObject

Shortcut method to create a new colorscheme Attribute instance. If passed n, name must match one of the brewer color scheme names and it will generate accessors for each fillcolor as well as push the colorscheme onto the node_attribs.



221
222
223
# File 'lib/graph.rb', line 221

def scheme
  @scheme
end

#subgraphsObject (readonly)

An array of subgraphs.



129
130
131
# File 'lib/graph.rb', line 129

def subgraphs
  @subgraphs
end

Class Method Details

.escape_label(s) ⇒ Object



309
310
311
312
313
314
315
316
# File 'lib/graph.rb', line 309

def self.escape_label s
  s = s.to_s.gsub(/\n/, '\n').gsub(/\"/, '\\\"')
  if s[0] == ?< and s[-1] == ?> then
    s
  else
    "\"#{s}\""
  end
end

Instance Method Details

#<<(subgraph) ⇒ Object

Push a subgraph into the current graph. Sets the subgraph’s graph to self.



166
167
168
169
# File 'lib/graph.rb', line 166

def << subgraph
  subgraphs << subgraph
  subgraph.graph = self
end

#[](name) ⇒ Object

Access a node by name



174
175
176
# File 'lib/graph.rb', line 174

def [] name
  nodes[name]
end

#arrowhead(shape) ⇒ Object

Shortcut method for creating an arrowhead attribute.

Raises:

  • (ArgumentError)


181
182
183
184
# File 'lib/graph.rb', line 181

def arrowhead shape
  raise ArgumentError, "Bad arrow shape: #{shape}" unless shape =~ ARROW_RE
  Attribute.new "arrowhead = #{shape}"
end

#arrowsize(size) ⇒ Object

Shortcut method for creating an arrowsize attribute.



197
198
199
# File 'lib/graph.rb', line 197

def arrowsize size
  Attribute.new "arrowsize = #{size}"
end

#arrowtail(shape) ⇒ Object

Shortcut method for creating an arrowtail attribute.

Raises:

  • (ArgumentError)


189
190
191
192
# File 'lib/graph.rb', line 189

def arrowtail shape
  raise ArgumentError, "Bad arrow shape: #{shape}" unless shape =~ ARROW_RE
  Attribute.new "arrowtail = #{shape}"
end

#bgcolor(n) ⇒ Object

Shortcut method to create a new fillcolor Attribute instance.



283
284
285
# File 'lib/graph.rb', line 283

def bgcolor n
  Attribute.new "bgcolor = %p" % [n]
end

#boxesObject

Shortcut method to set the global node attributes to use boxes.



204
205
206
# File 'lib/graph.rb', line 204

def boxes
  node_attribs << shape("box")
end

#cluster(name, &block) ⇒ Object

Shortcut method to create a clustered subgraph in the current graph. Use with the top-level digraph method in block form for a graph DSL.



397
398
399
# File 'lib/graph.rb', line 397

def cluster name, &block
  subgraph "cluster_#{name}", &block
end

#color(color) ⇒ Object

Shortcut method to create a new color Attribute instance.



211
212
213
# File 'lib/graph.rb', line 211

def color color
  Attribute.new "color = #{color}"
end

#colorscheme(name, n = nil) ⇒ Object

Shortcut method to create and set the graph to use a colorscheme.



226
227
228
229
230
231
232
233
# File 'lib/graph.rb', line 226

def colorscheme name, n = nil
  self.scheme = Attribute.new "colorscheme = %p" % ["#{name}#{n}"]
  max = COLOR_SCHEME_MAX[name.to_sym]

  node_attribs << scheme if max

  scheme
end

#compact!Object

Shortcut method to rotate and use boxes. Helps compact when there is lots of long text nodes.



353
354
355
356
# File 'lib/graph.rb', line 353

def compact!
  rotate
  boxes
end

#delete_node(node_name) ⇒ Object

Deletes a node from the graph



403
404
405
406
407
408
409
410
411
412
413
414
# File 'lib/graph.rb', line 403

def delete_node node_name
  nodes.delete node_name
  nodes_order.delete node_name

  edges_order.each do |(a, b)|
    edges[a].delete b if b == node_name
    edges.delete a if a == node_name
    edges.delete a if edges[a].empty?
  end

  edges_order.delete_if { |ary| ary.include? node_name }
end

#edge(*names) ⇒ Object

Define one or more edges.

edge "a", "b", "c", ...

is equivalent to:

edge "a", "b"
edge "b", "c"
...


252
253
254
255
256
257
258
# File 'lib/graph.rb', line 252

def edge(*names)
  last = nil
  names.each_cons(2) do |from, to|
    last = self[from][to]
  end
  last
end

#fillcolor(n) ⇒ Object

Shortcut method to create a new fillcolor Attribute instance.



276
277
278
# File 'lib/graph.rb', line 276

def fillcolor n
  Attribute.new "fillcolor = %p" % [n]
end

#font(name) ⇒ Object

Shortcut method to create a new font Attribute instance. You can pass in both the name and an optional font size.



291
292
293
# File 'lib/graph.rb', line 291

def font name
  Attribute.new "fontname = %p" % [name]
end

#fontcolor(name) ⇒ Object

Shortcut method to create a new fontcolor Attribute instance.



298
299
300
# File 'lib/graph.rb', line 298

def fontcolor name
  Attribute.new "fontcolor = %p" % [name]
end

#fontsize(size) ⇒ Object

Shortcut method to create a new fontsize Attribute instance.



305
306
307
# File 'lib/graph.rb', line 305

def fontsize size
  Attribute.new "fontsize = #{size}"
end

#invertObject

Creates a new Graph whose edges point the other direction.



263
264
265
266
267
268
269
270
271
# File 'lib/graph.rb', line 263

def invert
  result = self.class.new
  edges.each do |from, h|
    h.each do |to, edge|
      result[to][from]
    end
  end
  result
end

#label(name) ⇒ Object

Shortcut method to set the graph’s label. Usually used with subgraphs.



321
322
323
# File 'lib/graph.rb', line 321

def label name
  graph_attribs << "label = #{Graph.escape_label name}"
end

#node(name, label = nil) ⇒ Object

Access a node by name, supplying an optional label



328
329
330
331
332
# File 'lib/graph.rb', line 328

def node name, label = nil
  n = nodes[name]
  n.label label if label
  n
end

#orient(dir = "TB") ⇒ Object

Shortcut method to specify the orientation of the graph. Defaults to the graphviz default “TB”.



338
339
340
# File 'lib/graph.rb', line 338

def orient dir = "TB"
  graph_attribs << "rankdir = #{dir}"
end

#rotate(dir = "LR") ⇒ Object

Shortcut method to specify the orientation of the graph. Defaults to “LR”.



345
346
347
# File 'lib/graph.rb', line 345

def rotate dir = "LR"
  orient dir
end

#save(path, type = nil, cmd = "dot") ⇒ Object

Saves out both a dot file to path and an image for the specified type. Specify type as nil to skip exporting an image. Specify cmd as the command name like “neato” to use a command other than “dot”.



363
364
365
366
367
368
# File 'lib/graph.rb', line 363

def save path, type = nil, cmd = "dot"
  File.open "#{path}.dot", "w" do |f|
    f.puts self.to_s
  end
  system "#{cmd} -T#{type} #{path}.dot > #{path}.#{type}" if type
end

#shape(shape) ⇒ Object

Shortcut method to create a new shape Attribute instance.



373
374
375
# File 'lib/graph.rb', line 373

def shape shape
  Attribute.new "shape = %p" % [shape]
end

#style(name) ⇒ Object

Shortcut method to create a new style Attribute instance.



380
381
382
# File 'lib/graph.rb', line 380

def style name
  Attribute.new "style = %p" % [name]
end

#subgraph(name = nil, &block) ⇒ Object

Shortcut method to create a subgraph in the current graph. Use with the top-level digraph method in block form for a graph DSL.



388
389
390
# File 'lib/graph.rb', line 388

def subgraph name = nil, &block
  Graph.new name, self, &block
end

#to_sObject

Outputs a graphviz graph.



419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
# File 'lib/graph.rb', line 419

def to_s
  result = []

  type = graph ? "subgraph " : "digraph "
  type = "%s%p" % [type, name] if name and !name.empty?
  result << type
  result << "  {"

  graph_attribs.each do |line|
    result << "    #{line};"
  end

  unless node_attribs.empty? then
    result << "    node [ #{node_attribs.join(", ")} ];"
  end

  unless edge_attribs.empty? then
    result << "    edge [ #{edge_attribs.join(", ")} ];"
  end

  subgraphs.each do |line|
    result << "    #{line};"
  end

  nodes_order.each do |name|
    node = nodes[name]
    result << "    #{node};" if graph or node.attributes? or node.orphan?
  end

  edges_order.uniq.each do |(from, to)|
    edge = edges[from][to]
    result << "    #{edge};"
  end

  result << "  }"
  result.join "\n"
end