yohasebe-rubyfca 0.2.3

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ *.sw?
2
+ .DS_Store
3
+ coverage
4
+ rdoc
5
+ pkg
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Yoichiro Hasebe
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,45 @@
1
+ = RubyFCA
2
+
3
+ Command line tool for Formal Concept Analysis (FCA) written in Ruby.
4
+
5
+ == Features
6
+
7
+ * Convert a Conexp's CXT file and generate a Graphviz DOT file, or a PNG/JPG/EPS image file.
8
+ * Adopt the Ganter algorithm (through its Perl implementation of Fcastone by Uta Priss).
9
+
10
+ == Installation
11
+
12
+ Install the gem:
13
+
14
+ $sudo gem install rubyfca --source http://gems.github.com
15
+
16
+ == How to Use
17
+
18
+ rubyfca [options] <source file> <output file>
19
+
20
+ where:
21
+ <source file>
22
+ "foo.cxt"
23
+ <output file>
24
+ "bar.dot", "bar.png", "bar.jpg", or "bar.eps"
25
+ [options]:
26
+ --box, -b: Use box shaped concept nodes
27
+ --full, -f: Do not contract concept labels
28
+ --legend, -l: Print the legend of concept nodes (disabled when using circle node shape) (default: true)
29
+ --coloring, -c: Color concept nodes (default: true)
30
+ --straight, -s: Straighten edges (available when output format is either png, jpg, or eps)
31
+ --help, -h: Show this message
32
+
33
+
34
+ == ToDo
35
+
36
+ * Multiple input formats
37
+ * Database connection capability
38
+
39
+ == Links
40
+
41
+ under construction
42
+
43
+ == Copyright
44
+
45
+ Copyright (c) 2009 Yoichiro Hasebe and Kow Kuroda. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,56 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "rubyfca"
8
+ gem.summary = %Q{TODO: one-line summary of your gem}
9
+ gem.description = %Q{TODO: longer description of your gem}
10
+ gem.email = "yohasebe@gmail.com"
11
+ gem.homepage = "http://github.com/yohasebe/rubyfca"
12
+ gem.authors = ["Yoichiro Hasebe"]
13
+ gem.add_development_dependency "thoughtbot-shoulda"
14
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
15
+ end
16
+ rescue LoadError
17
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
18
+ end
19
+
20
+ require 'rake/testtask'
21
+ Rake::TestTask.new(:test) do |test|
22
+ test.libs << 'lib' << 'test'
23
+ test.pattern = 'test/**/*_test.rb'
24
+ test.verbose = true
25
+ end
26
+
27
+ begin
28
+ require 'rcov/rcovtask'
29
+ Rcov::RcovTask.new do |test|
30
+ test.libs << 'test'
31
+ test.pattern = 'test/**/*_test.rb'
32
+ test.verbose = true
33
+ end
34
+ rescue LoadError
35
+ task :rcov do
36
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
37
+ end
38
+ end
39
+
40
+ task :test => :check_dependencies
41
+
42
+ task :default => :test
43
+
44
+ require 'rake/rdoctask'
45
+ Rake::RDocTask.new do |rdoc|
46
+ if File.exist?('VERSION')
47
+ version = File.read('VERSION')
48
+ else
49
+ version = ""
50
+ end
51
+
52
+ rdoc.rdoc_dir = 'rdoc'
53
+ rdoc.title = "rubyfca #{version}"
54
+ rdoc.rdoc_files.include('README*')
55
+ rdoc.rdoc_files.include('lib/**/*.rb')
56
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.2.3
data/bin/rubyfca ADDED
@@ -0,0 +1,96 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.unshift File.join(File.dirname(__FILE__), '..', 'lib')
4
+ require 'trollop'
5
+ require 'rubyfca'
6
+ require 'ruby_graphviz'
7
+
8
+ ################ parse options ##########
9
+
10
+ opts = Trollop::options do
11
+ version = File.read(File.dirname(__FILE__) + "/../VERSION")
12
+ banner <<-EOS
13
+
14
+ RubuFCA converts Conexp CXT data to Graphviz dot format.
15
+
16
+ Usage:
17
+ rubyfca [options] <source file> <output file>
18
+
19
+ where:
20
+ <source file>
21
+ ".cxt"
22
+ <output file>
23
+ ."dot", ".png", ".jpg", or ".eps"
24
+ [options]:
25
+ EOS
26
+
27
+ opt :circle, "Use circle shaped concept nodes", :default=> false
28
+ opt :full, "Do not contract concept labels", :default=> false
29
+ opt :legend, "Print the legend of concept nodes (available only when using circle node shape)", :default => false
30
+ opt :coloring, "Color concept nodes", :default => false
31
+ opt :straight, "Straighten edges (available when output format is either png, jpg, or eps)", :default => false
32
+ end
33
+
34
+ ############### main program ###############
35
+
36
+ if ARGV.size != 2
37
+ showerror("Input and output files are not set properly", 1)
38
+ end
39
+
40
+ filename1 = ARGV[0] #input filename
41
+ filename2 = ARGV[1] #output filename
42
+
43
+ #
44
+ # extract input and output file types
45
+ #
46
+ input_type = filename1.slice(/\.[^\.]+\z/).split(//)[1..-1].join("")
47
+ output_type = filename2.slice(/\.[^\.]+\z/).split(//)[1..-1].join("")
48
+
49
+ if (input_type !~ /\A(cxt|csv)\z/ || output_type !~ /\A(dot|png|jpg|eps)\z/)
50
+ showerror("These file extensions are not (yet) supported.", 1)
51
+ end
52
+
53
+ #
54
+ # input cxt data is kept as plain text
55
+ #
56
+ f = File.open(filename1, "r")
57
+ inputdata = f.read
58
+ f.close
59
+
60
+ #
61
+ # ask for confirmation of overwriting an exisiting file
62
+ #
63
+ if (File.exist?(filename2) && !opts[:sil])
64
+ print "#{filename2} exists and will be overwritten, OK? [y/n]"
65
+ var1 = STDIN.gets;
66
+ if /y/i !~ var1
67
+ exit;
68
+ end
69
+ end
70
+
71
+ #
72
+ # context data is converted to a hash table
73
+ #
74
+ begin
75
+ ctxt = FormalContext.new(inputdata, opts[:full])
76
+ ctxt.calcurate
77
+ # rescue => e
78
+ # puts e
79
+ # showerror("Source data may have problems. Process aborted.", 1)
80
+ end
81
+
82
+ #
83
+ # create the output file
84
+ #
85
+ case output_type
86
+ when "dot"
87
+ File.open(filename2, "w") do |f|
88
+ f.write(ctxt.generate_dot(opts))
89
+ end
90
+ when "png"
91
+ ctxt.generate_img(filename2, "png", opts)
92
+ when "jpg"
93
+ ctxt.generate_img(filename2, "jpg", opts)
94
+ when "eps"
95
+ ctxt.generate_img(filename2, "eps", opts)
96
+ end
@@ -0,0 +1,167 @@
1
+ ## lib/ruby_graphviz.rb -- graphviz dot generator library
2
+ ## Author:: Yoichiro Hasebe (mailto: yohasebe@gmail.com)
3
+ ## Copyright:: Copyright 2009 Yoichiro Hasebe
4
+ ## License:: GNU GPL version 3
5
+
6
+ class RubyGraphviz
7
+
8
+ ## Example:
9
+ ##
10
+ ## g = RubyGraphviz.new("newgraph", {:rankdir => "LR", :nodesep => "0.4", :ranksep => "0.2"})
11
+ ##
12
+ def initialize(name, graph_hash = nil)
13
+ @name = name
14
+ @graph_data = graph_hash
15
+ @nodes = []
16
+ @edges = []
17
+ @dot = ""
18
+ create_graph
19
+ end
20
+
21
+ protected
22
+
23
+ def create_graph
24
+ @dot << "graph #{@name} {\n graph"
25
+ index = 0
26
+ if @graph_data
27
+ @dot << " ["
28
+ @graph_data.each do |k, v|
29
+ k = k.to_s
30
+ @dot << "#{k} = \"#{v}\""
31
+ index += 1
32
+ @dot << ", " unless index == @graph_data.size
33
+ end
34
+ @dot << "]"
35
+ end
36
+ @dot << ";\n"
37
+ end
38
+
39
+ def finish_graph
40
+ @dot << "}\n"
41
+ end
42
+
43
+ def create_edge(edgetype, nid1, nid2, edge_hash = nil)
44
+ temp = " #{nid1.to_s} #{edgetype} #{nid2.to_s}"
45
+ index = 0
46
+ if edge_hash
47
+ temp << " ["
48
+ edge_hash.each do |k, v|
49
+ k = k.to_s
50
+ temp << "#{k} = \"#{v}\""
51
+ index += 1
52
+ temp << ", " unless index == edge_hash.size
53
+ end
54
+ temp << "]"
55
+ end
56
+ return temp
57
+ end
58
+
59
+ public
60
+
61
+ ## Add a subgraph to a graph (recursively)
62
+ ##
63
+ ## Example:
64
+ ##
65
+ ## graph1.subgraph(graph2)
66
+ ##
67
+ def subgraph(graph)
68
+ @dot << graph.to_dot.sub(/\Agraph/, "subgraph")
69
+ end
70
+
71
+ ## Set default options for nodes
72
+ ##
73
+ ## Example:
74
+ ##
75
+ ## graph.node_default(:shape => "record", :color => "gray60")
76
+ ##
77
+ def node_default(node_hash = nil)
78
+ @dot << " node["
79
+ index = 0
80
+ node_hash.each do |k, v|
81
+ k = k.to_s
82
+ @dot << "#{k} = \"#{v}\""
83
+ index += 1
84
+ @dot << ", " unless index == node_hash.size
85
+ end
86
+ @dot << "];\n"
87
+ self
88
+ end
89
+
90
+ ## Set default options for edges
91
+ ##
92
+ ## Example:
93
+ ##
94
+ ## graph.edge_default(:color => "gray60")
95
+ ##
96
+ def edge_default(edge_hash = nil)
97
+ @dot << " edge["
98
+ index = 0
99
+ edge_hash.each do |k, v|
100
+ k = k.to_s
101
+ @dot << "#{k} = \"#{v}\""
102
+ index += 1
103
+ @dot << ", " unless index == edge_hash.size
104
+ end
105
+ @dot << "];\n"
106
+ self
107
+ end
108
+
109
+ ## Create a node with its options
110
+ ##
111
+ ## Example:
112
+ ##
113
+ ## graph.node("node-01", :label => "Node 01", :fillcolor => "pink")
114
+ ##
115
+ def node(node_id, node_hash = nil)
116
+ @dot << " #{node_id.to_s}"
117
+ index = 0
118
+ if node_hash
119
+ @dot << " ["
120
+ node_hash.each do |k, v|
121
+ k = k.to_s
122
+ @dot << "#{k} = \"#{v}\""
123
+ index += 1
124
+ @dot << ", " unless index == node_hash.size
125
+ end
126
+ @dot << "]"
127
+ end
128
+ @dot << ";\n"
129
+ self
130
+ end
131
+
132
+ ## Create a non-directional edge (connection line between nodes) with its options
133
+ ##
134
+ ## Example:
135
+ ##
136
+ ## graph.edge("node-01", "node-02", :label => "connecting 1 and 2", :color => "lightblue")
137
+ ##
138
+ def edge(nid1, nid2, edge_hash = nil)
139
+ @dot << create_edge("--", nid1, nid2, edge_hash) + ";\n"
140
+ self
141
+ end
142
+
143
+ ## Create a directional edge (arrow from node to node) with its options
144
+ ##
145
+ ## Example:
146
+ ## graph.arrow_edge("node-01", "node-02", :label => "from 1 to 2", :color => "lightblue")
147
+ ##
148
+ def arrow_edge(nid1, nid2, edge_hash = nil)
149
+ @dot << create_edge("->", nid1, nid2, edge_hash) + ";\n"
150
+ self
151
+ end
152
+
153
+ ## Align nodes on the same rank connecting them with non-directional edges
154
+ ##
155
+ def rank(nid1, nid2, edge_hash = nil)
156
+ @dot << "{rank=same " + create_edge("--", nid1, nid2, edge_hash) + "}\n"
157
+ self
158
+ end
159
+
160
+ ## Convert graph into dot formatted data
161
+ ##
162
+ def to_dot
163
+ finish_graph
164
+ @dot = @dot.gsub(/\"\</m, "<").gsub(/\>\"/m, ">")
165
+ return @dot
166
+ end
167
+ end
data/lib/rubyfca.rb ADDED
@@ -0,0 +1,397 @@
1
+ ## lib/rubyfca.rb -- Formal Concept Analysis tool in Ruby
2
+ ## Author:: Yoichiro Hasebe (mailto: yohasebe@gmail.com)
3
+ ## Kow Kuroda (mailto: kuroda@nict.go.jp)
4
+ ## Copyright:: Copyright 2009 Yoichiro Hasebe and Kow Kuroda
5
+ ## License:: GNU GPL version 3
6
+
7
+ $KCODE ='utf-8'
8
+ require 'ruby_graphviz'
9
+
10
+ private
11
+
12
+ ## Take two arrays each consisting of 0s and 1s and create their logical disjunction
13
+ def create_and_ary(a, b)
14
+ return false if (s = a.size) != b.size
15
+ result = (0..(s-1)).to_a.map{|i| a[i].to_i & b[i].to_i}
16
+ return result
17
+ end
18
+
19
+ ## Take two arrays each consisting of 0s and 1s and create their logical conjunction
20
+ def create_or_ary(a, b)
21
+ return false if (s = a.size) != b.size
22
+ result = (0..(s-1)).to_a.map{|i| a[i].to_i | b[i].to_i}
23
+ return result
24
+ end
25
+
26
+ public
27
+
28
+ ## Take care of errors
29
+ def showerror(sentence, severity)
30
+ if severity == 0
31
+ puts "Warning: #{sentence} The output may not be meaningful."
32
+ elsif severity == 1
33
+ puts "Error: #{sentence} No output generated."
34
+ exit
35
+ end
36
+ end
37
+
38
+ ## Basic structure of the code is the same as Fcastone written in Perl by Uta Priss
39
+ class FormalContext
40
+
41
+ ## Converte cxt data to three basic structures of objects, attributes, and matrix
42
+ def initialize(input, label_contraction = true)
43
+ if input.size == 0
44
+ showerror("File is empty", 1)
45
+ end
46
+ lines = input.split
47
+ t1 = 3
48
+ if (lines[0] !~ /B/i || (2 * lines[1].to_i + lines[2].to_i + t1) != lines.size)
49
+ showerror("Wrong cxt format!", 1)
50
+ end
51
+
52
+ @label_contraction = label_contraction
53
+
54
+ @objects = lines[t1..(lines[1].to_i + t1 - 1)]
55
+ @attributes = lines[(lines[1].to_i + t1) .. (lines[1].to_i + lines[2].to_i + t1 - 1)]
56
+ lines = lines[(lines[1].to_i + lines[2].to_i + t1) .. lines.size]
57
+ @matrix = changecrosssymbol("X", "\\.", lines)
58
+ end
59
+
60
+ ## Apply a formal concept analysis on the matrix
61
+ def calcurate
62
+ @concepts, @extM, @intM = ganter_alg(@matrix)
63
+ @relM, @reltrans, @rank = create_rel(@intM)
64
+ @gammaM, @muM = gammaMu(@extM, @intM, @matrix)
65
+ end
66
+
67
+ ## This is an implementation of an algorithm described by Bernhard Ganter
68
+ ## in "Two basic algorithms in concept analysis." Technische Hochschule
69
+ ## Darmstadt, FB4-Preprint, 831, 1984.
70
+ def ganter_alg(matrix)
71
+
72
+ m = matrix
73
+
74
+ ## all arrays except @idx are arrays of arrays of 0's and 1's
75
+ idx = []
76
+ extension = []
77
+ intension = []
78
+ ext_A = []
79
+ int_B = []
80
+ endkey = []
81
+ temp = []
82
+
83
+ ## the level in the lattice from the top
84
+ lvl = 0
85
+ ## is lower than the index of leftmost attr
86
+ idx[lvl] = -1
87
+ ## number of attr. and objs
88
+ anzM = m.size
89
+ ## only needed for initialization
90
+ anzG = m[0].size
91
+ ## initialize extA[0] = [1,...,1]
92
+ anzG.times do |i|
93
+ if !ext_A[0]
94
+ ext_A[0] = []
95
+ end
96
+ ext_A[0] << 1
97
+ end
98
+ ## initialize extB[0] = [0,...,0]
99
+ anzM.times do |i|
100
+ if !int_B[0]
101
+ int_B[0] = []
102
+ end
103
+ int_B[0] << 0
104
+ end
105
+ anzCpt = 0
106
+ extension[0] = ext_A[0]
107
+ intension[0] = int_B[0]
108
+ anzM.times do |i|
109
+ endkey << 1
110
+ end
111
+
112
+ ## start of algorithm
113
+ while int_B[lvl] != endkey
114
+ (anzM - 1).downto(0) do |i|
115
+ breakkey = false
116
+ if (int_B[lvl][i] != 1)
117
+ while (i < idx[lvl])
118
+ lvl -= 1
119
+ end
120
+ idx[lvl + 1] = i
121
+ ext_A[lvl + 1] = create_and_ary(ext_A[lvl], m[i])
122
+ 0.upto(i - 1) do |j|
123
+ if(!breakkey && int_B[lvl][j] != 1)
124
+ temp = create_and_ary(ext_A[lvl + 1], m[j])
125
+ if temp == ext_A[lvl + 1]
126
+ breakkey = true
127
+ end
128
+ end
129
+ end
130
+ unless breakkey
131
+ int_B[lvl + 1] = int_B[lvl].dup
132
+ int_B[lvl + 1][i] = 1
133
+ (i+1).upto(anzM - 1) do |k|
134
+ if int_B[lvl + 1][k] != 1
135
+ temp = create_and_ary(ext_A[lvl + 1], m[k])
136
+ if temp == ext_A[lvl + 1]
137
+ int_B[lvl + 1][k] = 1
138
+ end
139
+ end
140
+ end
141
+ lvl += 1
142
+ anzCpt += 1
143
+ extension[anzCpt] = ext_A[lvl]
144
+ intension[anzCpt] = int_B[lvl]
145
+ break
146
+ end
147
+ end
148
+ end
149
+ end
150
+
151
+ a1 = extension[0].join("")
152
+ a2 = extension[1].join("")
153
+ if a1 == a2
154
+ shift extension
155
+ shift intension
156
+ anzCpt -= 1
157
+ end
158
+
159
+ c = []
160
+ 0.upto(anzCpt) do |i|
161
+ c[i] = i
162
+ end
163
+
164
+ [c, intension, extension]
165
+ end
166
+
167
+ ## Output arrayconsists of the following:
168
+ ## r (subconcept superconcept relation)
169
+ ## rt (trans. closure of r)
170
+ ## s (ranked concepts)
171
+ def create_rel(intensions)
172
+ anzCpt = intensions.size
173
+ rank = []
174
+ sup_con = []
175
+ r = []
176
+ rt = []
177
+ s = []
178
+
179
+ 0.upto(anzCpt - 1) do |i|
180
+ 0.upto(anzCpt - 1) do |j|
181
+ unless r[i]
182
+ r[i] = []
183
+ end
184
+ r[i][j] = 0;
185
+ unless rt[i]
186
+ rt[i] = []
187
+ end
188
+ rt[i][j] = 0;
189
+ end
190
+ end
191
+
192
+ 1.upto(anzCpt - 1) do |i|
193
+ rank[i] = 1
194
+ (i - 1).downto(0) do |j|
195
+ temp = create_and_ary(intensions[j], intensions[i])
196
+ if temp == intensions[i]
197
+ unless sup_con[i]
198
+ sup_con[i] = []
199
+ end
200
+ sup_con[i] << j
201
+ r[i][j] = 1
202
+ rt[i][j] = 1
203
+ sup_con[i].each do |elem|
204
+ if r[elem][j] == 1
205
+ r[i][j] = 0
206
+ if rank[elem] >= rank [i]
207
+ rank[i] = rank[elem] + 1
208
+ end
209
+ break
210
+ end
211
+ end
212
+ end
213
+ end
214
+ unless s[rank[i]]
215
+ s[rank[i]] = []
216
+ end
217
+ s[rank[i]] << i
218
+ end
219
+ s = s.collect do |i|
220
+ i ? i : [0]
221
+ end
222
+
223
+ [r, rt, s]
224
+ end
225
+
226
+ def gammaMu(extent, intent, cxt)
227
+
228
+ gamma = []
229
+ mu = []
230
+ invcxt = []
231
+ 0.upto(cxt[0].size - 1) do |i|
232
+ 0.upto(cxt.size - 1) do |k|
233
+ invcxt[i] = [] unless invcxt[i]
234
+ invcxt[i][k] = cxt[k][i]
235
+ end
236
+ end
237
+
238
+ 0.upto(intent.size - 1) do |j|
239
+ 0.upto(cxt.size - 1) do |i|
240
+ gamma[i] = [] unless gamma[i]
241
+ if cxt[i] == intent[j]
242
+ gamma[i][j] = 2
243
+ elsif (!@label_contraction && create_or_ary(cxt[i], intent[j]) == cxt[i])
244
+ gamma[i][j] = 1
245
+ else
246
+ gamma[i][j] = 0
247
+ end
248
+ end
249
+
250
+ 0.upto(invcxt.size - 1) do |i|
251
+ # next unless invcxt[i]
252
+ mu[i] = [] unless mu[i]
253
+ if invcxt[i] == extent[j]
254
+ mu[i][j] = 2
255
+ elsif (!@label_contraction && create_or_ary(invcxt[i], extent[j]) == invcxt[i])
256
+ mu[i][j] = 1
257
+ else
258
+ mu[i][j] = 0
259
+ end
260
+ end
261
+ end
262
+
263
+ [gamma, mu]
264
+ end
265
+
266
+ def changecrosssymbol(char1, char2, lns)
267
+ rel = []
268
+ lns.each do |ln|
269
+ ary = []
270
+ elems = ln.split(//)
271
+ elems.each do |elem|
272
+ if /#{char1}/i =~ elem
273
+ ary << 1
274
+ elsif /#{char2}/i =~ elem
275
+ ary << 0
276
+ end
277
+ end
278
+ rel << ary
279
+ end
280
+ rel
281
+ end
282
+
283
+ ## Generate Graphviz dot data (not creating a file)
284
+ ## For options, see 'rubyfca'
285
+ def generate_dot(opts)
286
+ index_max_width = @concepts.size.to_s.split(//).size
287
+
288
+ clattice = RubyGraphviz.new("clattice", :rankdir => "", :nodesep => "0.4", :ranksep => "0.2")
289
+
290
+ if opts[:circle] and opts[:legend]
291
+ legend = RubyGraphviz.new("legend", :rankdir => "TB", :lebelloc => "t", :centered => "false")
292
+ legend.node_default(:shape => "plaintext")
293
+ legend.edge_default(:color => "gray60") if opts[:coloring]
294
+ legends = []
295
+ end
296
+
297
+ if opts[:circle]
298
+ clattice.node_default(:shape => "circle", :style => "filled")
299
+ clattice.edge_default(:dir => "none", :minlen => "2")
300
+ clattice.edge_default(:color => "gray60") if opts[:coloring]
301
+ else
302
+ clattice.node_default(:shape => "record", :margin => "0.2,0.055")
303
+ clattice.edge_default(:dir => "none")
304
+ clattice.edge_default(:color => "gray60") if opts[:coloring]
305
+ end
306
+
307
+ 0.upto(@concepts.size - 1) do |i|
308
+ objfull = []
309
+ attrfull = []
310
+ 0.upto(@gammaM.size - 1) do |j|
311
+ if @gammaM[j][i] == 2
312
+ obj = opts[:full] ? @objects[j] + " " + [0x261C].pack("U") : @objects[j]
313
+ objfull << obj
314
+ elsif @gammaM[j][i] == 1
315
+ objfull << @objects[j]
316
+ end
317
+ end
318
+ 0.upto(@muM.size - 1) do |k|
319
+ if @muM[k][i] == 2
320
+ att = opts[:full] ? @attributes[k] + " " + [0x261C].pack("U") : @attributes[k]
321
+ attrfull << att
322
+ elsif @muM[k][i] == 1
323
+ attrfull << @attributes[k]
324
+ end
325
+ end
326
+
327
+ concept_id = i + 1
328
+
329
+ attr_str = attrfull.join('<br />')
330
+ attr_str = attr_str == "" ? " " : attr_str
331
+ attr_color = (!opts[:coloring] || /\A\s+\z/ =~ attr_str) ? "white" : "lightblue"
332
+ obj_str = objfull.join('<br />')
333
+ obj_str = obj_str == "" ? " " : obj_str
334
+ obj_color = (!opts[:coloring] || /\A\s+\z/ =~ obj_str) ? "white" : "pink"
335
+
336
+ label = "<<table border=\"0\" cellborder=\"1\" cellspacing=\"0\">" +
337
+ "<tr><td balign=\"left\" align=\"left\" bgcolor=\"#{attr_color}\">#{attr_str}</td></tr>" +
338
+ "<tr><td balign=\"left\" align=\"left\" bgcolor=\"#{obj_color}\">#{obj_str}</td></tr>" +
339
+ "</table>>"
340
+
341
+ if opts[:circle] and opts[:legend]
342
+
343
+ leg = "<<table border=\"0\" cellborder=\"1\" cellspacing=\"0\">" +
344
+ "<tr><td rowspan=\"2\">#{concept_id}</td><td balign=\"left\" align=\"left\" bgcolor=\"#{attr_color}\">#{attr_str}</td></tr>" +
345
+ "<tr><td balign=\"left\" align=\"left\" bgcolor=\"#{obj_color}\">#{obj_str}</td></tr>" +
346
+ "</table>>"
347
+
348
+ if !attrfull.empty? and !objfull.empty?
349
+ legend.node("cl#{concept_id}k", :label => concept_id, :style => "invis")
350
+ legend.node("cl#{concept_id}v", :label => leg, :fillcolor => "white")
351
+ legend.rank("cl#{concept_id}k", "cl#{concept_id}v", :style => "invis", :length => "0.0")
352
+ if legends[-1]
353
+ legend.edge("cl#{legends[-1]}k", "cl#{concept_id}k", :style => "invis", :length => "0.0")
354
+ end
355
+ legends << concept_id
356
+ end
357
+ end
358
+
359
+ if opts[:circle]
360
+ clattice.node("c#{i}", :width => "0.5", :fontsize => "14.0", :label => concept_id)
361
+ else
362
+ clattice.node("c#{i}", :label => label, :shape => "plaintext",
363
+ :height => "0.0", :width => "0.0", :margin => "0.0")
364
+ end
365
+ end
366
+
367
+ 0.upto(@relM.size - 1) do |i|
368
+ 0.upto(@relM.size - 1) do |j|
369
+ if @relM[i][j] == 1
370
+ clattice.edge("c#{i}", "c#{j}")
371
+ end
372
+ end
373
+ end
374
+
375
+ clattice.subgraph(legend) if opts[:circle] and opts[:legend]
376
+ clattice.to_dot
377
+ end
378
+
379
+ ## Generate an actual graphic file (Graphviz dot needs to be installed properly)
380
+ def generate_img(outfile, image_type, opts)
381
+ dot = generate_dot(opts)
382
+ isthere_dot = `dot -V 2>&1`
383
+ if isthere_dot !~ /dot.*version/i
384
+ showerror("Graphviz's dot program cannot be found.", 1)
385
+ else
386
+ if opts[:straight]
387
+ cmd = "dot | neato -n -T#{image_type} -o#{outfile} 2>rubyfca.log"
388
+ else
389
+ cmd = "dot -T#{image_type} -o#{outfile} 2>rubyfca.log"
390
+ end
391
+ IO.popen(cmd, 'r+') do |io|
392
+ io.puts dot
393
+ end
394
+ end
395
+ end
396
+ end
397
+