ruby-graphviz_c 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gemrc +0 -0
- data/.gitignore +9 -0
- data/.travis.yml +7 -0
- data/AUTHORS.rdoc +33 -0
- data/CHANGELOG.rdoc +287 -0
- data/COPYING.rdoc +133 -0
- data/Gemfile +4 -0
- data/README.rdoc +206 -0
- data/Rakefile +71 -0
- data/bin/dot2ruby +91 -0
- data/bin/gem2gv +165 -0
- data/bin/git2gv +167 -0
- data/bin/ruby2gv +234 -0
- data/bin/xml2gv +96 -0
- data/examples/dot/JSP.dot +52 -0
- data/examples/dot/balanced.dot +36 -0
- data/examples/dot/cluster.dot +30 -0
- data/examples/dot/dotgraph.dot +28 -0
- data/examples/dot/fsm.dot +20 -0
- data/examples/dot/genetic.dot +118 -0
- data/examples/dot/hello.dot +1 -0
- data/examples/dot/hello_test.rb +33 -0
- data/examples/dot/lion_share.dot +103 -0
- data/examples/dot/prof.dot +150 -0
- data/examples/dot/psg.dot +28 -0
- data/examples/dot/rank.dot +6 -0
- data/examples/dot/sdh.dot +284 -0
- data/examples/dot/siblings.dot +492 -0
- data/examples/dot/so-sample001.gv +30 -0
- data/examples/dot/so-sample002.gv +33 -0
- data/examples/dot/so-sample003.gv +45 -0
- data/examples/dot/test.dot +17 -0
- data/examples/dot/test_parse.rb +13 -0
- data/examples/dot/this_crach_with_dot_2.20.dot +24 -0
- data/examples/dot/unix.dot +104 -0
- data/examples/graphml/attributes.ext.graphml +12 -0
- data/examples/graphml/attributes.graphml +40 -0
- data/examples/graphml/cluster.graphml +75 -0
- data/examples/graphml/failed_graph.graphml +461 -0
- data/examples/graphml/hyper.graphml +29 -0
- data/examples/graphml/nested.graphml +54 -0
- data/examples/graphml/port.graphml +32 -0
- data/examples/graphml/simple.graphml +30 -0
- data/examples/hello.png +0 -0
- data/examples/rgv/rgv.ps +125 -0
- data/examples/rgv/test_rgv.rb +12 -0
- data/examples/sample01.rb +32 -0
- data/examples/sample02.rb +42 -0
- data/examples/sample03.rb +31 -0
- data/examples/sample04.rb +22 -0
- data/examples/sample05.rb +32 -0
- data/examples/sample06.rb +46 -0
- data/examples/sample07.rb +23 -0
- data/examples/sample08.rb +34 -0
- data/examples/sample09.rb +50 -0
- data/examples/sample10.rb +50 -0
- data/examples/sample11.rb +42 -0
- data/examples/sample12.rb +55 -0
- data/examples/sample13.rb +48 -0
- data/examples/sample14.rb +44 -0
- data/examples/sample15.rb +25 -0
- data/examples/sample16.rb +8 -0
- data/examples/sample17.rb +92 -0
- data/examples/sample18.rb +24 -0
- data/examples/sample19.rb +59 -0
- data/examples/sample20.rb +47 -0
- data/examples/sample21.rb +12 -0
- data/examples/sample22.rb +10 -0
- data/examples/sample23.rb +11 -0
- data/examples/sample24.rb +11 -0
- data/examples/sample25.rb +11 -0
- data/examples/sample26.rb +8 -0
- data/examples/sample27.rb +8 -0
- data/examples/sample28.rb +12 -0
- data/examples/sample29.rb +8 -0
- data/examples/sample30.rb +12 -0
- data/examples/sample31.rb +10 -0
- data/examples/sample32.rb +14 -0
- data/examples/sample33.rb +43 -0
- data/examples/sample34.rb +29 -0
- data/examples/sample35.rb +43 -0
- data/examples/sample36.rb +35 -0
- data/examples/sample37.rb +87 -0
- data/examples/sample38.rb +12 -0
- data/examples/sample39.rb +11 -0
- data/examples/sample40.rb +17 -0
- data/examples/sample41.rb +8 -0
- data/examples/sample42.rb +35 -0
- data/examples/sample43.rb +26 -0
- data/examples/sample44.rb +97 -0
- data/examples/sample45.rb +24 -0
- data/examples/sample46.rb +43 -0
- data/examples/sample47.rb +7 -0
- data/examples/sample48.rb +62 -0
- data/examples/sample49.rb +10 -0
- data/examples/sample50.rb +215 -0
- data/examples/sample51.rb +37 -0
- data/examples/sample52.rb +62 -0
- data/examples/sample53.rb +26 -0
- data/examples/sample54.rb +26 -0
- data/examples/sample55.rb +9 -0
- data/examples/sample56.rb +10 -0
- data/examples/sample57.rb +8 -0
- data/examples/sample58.rb +33 -0
- data/examples/sample59.rb +14 -0
- data/examples/sample60.rb +12 -0
- data/examples/sample61.rb +12 -0
- data/examples/sample62.rb +24 -0
- data/examples/sample63.rb +32 -0
- data/examples/sample64.rb +31 -0
- data/examples/sample65.rb +9 -0
- data/examples/sample66.rb +4 -0
- data/examples/sample67.rb +10 -0
- data/examples/sample68.rb +27 -0
- data/examples/sample69.rb +23 -0
- data/examples/sample70.rb +9 -0
- data/examples/sample99.rb +70 -0
- data/examples/sdlshapes/README +2 -0
- data/examples/sdlshapes/sdl.ps +655 -0
- data/examples/sdlshapes/sdlshapes.dot +78 -0
- data/examples/test.xml +26 -0
- data/examples/theory/pert.rb +47 -0
- data/examples/theory/tests.rb +87 -0
- data/lib/ext/gvpr/dot2ruby.g +185 -0
- data/lib/graphviz/attrs.rb +73 -0
- data/lib/graphviz/constants.rb +294 -0
- data/lib/graphviz/core_ext.rb +64 -0
- data/lib/graphviz/dot2ruby.rb +59 -0
- data/lib/graphviz/dot_script.rb +109 -0
- data/lib/graphviz/dsl.rb +67 -0
- data/lib/graphviz/edge.rb +197 -0
- data/lib/graphviz/elements.rb +39 -0
- data/lib/graphviz/ext.rb +17 -0
- data/lib/graphviz/family_tree/couple.rb +63 -0
- data/lib/graphviz/family_tree/generation.rb +39 -0
- data/lib/graphviz/family_tree/person.rb +120 -0
- data/lib/graphviz/family_tree/sibling.rb +13 -0
- data/lib/graphviz/family_tree.rb +118 -0
- data/lib/graphviz/graphml.rb +268 -0
- data/lib/graphviz/math/matrix.rb +221 -0
- data/lib/graphviz/node.rb +160 -0
- data/lib/graphviz/nothugly/nothugly.xsl +321 -0
- data/lib/graphviz/nothugly.rb +63 -0
- data/lib/graphviz/theory.rb +321 -0
- data/lib/graphviz/types/arrow_type.rb +32 -0
- data/lib/graphviz/types/color.rb +58 -0
- data/lib/graphviz/types/color_list.rb +24 -0
- data/lib/graphviz/types/esc_string.rb +20 -0
- data/lib/graphviz/types/gv_bool.rb +49 -0
- data/lib/graphviz/types/gv_double.rb +32 -0
- data/lib/graphviz/types/html_string.rb +18 -0
- data/lib/graphviz/types/lbl_string.rb +22 -0
- data/lib/graphviz/types/rect.rb +35 -0
- data/lib/graphviz/types/spline_type.rb +77 -0
- data/lib/graphviz/types.rb +22 -0
- data/lib/graphviz/utils/colors.rb +1018 -0
- data/lib/graphviz/utils.rb +70 -0
- data/lib/graphviz/xml.rb +119 -0
- data/lib/graphviz.rb +967 -0
- data/lib/ruby-graphviz.rb +1 -0
- data/man/dot2ruby.1 +66 -0
- data/man/dot2ruby.1.ronn +55 -0
- data/man/gem2gv.1 +60 -0
- data/man/gem2gv.1.ronn +47 -0
- data/man/git2gv.1 +48 -0
- data/man/git2gv.1.ronn +40 -0
- data/man/ruby2gv.1 +60 -0
- data/man/ruby2gv.1.ronn +47 -0
- data/man/xml2gv.1 +48 -0
- data/man/xml2gv.1.ronn +39 -0
- data/ruby-graphviz.gemspec +47 -0
- data/setup.rb +1585 -0
- data/test/helper.rb +13 -0
- data/test/support.rb +95 -0
- data/test/test_dot_script.rb +47 -0
- data/test/test_examples.rb +151 -0
- data/test/test_graph.rb +115 -0
- data/test/test_search.rb +29 -0
- data/test/test_subgraph.rb +27 -0
- data/test/test_theory.rb +98 -0
- data/test/test_types.rb +65 -0
- data/test/test_utils_colors.rb +52 -0
- metadata +301 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
require 'rbconfig'
|
|
2
|
+
|
|
3
|
+
class GraphViz
|
|
4
|
+
module Utils
|
|
5
|
+
# Since this code is an adaptation of Launchy::Application#find_executable
|
|
6
|
+
# (http://copiousfreetime.rubyforge.org/launchy/Launchy/Application.html)
|
|
7
|
+
# it follow is licence :
|
|
8
|
+
#
|
|
9
|
+
# Permission to use, copy, modify, and/or distribute this software for any
|
|
10
|
+
# purpose with or without fee is hereby granted, provided that the above
|
|
11
|
+
# copyright notice and this permission notice appear in all copies.
|
|
12
|
+
#
|
|
13
|
+
# THE SOFTWARE IS PROVIDED AS IS AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
14
|
+
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
15
|
+
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
|
16
|
+
# SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
17
|
+
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
|
|
18
|
+
# OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
|
19
|
+
# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
20
|
+
def find_executable(bin, custom_paths) #:nodoc:
|
|
21
|
+
system_path = ENV['PATH']
|
|
22
|
+
user_given_path = Array(custom_paths).join(File::PATH_SEPARATOR)
|
|
23
|
+
search_path = system_path + File::PATH_SEPARATOR + user_given_path
|
|
24
|
+
|
|
25
|
+
search_path.split(File::PATH_SEPARATOR).each do |path|
|
|
26
|
+
file_path = File.join(path,bin)
|
|
27
|
+
return file_path if File.executable?(file_path) and File.file?(file_path)
|
|
28
|
+
|
|
29
|
+
if RbConfig::CONFIG['host_os'] =~ /mswin|mingw/ # WAS: elsif RUBY_PLATFORM =~ /mswin|mingw/
|
|
30
|
+
found_ext = (ENV['PATHEXT'] || '.exe;.bat;.com').split(";").find {|ext| File.executable?(file_path + ext) }
|
|
31
|
+
return file_path + found_ext if found_ext
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
return nil
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def output_and_errors_from_command(cmd) #:nodoc:
|
|
38
|
+
unless defined? Open3
|
|
39
|
+
begin
|
|
40
|
+
require 'open3'
|
|
41
|
+
require 'win32/open3'
|
|
42
|
+
rescue LoadError
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
begin
|
|
46
|
+
Open3.popen3( *cmd ) do |stdin, stdout, stderr|
|
|
47
|
+
stdin.close
|
|
48
|
+
stdout.binmode
|
|
49
|
+
[stdout.read, stderr.read]
|
|
50
|
+
end
|
|
51
|
+
rescue NotImplementedError, NoMethodError
|
|
52
|
+
IO.popen( *cmd ) do |stdout|
|
|
53
|
+
stdout.binmode
|
|
54
|
+
[stdout.read, nil]
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def output_from_command(cmd) #:nodoc:
|
|
60
|
+
output, errors = output_and_errors_from_command(cmd)
|
|
61
|
+
if errors.nil? || errors.strip.empty?
|
|
62
|
+
output
|
|
63
|
+
else
|
|
64
|
+
raise "Error from #{cmd}:\n#{errors}"
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
data/lib/graphviz/xml.rb
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# Copyright (C) 2004 - 2012 Gregoire Lejeune <gregoire.lejeune@free.fr>
|
|
2
|
+
#
|
|
3
|
+
# This program is free software; you can redistribute it and/or modify
|
|
4
|
+
# it under the terms of the GNU General Public License as published by
|
|
5
|
+
# the Free Software Foundation; either version 2 of the License, or
|
|
6
|
+
# (at your option) any later version.
|
|
7
|
+
#
|
|
8
|
+
# This program is distributed in the hope that it will be useful,
|
|
9
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
10
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
11
|
+
# GNU General Public License for more details.
|
|
12
|
+
#
|
|
13
|
+
# You should have received a copy of the GNU General Public License
|
|
14
|
+
# along with this program; if not, write to the Free Software
|
|
15
|
+
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
16
|
+
|
|
17
|
+
require 'graphviz'
|
|
18
|
+
require 'rexml/document'
|
|
19
|
+
|
|
20
|
+
class GraphViz
|
|
21
|
+
class XML
|
|
22
|
+
|
|
23
|
+
# The GraphViz object
|
|
24
|
+
attr_accessor :graph
|
|
25
|
+
|
|
26
|
+
#
|
|
27
|
+
# Generate the graph
|
|
28
|
+
#
|
|
29
|
+
# THIS METHOD IS DEPRECATED, PLEASE USE GraphViz::XML.graph.output
|
|
30
|
+
#
|
|
31
|
+
def output( *options )
|
|
32
|
+
warn "GraphViz::XML.output is deprecated, use GraphViz::XML.graph.output"
|
|
33
|
+
@graph.output( *options )
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
#
|
|
39
|
+
# Create a graph from a XML file
|
|
40
|
+
#
|
|
41
|
+
# In:
|
|
42
|
+
# * xml_file : XML File
|
|
43
|
+
# * *options : Graph options:
|
|
44
|
+
# * :text : show text nodes (default true)
|
|
45
|
+
# * :attrs : show XML attributes (default true)
|
|
46
|
+
#
|
|
47
|
+
def initialize( xml_file, *options )
|
|
48
|
+
@node_name = "00000"
|
|
49
|
+
@show_text = true
|
|
50
|
+
@show_attributes = true
|
|
51
|
+
|
|
52
|
+
if options and options[0]
|
|
53
|
+
options[0].each do |xKey, xValue|
|
|
54
|
+
case xKey.to_s
|
|
55
|
+
when "text"
|
|
56
|
+
@show_text = xValue
|
|
57
|
+
options[0].delete( xKey )
|
|
58
|
+
when "attrs"
|
|
59
|
+
@show_attributes = xValue
|
|
60
|
+
options[0].delete( xKey )
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
@rexml_document = REXML::Document::new( File::new( xml_file ) )
|
|
66
|
+
@graph = GraphViz::new( "XML", *options )
|
|
67
|
+
parse_xml_node( @rexml_document.root() )
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def parse_xml_node( xml_node ) #:nodoc:
|
|
71
|
+
local_node_name = @node_name.clone
|
|
72
|
+
@node_name.succ!
|
|
73
|
+
|
|
74
|
+
label = xml_node.name
|
|
75
|
+
if xml_node.has_attributes? and @show_attributes
|
|
76
|
+
label = "{ " + xml_node.name
|
|
77
|
+
|
|
78
|
+
xml_node.attributes.each do |xName, xValue|
|
|
79
|
+
label << "| { #{xName} | #{xValue} } "
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
label << "}"
|
|
83
|
+
end
|
|
84
|
+
@graph.add_nodes( local_node_name, "label" => label, "color" => "blue", "shape" => "record" )
|
|
85
|
+
|
|
86
|
+
## Act: Search and add Text nodes
|
|
87
|
+
if xml_node.has_text? and @show_text
|
|
88
|
+
text_node_name = local_node_name.clone
|
|
89
|
+
text_node_name << "111"
|
|
90
|
+
|
|
91
|
+
xText = ""
|
|
92
|
+
xSep = ""
|
|
93
|
+
xml_node.texts().each do |l|
|
|
94
|
+
x = l.value.chomp.strip
|
|
95
|
+
if x.length > 0
|
|
96
|
+
xText << xSep << x
|
|
97
|
+
xSep = "\n"
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
if xText.length > 0
|
|
102
|
+
@graph.add_nodes( text_node_name, "label" => xText, "color" => "black", "shape" => "ellipse" )
|
|
103
|
+
@graph.add_edges( local_node_name, text_node_name )
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
## Act: Search and add attributes
|
|
108
|
+
## TODO
|
|
109
|
+
|
|
110
|
+
xml_node.each_element( ) do |xml_child_node|
|
|
111
|
+
child_node_name = parse_xml_node( xml_child_node )
|
|
112
|
+
@graph.add_edges( local_node_name, child_node_name )
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
return( local_node_name )
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
end
|
|
119
|
+
end
|
data/lib/graphviz.rb
ADDED
|
@@ -0,0 +1,967 @@
|
|
|
1
|
+
# Copyright (C) 2003 - 2012 Gregoire Lejeune <gregoire.lejeune@free.fr>
|
|
2
|
+
#
|
|
3
|
+
# This program is free software; you can redistribute it and/or modify
|
|
4
|
+
# it under the terms of the GNU General Public License as published by
|
|
5
|
+
# the Free Software Foundation; either version 2 of the License, or
|
|
6
|
+
# (at your option) any later version.
|
|
7
|
+
#
|
|
8
|
+
# This program is distributed in the hope that it will be useful,
|
|
9
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
10
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
11
|
+
# GNU General Public License for more details.
|
|
12
|
+
#
|
|
13
|
+
# You should have received a copy of the GNU General Public License
|
|
14
|
+
# along with this program; if not, write to the Free Software
|
|
15
|
+
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
IS_JRUBY = (defined?( JRUBY_VERSION ) != nil)
|
|
19
|
+
IS_CYGWIN = ((RUBY_PLATFORM =~ /cygwin/) != nil)
|
|
20
|
+
|
|
21
|
+
require 'tempfile'
|
|
22
|
+
|
|
23
|
+
require 'graphviz/utils'
|
|
24
|
+
require 'graphviz/node'
|
|
25
|
+
require 'graphviz/edge'
|
|
26
|
+
require 'graphviz/attrs'
|
|
27
|
+
require 'graphviz/constants'
|
|
28
|
+
require 'graphviz/elements'
|
|
29
|
+
require 'graphviz/dot_script'
|
|
30
|
+
|
|
31
|
+
require 'graphviz/dot2ruby'
|
|
32
|
+
require 'graphviz/types'
|
|
33
|
+
require 'graphviz/core_ext'
|
|
34
|
+
|
|
35
|
+
if /^1.8/.match RUBY_VERSION
|
|
36
|
+
$KCODE = "UTF8"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
class GraphViz
|
|
40
|
+
include GraphViz::Constants
|
|
41
|
+
include GraphViz::Utils
|
|
42
|
+
|
|
43
|
+
public
|
|
44
|
+
|
|
45
|
+
## Var: Output format (dot, png, jpeg, ...)
|
|
46
|
+
@@format = nil #"canon"
|
|
47
|
+
@format = nil
|
|
48
|
+
## Var: Output file name
|
|
49
|
+
@filename = nil
|
|
50
|
+
## Var: Output format and file
|
|
51
|
+
@output = nil
|
|
52
|
+
## Var: program to use (dot|twopi)
|
|
53
|
+
@@prog = "dot"
|
|
54
|
+
@prog = nil
|
|
55
|
+
## Var: program path
|
|
56
|
+
@@path = []
|
|
57
|
+
@path = nil
|
|
58
|
+
## Var: Error level
|
|
59
|
+
@@errors = 1
|
|
60
|
+
@errors = nil
|
|
61
|
+
## Var: External libraries
|
|
62
|
+
@@extlibs = []
|
|
63
|
+
@extlibs = nil
|
|
64
|
+
|
|
65
|
+
## Var: Graph name
|
|
66
|
+
@name = nil
|
|
67
|
+
|
|
68
|
+
## Var: defined attributes
|
|
69
|
+
@graph = nil
|
|
70
|
+
@node = nil
|
|
71
|
+
@edge = nil
|
|
72
|
+
|
|
73
|
+
# This accessor allow you to set global graph attributes
|
|
74
|
+
attr_accessor :graph
|
|
75
|
+
alias_method :graph_attrs, :graph
|
|
76
|
+
|
|
77
|
+
# This accessor allow you to set global nodes attributes
|
|
78
|
+
attr_accessor :node
|
|
79
|
+
alias_method :node_attrs, :node
|
|
80
|
+
|
|
81
|
+
# This accessor allow you to set global edges attributes
|
|
82
|
+
attr_accessor :edge
|
|
83
|
+
alias_method :edge_attrs, :edge
|
|
84
|
+
|
|
85
|
+
@elements_order = nil
|
|
86
|
+
|
|
87
|
+
def add_node( xNodeName, hOpts = {} )
|
|
88
|
+
if xNodeName.kind_of? Array
|
|
89
|
+
raise ArgumentError, "use `add_nodes' to add several nodes at the same time"
|
|
90
|
+
end
|
|
91
|
+
return add_nodes(xNodeName, hOpts)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Create a new node
|
|
95
|
+
#
|
|
96
|
+
# In:
|
|
97
|
+
# * xNodeName : Name of the new node
|
|
98
|
+
# * hOpts : Node attributes
|
|
99
|
+
#
|
|
100
|
+
# Return the GraphViz::Node object created
|
|
101
|
+
def add_nodes(node_name, options = {})
|
|
102
|
+
if node_name.kind_of? Array
|
|
103
|
+
node_name.each { |n| add_nodes(n, options.clone) }
|
|
104
|
+
else
|
|
105
|
+
node = @hoNodes[node_name]
|
|
106
|
+
|
|
107
|
+
if node.nil?
|
|
108
|
+
@hoNodes[node_name] = GraphViz::Node::new( node_name, self )
|
|
109
|
+
@hoNodes[node_name].index = @elements_order.size_of( "node" )
|
|
110
|
+
|
|
111
|
+
unless options.keys.include?(:label) or options.keys.include?("label")
|
|
112
|
+
options[:label] = node_name
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
@elements_order.push( {
|
|
116
|
+
"type" => "node",
|
|
117
|
+
"name" => node_name,
|
|
118
|
+
"value" => @hoNodes[node_name]
|
|
119
|
+
} )
|
|
120
|
+
|
|
121
|
+
node = @hoNodes[node_name]
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
options.each do |xKey, xValue|
|
|
125
|
+
@hoNodes[node_name][xKey.to_s] = xValue
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
return node
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Return the node object for the given name (or nil) in the current graph
|
|
133
|
+
def get_node( xNodeName, &block )
|
|
134
|
+
node = @hoNodes[xNodeName] || nil
|
|
135
|
+
|
|
136
|
+
yield( node ) if( block and node )
|
|
137
|
+
|
|
138
|
+
return node
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Returns the first node found in the entire graph, starting from the root graph
|
|
142
|
+
def find_node(name)
|
|
143
|
+
root = root_graph
|
|
144
|
+
return root.search_node(name)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Return the first node found in the current graph, and it subgraphs
|
|
148
|
+
def search_node(name)
|
|
149
|
+
n = get_node(name)
|
|
150
|
+
return n unless n.nil?
|
|
151
|
+
each_graph { |_, g|
|
|
152
|
+
n = g.search_node(name)
|
|
153
|
+
return n unless n.nil?
|
|
154
|
+
}
|
|
155
|
+
return nil
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
#
|
|
159
|
+
# Return the node object for the given index
|
|
160
|
+
#
|
|
161
|
+
def get_node_at_index( index )
|
|
162
|
+
element = @elements_order[index, "node"]
|
|
163
|
+
(element.nil?) ? nil : element["value"]
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
#
|
|
167
|
+
# Allow you to traverse nodes
|
|
168
|
+
#
|
|
169
|
+
def each_node( &block )
|
|
170
|
+
if block_given?
|
|
171
|
+
@hoNodes.each do |name, node|
|
|
172
|
+
yield( name, node )
|
|
173
|
+
end
|
|
174
|
+
else
|
|
175
|
+
return( @hoNodes )
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# Get the number of nodes
|
|
180
|
+
def node_count
|
|
181
|
+
@hoNodes.size
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def add_edge( oNodeOne, oNodeTwo, hOpts = {} )
|
|
185
|
+
if oNodeTwo.kind_of? Array or oNodeOne.kind_of? Array
|
|
186
|
+
raise ArgumentError, "use `add_edges' to add several edges at the same time"
|
|
187
|
+
end
|
|
188
|
+
add_edges(oNodeOne, oNodeTwo, hOpts)
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# Create a new edge
|
|
192
|
+
#
|
|
193
|
+
# In:
|
|
194
|
+
# * node_one : First node (or node list)
|
|
195
|
+
# * node_two : Second Node (or node list)
|
|
196
|
+
# * options : Edge attributes
|
|
197
|
+
def add_edges( node_one, node_two, options = {} )
|
|
198
|
+
|
|
199
|
+
if( node_one.class == Array )
|
|
200
|
+
node_one.each do |no|
|
|
201
|
+
add_edges( no, node_two, options )
|
|
202
|
+
end
|
|
203
|
+
else
|
|
204
|
+
if( node_two.class == Array )
|
|
205
|
+
node_two.each do |nt|
|
|
206
|
+
add_edges( node_one, nt, options )
|
|
207
|
+
end
|
|
208
|
+
else
|
|
209
|
+
edge = GraphViz::Edge::new( node_one, node_two, self )
|
|
210
|
+
edge.index = @elements_order.size_of( "edge" )
|
|
211
|
+
|
|
212
|
+
options.each do |xKey, xValue|
|
|
213
|
+
edge[xKey.to_s] = xValue
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
@elements_order.push( {
|
|
217
|
+
"type" => "edge",
|
|
218
|
+
"value" => edge
|
|
219
|
+
} )
|
|
220
|
+
@loEdges.push( edge )
|
|
221
|
+
|
|
222
|
+
return( edge )
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
#
|
|
228
|
+
# Allow you to traverse edges
|
|
229
|
+
#
|
|
230
|
+
def each_edge( &block )
|
|
231
|
+
if block_given?
|
|
232
|
+
@loEdges.each do |edge|
|
|
233
|
+
yield(edge)
|
|
234
|
+
end
|
|
235
|
+
else
|
|
236
|
+
return @loEdges
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
#
|
|
241
|
+
# Get the number of edges
|
|
242
|
+
#
|
|
243
|
+
def edge_count
|
|
244
|
+
@loEdges.size
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
#
|
|
248
|
+
# Return the edge object for the given index
|
|
249
|
+
#
|
|
250
|
+
def get_edge_at_index( index )
|
|
251
|
+
element = @elements_order[index, "edge"]
|
|
252
|
+
(element.nil?) ? nil : element["value"]
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
#
|
|
256
|
+
# Create a new graph
|
|
257
|
+
#
|
|
258
|
+
# In:
|
|
259
|
+
# * xGraphName : Graph name
|
|
260
|
+
# * hOpts : Graph attributes
|
|
261
|
+
#
|
|
262
|
+
def add_graph( xGraphName = nil, hOpts = {}, &block )
|
|
263
|
+
if xGraphName.kind_of?(GraphViz)
|
|
264
|
+
xGraphID = xGraphName.id
|
|
265
|
+
@hoGraphs[xGraphID] = xGraphName.clone
|
|
266
|
+
@hoGraphs[xGraphID].type = @oGraphType
|
|
267
|
+
@hoGraphs[xGraphID].pg = self
|
|
268
|
+
xGraphName = xGraphID
|
|
269
|
+
else
|
|
270
|
+
if xGraphName.kind_of?(Hash)
|
|
271
|
+
hOpts = xGraphName
|
|
272
|
+
xGraphName = nil
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
if xGraphName.nil?
|
|
276
|
+
xGraphID = String.random(11)
|
|
277
|
+
xGraphName = ""
|
|
278
|
+
else
|
|
279
|
+
xGraphID = xGraphName
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
@hoGraphs[xGraphID] = GraphViz::new( xGraphName, {:parent => self, :type => @oGraphType}, &block )
|
|
283
|
+
|
|
284
|
+
hOpts.each do |xKey, xValue|
|
|
285
|
+
@hoGraphs[xGraphID][xKey.to_s] = xValue
|
|
286
|
+
end
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
@elements_order.push( {
|
|
290
|
+
"type" => "graph",
|
|
291
|
+
"name" => xGraphName,
|
|
292
|
+
"value" => @hoGraphs[xGraphID]
|
|
293
|
+
} )
|
|
294
|
+
|
|
295
|
+
return( @hoGraphs[xGraphID] )
|
|
296
|
+
end
|
|
297
|
+
alias :subgraph :add_graph
|
|
298
|
+
#
|
|
299
|
+
# Return the graph object for the given name (or nil)
|
|
300
|
+
#
|
|
301
|
+
def get_graph( xGraphName, &block )
|
|
302
|
+
graph = @hoGraphs[xGraphName] || nil
|
|
303
|
+
|
|
304
|
+
yield( graph ) if( block and graph )
|
|
305
|
+
|
|
306
|
+
return graph
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
#
|
|
310
|
+
# Allow you to traverse graphs
|
|
311
|
+
#
|
|
312
|
+
def each_graph( &block )
|
|
313
|
+
if block_given?
|
|
314
|
+
@hoGraphs.each do |name, graph|
|
|
315
|
+
yield( name, graph )
|
|
316
|
+
end
|
|
317
|
+
else
|
|
318
|
+
return @hoGraphs
|
|
319
|
+
end
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
#
|
|
323
|
+
# Add nodes and edges defined by a Hash
|
|
324
|
+
#
|
|
325
|
+
def add(h)
|
|
326
|
+
if h.kind_of? Hash
|
|
327
|
+
h.each do |node, data|
|
|
328
|
+
add_hash_edge(node, data)
|
|
329
|
+
end
|
|
330
|
+
end
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
#
|
|
334
|
+
# Return the graph type (graph digraph)
|
|
335
|
+
#
|
|
336
|
+
def type
|
|
337
|
+
@oGraphType
|
|
338
|
+
end
|
|
339
|
+
def type=(x) #:nodoc:
|
|
340
|
+
@oGraphType = x
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
#
|
|
344
|
+
# Get the number of graphs
|
|
345
|
+
#
|
|
346
|
+
def graph_count
|
|
347
|
+
@hoGraphs.size
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
def method_missing( idName, *args, &block ) #:nodoc:
|
|
351
|
+
xName = idName.id2name
|
|
352
|
+
|
|
353
|
+
rCod = nil
|
|
354
|
+
|
|
355
|
+
if block
|
|
356
|
+
# Creating a cluster named '#{xName}'
|
|
357
|
+
rCod = add_graph( xName, args[0]||{} )
|
|
358
|
+
yield( rCod )
|
|
359
|
+
else
|
|
360
|
+
# Create a node named '#{xName}' or search for a node, edge or cluster
|
|
361
|
+
if @hoNodes.keys.include?( xName )
|
|
362
|
+
if( args[0] )
|
|
363
|
+
return { xName => args[0] }
|
|
364
|
+
else
|
|
365
|
+
return( @hoNodes[xName] )
|
|
366
|
+
end
|
|
367
|
+
end
|
|
368
|
+
return( @hoGraphs[xName] ) if @hoGraphs.keys.include?( xName )
|
|
369
|
+
|
|
370
|
+
rCod = add_nodes( xName, args[0]||{} )
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
return rCod
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
#
|
|
377
|
+
# Set value +xValue+ to the graph attribute +xAttrName+
|
|
378
|
+
#
|
|
379
|
+
def []=( xAttrName, xValue )
|
|
380
|
+
xValue = xValue.to_s if xValue.class == Symbol
|
|
381
|
+
@graph[xAttrName] = xValue
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
#
|
|
385
|
+
# Get the value of the graph attribute +xAttrName+
|
|
386
|
+
#
|
|
387
|
+
def []( xAttrName )
|
|
388
|
+
if Hash === xAttrName
|
|
389
|
+
xAttrName.each do |key, value|
|
|
390
|
+
self[key] = value
|
|
391
|
+
end
|
|
392
|
+
else
|
|
393
|
+
attr = @graph[xAttrName]
|
|
394
|
+
if attr.nil?
|
|
395
|
+
return nil
|
|
396
|
+
else
|
|
397
|
+
return( @graph[xAttrName].clone )
|
|
398
|
+
end
|
|
399
|
+
end
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
#
|
|
403
|
+
# Calls block once for each attribute of the graph, passing the name and value to the
|
|
404
|
+
# block as a two-element array.
|
|
405
|
+
#
|
|
406
|
+
def each_attribute(&b)
|
|
407
|
+
@graph.each do |k,v|
|
|
408
|
+
yield(k,v)
|
|
409
|
+
end
|
|
410
|
+
end
|
|
411
|
+
def each_attribut(&b)
|
|
412
|
+
warn "`GraphViz#each_attribut` is deprecated, please use `GraphViz#each_attribute`"
|
|
413
|
+
each_attribute(&b)
|
|
414
|
+
end
|
|
415
|
+
|
|
416
|
+
# Create a new graph from the current subgraph
|
|
417
|
+
def to_graph
|
|
418
|
+
graph = self.clone
|
|
419
|
+
graph.pg = nil
|
|
420
|
+
return graph
|
|
421
|
+
end
|
|
422
|
+
|
|
423
|
+
#
|
|
424
|
+
# Generate the graph
|
|
425
|
+
#
|
|
426
|
+
# Options :
|
|
427
|
+
# * :output : Output format (GraphViz::Constants::FORMATS)
|
|
428
|
+
# * :file : Output file name
|
|
429
|
+
# * :use : Program to use (GraphViz::Constants::PROGRAMS)
|
|
430
|
+
# * :path : Program PATH
|
|
431
|
+
# * :<format> => <file> : <file> can be
|
|
432
|
+
# * a file name
|
|
433
|
+
# * nil, then the output will be printed to STDOUT
|
|
434
|
+
# * String, then the output will be returned as a String
|
|
435
|
+
# * :errors : DOT error level (default 1)
|
|
436
|
+
# * 0 = Error + Warning
|
|
437
|
+
# * 1 = Error
|
|
438
|
+
# * 2 = none
|
|
439
|
+
#
|
|
440
|
+
def output( hOpts = {} )
|
|
441
|
+
xDOTScript = DOTScript.new
|
|
442
|
+
lNotHugly = []
|
|
443
|
+
|
|
444
|
+
append_attributes_and_types(xDOTScript)
|
|
445
|
+
|
|
446
|
+
xDOTScript << "}"
|
|
447
|
+
|
|
448
|
+
if has_parent_graph?
|
|
449
|
+
xDOTScript.make_subgraph("#{GraphViz.escape(@name, :unquote_empty => true)}")
|
|
450
|
+
else
|
|
451
|
+
hOutput = {}
|
|
452
|
+
|
|
453
|
+
hOpts.each do |xKey, xValue|
|
|
454
|
+
xValue = xValue.to_s unless xValue.nil? or [Class, TrueClass, FalseClass].include?(xValue.class)
|
|
455
|
+
case xKey.to_s
|
|
456
|
+
when "use"
|
|
457
|
+
if PROGRAMS.index( xValue ).nil?
|
|
458
|
+
raise ArgumentError, "can't use '#{xValue}'"
|
|
459
|
+
end
|
|
460
|
+
@prog = xValue
|
|
461
|
+
when "path"
|
|
462
|
+
@path = xValue && xValue.split( "," ).map{ |x| x.strip }
|
|
463
|
+
when "errors"
|
|
464
|
+
@errors = xValue
|
|
465
|
+
when "extlib"
|
|
466
|
+
@extlibs = xValue.split( "," ).map{ |x| x.strip }
|
|
467
|
+
when "scale"
|
|
468
|
+
# Scale input by 'v' (=72)
|
|
469
|
+
@scale = xValue
|
|
470
|
+
when "inverty"
|
|
471
|
+
# Invert y coordinate in output
|
|
472
|
+
@inverty = xValue
|
|
473
|
+
when "no_layout"
|
|
474
|
+
# No layout mode 'v' (=1)
|
|
475
|
+
@no_layout = xValue
|
|
476
|
+
when "reduce"
|
|
477
|
+
# Reduce graph
|
|
478
|
+
@reduce_graph = xValue
|
|
479
|
+
when "Lg"
|
|
480
|
+
# Don't use grid
|
|
481
|
+
@Lg = xValue
|
|
482
|
+
when "LO"
|
|
483
|
+
# Use old attractive force
|
|
484
|
+
@LO = xValue
|
|
485
|
+
when "Ln"
|
|
486
|
+
# Set number of iterations to i
|
|
487
|
+
@Ln = xValue
|
|
488
|
+
when "LU"
|
|
489
|
+
# Set unscaled factor to i
|
|
490
|
+
@LU = xValue
|
|
491
|
+
when "LC"
|
|
492
|
+
# Set overlap expansion factor to v
|
|
493
|
+
@LC = xValue
|
|
494
|
+
when "LT"
|
|
495
|
+
# Set temperature (temperature factor) to v
|
|
496
|
+
@LT = xValue
|
|
497
|
+
when "nothugly"
|
|
498
|
+
begin
|
|
499
|
+
require 'graphviz/nothugly'
|
|
500
|
+
@nothugly = true
|
|
501
|
+
rescue LoadError
|
|
502
|
+
warn "You must install ruby-xslt or libxslt-ruby to use nothugly option!"
|
|
503
|
+
@nothugly = false
|
|
504
|
+
end
|
|
505
|
+
else
|
|
506
|
+
if FORMATS.index( xKey.to_s ).nil?
|
|
507
|
+
raise ArgumentError, "output format '#{xValue}' invalid"
|
|
508
|
+
end
|
|
509
|
+
hOutput[xKey.to_s] = xValue
|
|
510
|
+
end
|
|
511
|
+
end
|
|
512
|
+
|
|
513
|
+
@output = hOutput if hOutput.size > 0
|
|
514
|
+
|
|
515
|
+
xStict = ((@strict && @oGraphType == "digraph") ? "strict " : "")
|
|
516
|
+
xDOTScript.prepend(
|
|
517
|
+
"#{xStict}#{@oGraphType} #{GraphViz.escape(@name, :unquote_empty => true)} {"
|
|
518
|
+
)
|
|
519
|
+
|
|
520
|
+
xOutputString = (@filename == String ||
|
|
521
|
+
@output.any? {|format, file| file == String })
|
|
522
|
+
|
|
523
|
+
xOutput = ""
|
|
524
|
+
if @format.to_s == "none" or @output.any? {|fmt, fn| fmt.to_s == "none"}
|
|
525
|
+
if xOutputString
|
|
526
|
+
xOutput << xDOTScript
|
|
527
|
+
else
|
|
528
|
+
xFileName = @output["none"] || @filename
|
|
529
|
+
open( xFileName, "w" ) do |h|
|
|
530
|
+
h.puts xDOTScript
|
|
531
|
+
end
|
|
532
|
+
end
|
|
533
|
+
end
|
|
534
|
+
|
|
535
|
+
if (@format.to_s != "none" and not @format.nil?) or (@output.any? {|format, file| format != "none" } and @output.size > 0)
|
|
536
|
+
## Act: Save script and send it to dot
|
|
537
|
+
t = Tempfile::open( File.basename(__FILE__) )
|
|
538
|
+
t.print( xDOTScript )
|
|
539
|
+
t.close
|
|
540
|
+
|
|
541
|
+
cmd = find_executable( @prog, @path )
|
|
542
|
+
if cmd == nil
|
|
543
|
+
raise StandardError, "GraphViz not installed or #{@prog} not in PATH. Install GraphViz or use the 'path' option"
|
|
544
|
+
end
|
|
545
|
+
|
|
546
|
+
xOutputWithFile = []
|
|
547
|
+
xOutputWithoutFile = []
|
|
548
|
+
unless @format.nil? or @format == "none"
|
|
549
|
+
lNotHugly << @filename if @format.to_s == "svg" and @nothugly
|
|
550
|
+
if @filename.nil? or @filename == String
|
|
551
|
+
xOutputWithoutFile = ["-T#{@format}"]
|
|
552
|
+
else
|
|
553
|
+
xOutputWithFile = ["-T#{@format}", "-o#{@filename}"]
|
|
554
|
+
end
|
|
555
|
+
end
|
|
556
|
+
@output.each_except( :key => ["none"] ) do |format, file|
|
|
557
|
+
lNotHugly << file if format.to_s == "svg" and @nothugly
|
|
558
|
+
if file.nil? or file == String
|
|
559
|
+
xOutputWithoutFile += ["-T#{format}"]
|
|
560
|
+
else
|
|
561
|
+
xOutputWithFile += ["-T#{format}", "-o#{file}"]
|
|
562
|
+
end
|
|
563
|
+
end
|
|
564
|
+
|
|
565
|
+
xExternalLibraries = @extlibs.map { |lib| "-l#{lib}" }
|
|
566
|
+
|
|
567
|
+
xOtherOptions = []
|
|
568
|
+
xOtherOptions << "-s#{@scale}" if @scale
|
|
569
|
+
xOtherOptions << "-y" if @inverty
|
|
570
|
+
xOtherOptions << "-n#{@no_layout}" if @no_layout
|
|
571
|
+
xOtherOptions << "-x" if @reduce_graph
|
|
572
|
+
xOtherOptions << "-Lg" if @Lg
|
|
573
|
+
xOtherOptions << "-LO" if @LO
|
|
574
|
+
xOtherOptions << "-Ln#{@Ln}" if @Ln
|
|
575
|
+
xOtherOptions << "-LU#{@LU}" if @LU
|
|
576
|
+
xOtherOptions << "-LC#{@LC}" if @LC
|
|
577
|
+
xOtherOptions << "-LT#{@LT}" if @LT
|
|
578
|
+
|
|
579
|
+
tmpPath = if IS_JRUBY
|
|
580
|
+
t.path
|
|
581
|
+
elsif IS_CYGWIN
|
|
582
|
+
begin
|
|
583
|
+
IO.popen("cygpath", "-w", t.path).chomp
|
|
584
|
+
rescue
|
|
585
|
+
warn "cygpath is not installed!"
|
|
586
|
+
t.path
|
|
587
|
+
end
|
|
588
|
+
else
|
|
589
|
+
t.path
|
|
590
|
+
end
|
|
591
|
+
|
|
592
|
+
xCmd = [cmd, "-q#{@errors}"] +
|
|
593
|
+
xExternalLibraries +
|
|
594
|
+
xOtherOptions +
|
|
595
|
+
xOutputWithFile +
|
|
596
|
+
xOutputWithoutFile +
|
|
597
|
+
[tmpPath]
|
|
598
|
+
|
|
599
|
+
xOutput << output_from_command( xCmd )
|
|
600
|
+
end
|
|
601
|
+
|
|
602
|
+
# Not Hugly
|
|
603
|
+
lNotHugly.each do |f|
|
|
604
|
+
if f.nil? or f == String
|
|
605
|
+
xOutput = GraphViz.nothugly( xOutput, false )
|
|
606
|
+
else
|
|
607
|
+
GraphViz.nothugly( f, true )
|
|
608
|
+
end
|
|
609
|
+
end
|
|
610
|
+
|
|
611
|
+
if xOutputString
|
|
612
|
+
xOutput
|
|
613
|
+
else
|
|
614
|
+
print xOutput
|
|
615
|
+
end
|
|
616
|
+
end
|
|
617
|
+
end
|
|
618
|
+
alias :save :output
|
|
619
|
+
|
|
620
|
+
def append_attributes_and_types(script)
|
|
621
|
+
script_data = DOTScriptData.new
|
|
622
|
+
|
|
623
|
+
@elements_order.each { |kElement|
|
|
624
|
+
is_new_type = script_data.type != kElement["type"]
|
|
625
|
+
if is_new_type
|
|
626
|
+
script << script_data unless script_data.type.nil? or script_data.empty?
|
|
627
|
+
script_data = DOTScriptData.new(kElement["type"])
|
|
628
|
+
end
|
|
629
|
+
|
|
630
|
+
# Modified by Brandon Coleman verify value is NOT NULL
|
|
631
|
+
kElement["value"] or raise ArgumentError, "#{kElement["name"]} is nil!"
|
|
632
|
+
|
|
633
|
+
case kElement["type"]
|
|
634
|
+
when "graph_attr", "node_attr", "edge_attr"
|
|
635
|
+
script_data.add_attribute(kElement["name"], kElement["value"].to_gv)
|
|
636
|
+
when "node", "graph"
|
|
637
|
+
script << kElement["value"].output()
|
|
638
|
+
when "edge"
|
|
639
|
+
script << " " + kElement["value"].output( @oGraphType )
|
|
640
|
+
else
|
|
641
|
+
raise ArgumentError,
|
|
642
|
+
"Don't know what to do with element type '#{kElement['type']}'"
|
|
643
|
+
end
|
|
644
|
+
}
|
|
645
|
+
script << script_data unless script_data.type.nil? or script_data.empty?
|
|
646
|
+
end
|
|
647
|
+
|
|
648
|
+
def to_s
|
|
649
|
+
self.output(:none => String)
|
|
650
|
+
end
|
|
651
|
+
|
|
652
|
+
#
|
|
653
|
+
# Get the graph name
|
|
654
|
+
#
|
|
655
|
+
def name
|
|
656
|
+
@name.clone
|
|
657
|
+
end
|
|
658
|
+
alias :id :name
|
|
659
|
+
|
|
660
|
+
#
|
|
661
|
+
# Create an edge between the current cluster and the node or cluster +oNode+
|
|
662
|
+
#
|
|
663
|
+
def <<( oNode )
|
|
664
|
+
raise( ArgumentError, "Edge between root graph and node or cluster not allowed!" ) if self.pg.nil?
|
|
665
|
+
|
|
666
|
+
if( oNode.class == Array )
|
|
667
|
+
oNode.each do |no|
|
|
668
|
+
self << no
|
|
669
|
+
end
|
|
670
|
+
else
|
|
671
|
+
return GraphViz::commonGraph( oNode, self ).add_edges( self, oNode )
|
|
672
|
+
end
|
|
673
|
+
end
|
|
674
|
+
alias :> :<<
|
|
675
|
+
alias :- :<<
|
|
676
|
+
alias :>> :<<
|
|
677
|
+
|
|
678
|
+
def pg #:nodoc:
|
|
679
|
+
@oParentGraph
|
|
680
|
+
end
|
|
681
|
+
def pg=(x) #:nodoc:
|
|
682
|
+
@oParentGraph = x
|
|
683
|
+
end
|
|
684
|
+
|
|
685
|
+
#
|
|
686
|
+
# Return the root graph
|
|
687
|
+
#
|
|
688
|
+
def root_graph
|
|
689
|
+
return( (self.pg.nil?) ? self : self.pg.root_graph )
|
|
690
|
+
end
|
|
691
|
+
|
|
692
|
+
def self.commonGraph( o1, o2 ) #:nodoc:
|
|
693
|
+
g1 = o1.pg
|
|
694
|
+
g2 = o2.pg
|
|
695
|
+
|
|
696
|
+
return o1 if g1.nil?
|
|
697
|
+
return o2 if g2.nil?
|
|
698
|
+
|
|
699
|
+
return g1 if g1.object_id == g2.object_id
|
|
700
|
+
|
|
701
|
+
return GraphViz::commonGraph( g1, g2 )
|
|
702
|
+
end
|
|
703
|
+
|
|
704
|
+
def set_position( xType, xKey, xValue ) #:nodoc:
|
|
705
|
+
@elements_order.push( {
|
|
706
|
+
"type" => "#{xType}_attr",
|
|
707
|
+
"name" => xKey,
|
|
708
|
+
"value" => xValue
|
|
709
|
+
} )
|
|
710
|
+
end
|
|
711
|
+
|
|
712
|
+
## ----------------------------------------------------------------------------
|
|
713
|
+
|
|
714
|
+
#
|
|
715
|
+
# Change default options (:use, :path, :errors and :output)
|
|
716
|
+
#
|
|
717
|
+
def self.default( hOpts )
|
|
718
|
+
hOpts.each do |k, v|
|
|
719
|
+
case k.to_s
|
|
720
|
+
when "use"
|
|
721
|
+
@@prog = v
|
|
722
|
+
when "path"
|
|
723
|
+
@@path = v.split( "," ).map{ |x| x.strip }
|
|
724
|
+
when "errors"
|
|
725
|
+
@@errors = v
|
|
726
|
+
when "extlibs"
|
|
727
|
+
@@extlibs = v.split( "," ).map{ |x| x.strip }
|
|
728
|
+
else
|
|
729
|
+
warn "Invalid option #{k}!"
|
|
730
|
+
end
|
|
731
|
+
end
|
|
732
|
+
end
|
|
733
|
+
|
|
734
|
+
def self.options( hOpts )
|
|
735
|
+
GraphViz::default( hOpts )
|
|
736
|
+
end
|
|
737
|
+
|
|
738
|
+
## ----------------------------------------------------------------------------
|
|
739
|
+
|
|
740
|
+
# Create a new graph from a GraphViz File
|
|
741
|
+
#
|
|
742
|
+
# Options :
|
|
743
|
+
# * :output : Output format (GraphViz::Constants::FORMATS) (default : dot)
|
|
744
|
+
# * :file : Output file name (default : none)
|
|
745
|
+
# * :use : Program to use (GraphViz::Constants::PROGRAMS) (default : dot)
|
|
746
|
+
# * :path : Program PATH
|
|
747
|
+
#
|
|
748
|
+
def self.parse( xFile, hOpts = {}, &block )
|
|
749
|
+
graph = Dot2Ruby::new( hOpts[:path], nil, nil ).eval( xFile )
|
|
750
|
+
yield( graph ) if( block and graph )
|
|
751
|
+
return graph
|
|
752
|
+
end
|
|
753
|
+
|
|
754
|
+
# Create a new graph from a GraphViz File
|
|
755
|
+
#
|
|
756
|
+
# Options :
|
|
757
|
+
# * :output : Output format (GraphViz::Constants::FORMATS) (default : dot)
|
|
758
|
+
# * :file : Output file name (default : none)
|
|
759
|
+
# * :use : Program to use (GraphViz::Constants::PROGRAMS) (default : dot)
|
|
760
|
+
# * :path : Program PATH
|
|
761
|
+
#
|
|
762
|
+
def self.parse_string( str, hOpts = {}, &block )
|
|
763
|
+
graph = Dot2Ruby::new( hOpts[:path], nil, nil ).eval_string( str )
|
|
764
|
+
yield( graph ) if( block and graph )
|
|
765
|
+
return graph
|
|
766
|
+
end
|
|
767
|
+
|
|
768
|
+
# Return a new completed graph
|
|
769
|
+
def complete
|
|
770
|
+
GraphViz.parse_string( root_graph.output( :dot => String ) )
|
|
771
|
+
end
|
|
772
|
+
|
|
773
|
+
# Complete the current graph
|
|
774
|
+
def complete!
|
|
775
|
+
# TODO: Keep options
|
|
776
|
+
complete
|
|
777
|
+
end
|
|
778
|
+
|
|
779
|
+
# Return true if the graph is directed.
|
|
780
|
+
def directed?
|
|
781
|
+
not((/digraph/ =~ "bla digraph bla").nil?)
|
|
782
|
+
end
|
|
783
|
+
|
|
784
|
+
def has_parent_graph?
|
|
785
|
+
not @oParentGraph.nil?
|
|
786
|
+
end
|
|
787
|
+
## ----------------------------------------------------------------------------
|
|
788
|
+
|
|
789
|
+
private
|
|
790
|
+
|
|
791
|
+
## Var: Nodes, Edges and Graphs tables
|
|
792
|
+
@hoNodes = nil
|
|
793
|
+
@loEdges = nil
|
|
794
|
+
@hoGraphs = nil
|
|
795
|
+
|
|
796
|
+
## Var: Parent graph
|
|
797
|
+
@oParentGraph = nil
|
|
798
|
+
|
|
799
|
+
## Var: Type de graphe (orienté ou non)
|
|
800
|
+
@oGraphType = nil
|
|
801
|
+
|
|
802
|
+
#
|
|
803
|
+
# Create a new graph object
|
|
804
|
+
#
|
|
805
|
+
# Options :
|
|
806
|
+
# * :output : Output format (GraphViz::Constants::FORMATS) (default : dot)
|
|
807
|
+
# * :file : Output file name (default : nil)
|
|
808
|
+
# * :use : Program to use (GraphViz::Constants::PROGRAMS) (default : dot)
|
|
809
|
+
# * :path : Program PATH
|
|
810
|
+
# * :parent : Parent graph (default : nil)
|
|
811
|
+
# * :type : Graph type (GraphViz::Constants::GRAPHTYPE) (default : digraph)
|
|
812
|
+
# * :errors : DOT error level (default 1)
|
|
813
|
+
# * 0 = Error + Warning
|
|
814
|
+
# * 1 = Error
|
|
815
|
+
# * 2 = none
|
|
816
|
+
#
|
|
817
|
+
def initialize( xGraphName, hOpts = {}, &block )
|
|
818
|
+
@filename = nil
|
|
819
|
+
@name = xGraphName.to_s
|
|
820
|
+
@format = @@format
|
|
821
|
+
@prog = @@prog
|
|
822
|
+
@path = @@path
|
|
823
|
+
@errors = @@errors
|
|
824
|
+
@extlibs = @@extlibs
|
|
825
|
+
@output = {}
|
|
826
|
+
@nothugly = false
|
|
827
|
+
@strict = false
|
|
828
|
+
|
|
829
|
+
@scale = nil
|
|
830
|
+
@inverty = nil
|
|
831
|
+
@no_layout = nil
|
|
832
|
+
@reduce_graph = nil
|
|
833
|
+
@Lg = nil
|
|
834
|
+
@LO = nil
|
|
835
|
+
@Ln = nil
|
|
836
|
+
@LU = nil
|
|
837
|
+
@LC = nil
|
|
838
|
+
@LT = nil
|
|
839
|
+
|
|
840
|
+
@elements_order = GraphViz::Elements::new()
|
|
841
|
+
|
|
842
|
+
@oParentGraph = nil
|
|
843
|
+
@oGraphType = "digraph"
|
|
844
|
+
|
|
845
|
+
@hoNodes = Hash::new()
|
|
846
|
+
@loEdges = Array::new()
|
|
847
|
+
@hoGraphs = Hash::new()
|
|
848
|
+
|
|
849
|
+
@node = GraphViz::Attrs::new( self, "node", NODESATTRS )
|
|
850
|
+
@edge = GraphViz::Attrs::new( self, "edge", EDGESATTRS )
|
|
851
|
+
@graph = GraphViz::Attrs::new( self, "graph", GRAPHSATTRS )
|
|
852
|
+
|
|
853
|
+
hOpts.each do |xKey, xValue|
|
|
854
|
+
case xKey.to_s
|
|
855
|
+
when "use"
|
|
856
|
+
if PROGRAMS.index( xValue.to_s ).nil?
|
|
857
|
+
raise ArgumentError, "can't use '#{xValue}'"
|
|
858
|
+
end
|
|
859
|
+
@prog = xValue.to_s
|
|
860
|
+
when "parent"
|
|
861
|
+
@oParentGraph = xValue
|
|
862
|
+
when "type"
|
|
863
|
+
if GRAPHTYPE.index( xValue.to_s ).nil?
|
|
864
|
+
raise ArgumentError, "graph type '#{xValue}' unknow"
|
|
865
|
+
end
|
|
866
|
+
@oGraphType = xValue.to_s
|
|
867
|
+
when "path"
|
|
868
|
+
@path = xValue.split( "," ).map{ |x| x.strip }
|
|
869
|
+
when "strict"
|
|
870
|
+
@strict = (xValue ? true : false)
|
|
871
|
+
when "errors"
|
|
872
|
+
@errors = xValue
|
|
873
|
+
when "extlibs"
|
|
874
|
+
@extlibs = xValue.split( "," ).map{ |x| x.strip }
|
|
875
|
+
else
|
|
876
|
+
self[xKey.to_s] = xValue.to_s
|
|
877
|
+
end
|
|
878
|
+
end
|
|
879
|
+
|
|
880
|
+
yield( self ) if( block )
|
|
881
|
+
end
|
|
882
|
+
|
|
883
|
+
# Edge between a node and a Hash
|
|
884
|
+
# Used by GraphViz#add
|
|
885
|
+
def add_hash_edge(node, hash)
|
|
886
|
+
if hash.kind_of? Hash
|
|
887
|
+
hash.each do |nt, data|
|
|
888
|
+
add_edges(node, nt)
|
|
889
|
+
add_hash_edge(nt, data)
|
|
890
|
+
end
|
|
891
|
+
else
|
|
892
|
+
add_edges(node, hash)
|
|
893
|
+
end
|
|
894
|
+
end
|
|
895
|
+
|
|
896
|
+
#
|
|
897
|
+
# Create a new undirected graph
|
|
898
|
+
#
|
|
899
|
+
# See also GraphViz::new
|
|
900
|
+
#
|
|
901
|
+
def self.graph( xGraphName, hOpts = {}, &block )
|
|
902
|
+
new( xGraphName, hOpts.symbolize_keys.merge( {:type => "graph"} ), &block )
|
|
903
|
+
end
|
|
904
|
+
|
|
905
|
+
#
|
|
906
|
+
# Create a new directed graph
|
|
907
|
+
#
|
|
908
|
+
# See also GraphViz::new
|
|
909
|
+
#
|
|
910
|
+
def self.digraph( xGraphName, hOpts = {}, &block )
|
|
911
|
+
new( xGraphName, hOpts.symbolize_keys.merge( {:type => "digraph"} ), &block )
|
|
912
|
+
end
|
|
913
|
+
|
|
914
|
+
# Create a new strict directed graph
|
|
915
|
+
#
|
|
916
|
+
# See also GraphViz::new
|
|
917
|
+
def self.strict_digraph( xGraphName, hOpts = {}, &block )
|
|
918
|
+
new( xGraphName, hOpts.symbolize_keys.merge( {:type => "digraph", :strict => true} ), &block )
|
|
919
|
+
end
|
|
920
|
+
|
|
921
|
+
# Create a random graph.
|
|
922
|
+
def self.generate(num_nodes, num_edges, directed = false, weight_range = (1..1))
|
|
923
|
+
g = nil
|
|
924
|
+
if directed
|
|
925
|
+
g = GraphViz.digraph(:G)
|
|
926
|
+
else
|
|
927
|
+
g = GraphViz.graph(:G)
|
|
928
|
+
end
|
|
929
|
+
|
|
930
|
+
nodes = (1..num_nodes).map{ |e| e.to_s }
|
|
931
|
+
g.add_nodes(nodes)
|
|
932
|
+
|
|
933
|
+
edges = []
|
|
934
|
+
nodes.each do |head|
|
|
935
|
+
nodes.each do |tail|
|
|
936
|
+
if (directed and head != tail) or (head < tail)
|
|
937
|
+
edges << {:head => head, :tail => tail, :weight => weight_range.to_a.shuffle[0]}
|
|
938
|
+
end
|
|
939
|
+
end
|
|
940
|
+
end
|
|
941
|
+
edges.shuffle!
|
|
942
|
+
|
|
943
|
+
(num_edges - 1).times do |i|
|
|
944
|
+
g.add_edges(edges[i][:head], edges[i][:tail], :label => edges[i][:weight].to_s, :weight => edges[i][:weight])
|
|
945
|
+
end
|
|
946
|
+
|
|
947
|
+
return g
|
|
948
|
+
end
|
|
949
|
+
|
|
950
|
+
#
|
|
951
|
+
# Escape a string to be acceptable as a node name in a graphviz input file
|
|
952
|
+
#
|
|
953
|
+
def self.escape(str, opts = {} ) #:nodoc:
|
|
954
|
+
options = {
|
|
955
|
+
:force => false,
|
|
956
|
+
:unquote_empty => false,
|
|
957
|
+
}.merge(opts)
|
|
958
|
+
|
|
959
|
+
if (options[:force] or str.match( /\A[a-zA-Z_]+[a-zA-Z0-9_]*\Z/ ).nil?)
|
|
960
|
+
unless options[:unquote_empty] and str.size == 0
|
|
961
|
+
'"' + str.gsub('"', '\\"').gsub("\n", '\\\\n') + '"'
|
|
962
|
+
end
|
|
963
|
+
else
|
|
964
|
+
str
|
|
965
|
+
end
|
|
966
|
+
end
|
|
967
|
+
end
|