rubyfca 0.2.10 → 0.3.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/.gitignore +24 -3
- data/Gemfile +4 -0
- data/LICENSE +674 -20
- data/README.md +129 -0
- data/Rakefile +8 -51
- data/bin/rubyfca +61 -57
- data/lib/{ruby_graphviz.rb → rubyfca/ruby_graphviz.rb} +22 -22
- data/lib/rubyfca/version.rb +5 -0
- data/lib/rubyfca.rb +152 -139
- data/rubyfca.gemspec +20 -55
- data/samples/fca-source-result.png +0 -0
- data/samples/numbers-result-compact.png +0 -0
- data/samples/numbers-result.png +0 -0
- data/samples/numbers-source.png +0 -0
- data/samples/sample_01.svg +202 -0
- data/samples/sample_02.svg +178 -0
- data/samples/sample_03.svg +244 -0
- data/samples/xlsx-sample.png +0 -0
- data/test/rubyfca_test.rb +55 -4
- data/test/test_data_.csv +7 -0
- data/test/test_expected/test_result_01.svg +202 -0
- data/test/test_expected/test_result_02.svg +178 -0
- data/test/test_expected/test_result_03.svg +481 -0
- data/test/test_expected/test_result_04.svg +314 -0
- data/test/test_input/test_data.csv +7 -0
- data/test/{test_data.cxt → test_input/test_data.cxt} +5 -5
- data/test/test_input/test_data.xlsx +0 -0
- data/test/test_input/test_data_numbers.xlsx +0 -0
- data/test_data_numbers.svg +272 -0
- data/test_data_numbers_a.svg +272 -0
- metadata +107 -58
- data/README.rdoc +0 -45
- data/VERSION +0 -1
- data/lib/trollop.rb +0 -739
- data/test/test_helper.rb +0 -10
data/lib/rubyfca.rb
CHANGED
@@ -1,35 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
## lib/rubyfca.rb -- Formal Concept Analysis tool in Ruby
|
2
4
|
## Author:: Yoichiro Hasebe (mailto: yohasebe@gmail.com)
|
3
|
-
## Kow Kuroda (mailto: kuroda@nict.go.jp)
|
4
|
-
## Copyright:: Copyright
|
5
|
+
## Kow Kuroda (mailto: kuroda@nict.go.jp)
|
6
|
+
## Copyright:: Copyright 2009-2023 Yoichiro Hasebe and Kow Kuroda
|
5
7
|
## License:: GNU GPL version 3
|
6
8
|
|
7
|
-
|
9
|
+
require "csv"
|
10
|
+
require "optimist"
|
11
|
+
require "roo"
|
8
12
|
|
9
|
-
|
10
|
-
|
13
|
+
require_relative "./rubyfca/ruby_graphviz"
|
14
|
+
require_relative "./rubyfca/version"
|
11
15
|
|
12
16
|
private
|
13
17
|
|
14
|
-
## Take two arrays each consisting of 0s and 1s and create their logical disjunction
|
18
|
+
## Take two arrays each consisting of 0s and 1s and create their logical disjunction
|
15
19
|
def create_and_ary(a, b)
|
16
20
|
return false if (s = a.size) != b.size
|
17
|
-
|
18
|
-
|
21
|
+
|
22
|
+
(0..(s - 1)).to_a.map { |i| a[i].to_i & b[i].to_i }
|
19
23
|
end
|
20
24
|
|
21
25
|
## Take two arrays each consisting of 0s and 1s and create their logical conjunction
|
22
26
|
def create_or_ary(a, b)
|
23
27
|
return false if (s = a.size) != b.size
|
24
|
-
|
25
|
-
|
28
|
+
|
29
|
+
(0..(s - 1)).to_a.map { |i| a[i].to_i | b[i].to_i }
|
26
30
|
end
|
27
31
|
|
28
32
|
public
|
29
33
|
|
30
34
|
## Take care of errors
|
31
35
|
def showerror(sentence, severity)
|
32
|
-
if severity
|
36
|
+
if severity.zero?
|
33
37
|
puts "Warning: #{sentence} The output may not be meaningful."
|
34
38
|
elsif severity == 1
|
35
39
|
puts "Error: #{sentence} No output generated."
|
@@ -39,25 +43,27 @@ end
|
|
39
43
|
|
40
44
|
## Basic structure of the code is the same as Fcastone written in Perl by Uta Priss
|
41
45
|
class FormalContext
|
42
|
-
|
43
46
|
## Converte cxt data to three basic structures of objects, attributes, and matrix
|
44
|
-
def initialize(input, mode, label_contraction
|
45
|
-
if input.
|
46
|
-
|
47
|
-
end
|
47
|
+
def initialize(input, mode, label_contraction: false)
|
48
|
+
showerror("File is empty", 1) if input.empty?
|
49
|
+
input = input.gsub(" ", " ")
|
48
50
|
begin
|
49
51
|
case mode
|
50
52
|
when /cxt\z/
|
51
53
|
read_cxt(input)
|
52
54
|
when /csv\z/
|
53
55
|
read_csv(input)
|
56
|
+
when /xlsx\z/
|
57
|
+
read_xlsx(input)
|
54
58
|
end
|
55
|
-
rescue => e
|
56
|
-
|
59
|
+
rescue StandardError => e
|
60
|
+
pp e.message
|
61
|
+
pp e.backtrace
|
62
|
+
showerror("Input data contains a syntactic problem.", 1)
|
57
63
|
end
|
58
64
|
@label_contraction = label_contraction
|
59
65
|
end
|
60
|
-
|
66
|
+
|
61
67
|
## process cxt data
|
62
68
|
def read_cxt(input)
|
63
69
|
lines = input.split
|
@@ -65,53 +71,65 @@ class FormalContext
|
|
65
71
|
if (lines[0] !~ /B/i || (2 * lines[1].to_i + lines[2].to_i + t1) != lines.size)
|
66
72
|
showerror("Wrong cxt format!", 1)
|
67
73
|
end
|
68
|
-
@objects
|
69
|
-
@attributes = lines[(lines[1].to_i + t1)
|
70
|
-
lines
|
71
|
-
@matrix
|
74
|
+
@objects = lines[t1..(lines[1].to_i + t1 - 1)]
|
75
|
+
@attributes = lines[(lines[1].to_i + t1)..(lines[1].to_i + lines[2].to_i + t1 - 1)]
|
76
|
+
lines = lines[(lines[1].to_i + lines[2].to_i + t1)..lines.size]
|
77
|
+
@matrix = changecrosssymbol("X", "\\.", lines)
|
72
78
|
end
|
73
|
-
|
79
|
+
|
74
80
|
# process csv data using the standard csv library
|
75
81
|
def read_csv(input)
|
76
82
|
input = remove_blank(input)
|
77
83
|
data = CSV.parse(input)
|
78
|
-
@objects = trim_ary(data.transpose.first[1
|
79
|
-
@attributes = trim_ary(data.first[1
|
84
|
+
@objects = trim_ary(data.transpose.first[1..])
|
85
|
+
@attributes = trim_ary(data.first[1..])
|
86
|
+
@matrix = []
|
87
|
+
data[1..].each do |line|
|
88
|
+
@matrix << line[1..].collect { |cell| /x/i =~ cell ? 1 : 0 }
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# process xlsx data using the roo gem
|
93
|
+
def read_xlsx(file_path)
|
94
|
+
xlsx = Roo::Spreadsheet.open(file_path, extension: :xlsx)
|
95
|
+
@objects = xlsx.sheet(0).column(1)[1..]
|
96
|
+
@attributes = xlsx.sheet(0).row(1)[1..]
|
80
97
|
@matrix = []
|
81
|
-
|
82
|
-
|
98
|
+
(2..xlsx.sheet(0).last_row).each do |row|
|
99
|
+
matrix_row = []
|
100
|
+
(2..xlsx.sheet(0).last_column).each do |col|
|
101
|
+
matrix_row << (xlsx.sheet(0).cell(row, col) =~ /x/i ? 1 : 0)
|
102
|
+
end
|
103
|
+
@matrix << matrix_row
|
83
104
|
end
|
84
105
|
end
|
85
|
-
|
106
|
+
|
86
107
|
def remove_blank(input)
|
87
|
-
blank_removed = ""
|
88
|
-
input.each do |line|
|
89
|
-
|
90
|
-
|
108
|
+
blank_removed = +""
|
109
|
+
input.split("\n").each do |line|
|
110
|
+
line = line.strip
|
111
|
+
unless /\A\s*\z/ =~ line
|
112
|
+
blank_removed << line + "\n"
|
91
113
|
end
|
92
114
|
end
|
93
115
|
blank_removed
|
94
116
|
end
|
95
|
-
|
117
|
+
|
96
118
|
def trim_ary(ary)
|
97
|
-
|
98
|
-
cell.strip
|
99
|
-
end
|
100
|
-
newary
|
119
|
+
ary.map(&:strip)
|
101
120
|
end
|
102
|
-
|
121
|
+
|
103
122
|
## Apply a formal concept analysis on the matrix
|
104
|
-
def
|
123
|
+
def calcurate
|
105
124
|
@concepts, @extM, @intM = ganter_alg(@matrix)
|
106
125
|
@relM, @reltrans, @rank = create_rel(@intM)
|
107
126
|
@gammaM, @muM = gammaMu(@extM, @intM, @matrix)
|
108
127
|
end
|
109
128
|
|
110
|
-
## This is an implementation of an algorithm described by Bernhard Ganter
|
129
|
+
## This is an implementation of an algorithm described by Bernhard Ganter
|
111
130
|
## in "Two basic algorithms in concept analysis." Technische Hochschule
|
112
131
|
## Darmstadt, FB4-Preprint, 831, 1984.
|
113
132
|
def ganter_alg(matrix)
|
114
|
-
|
115
133
|
m = matrix
|
116
134
|
|
117
135
|
## all arrays except @idx are arrays of arrays of 0's and 1's
|
@@ -122,25 +140,25 @@ class FormalContext
|
|
122
140
|
int_B = []
|
123
141
|
endkey = []
|
124
142
|
temp = []
|
125
|
-
|
143
|
+
|
126
144
|
## the level in the lattice from the top
|
127
|
-
lvl = 0
|
145
|
+
lvl = 0
|
128
146
|
## is lower than the index of leftmost attr
|
129
|
-
idx[lvl] = -1
|
147
|
+
idx[lvl] = -1
|
130
148
|
## number of attr. and objs
|
131
|
-
anzM = m.size
|
132
|
-
## only needed for initialization
|
149
|
+
anzM = m.size
|
150
|
+
## only needed for initialization
|
133
151
|
anzG = m[0].size
|
134
152
|
## initialize extA[0] = [1,...,1]
|
135
|
-
anzG.times do
|
136
|
-
|
153
|
+
anzG.times do
|
154
|
+
unless ext_A[0]
|
137
155
|
ext_A[0] = []
|
138
156
|
end
|
139
157
|
ext_A[0] << 1
|
140
158
|
end
|
141
159
|
## initialize extB[0] = [0,...,0]
|
142
|
-
anzM.times do
|
143
|
-
|
160
|
+
anzM.times do
|
161
|
+
unless int_B[0]
|
144
162
|
int_B[0] = []
|
145
163
|
end
|
146
164
|
int_B[0] << 0
|
@@ -148,7 +166,7 @@ class FormalContext
|
|
148
166
|
anzCpt = 0
|
149
167
|
extension[0] = ext_A[0]
|
150
168
|
intension[0] = int_B[0]
|
151
|
-
anzM.times do
|
169
|
+
anzM.times do
|
152
170
|
endkey << 1
|
153
171
|
end
|
154
172
|
|
@@ -157,13 +175,11 @@ class FormalContext
|
|
157
175
|
(anzM - 1).downto(0) do |i|
|
158
176
|
breakkey = false
|
159
177
|
if (int_B[lvl][i] != 1)
|
160
|
-
while (i < idx[lvl])
|
161
|
-
lvl -= 1
|
162
|
-
end
|
178
|
+
lvl -= 1 while (i < idx[lvl])
|
163
179
|
idx[lvl + 1] = i
|
164
180
|
ext_A[lvl + 1] = create_and_ary(ext_A[lvl], m[i])
|
165
181
|
0.upto(i - 1) do |j|
|
166
|
-
if(!breakkey && int_B[lvl][j] != 1)
|
182
|
+
if (!breakkey && int_B[lvl][j] != 1)
|
167
183
|
temp = create_and_ary(ext_A[lvl + 1], m[j])
|
168
184
|
if temp == ext_A[lvl + 1]
|
169
185
|
breakkey = true
|
@@ -173,7 +189,7 @@ class FormalContext
|
|
173
189
|
unless breakkey
|
174
190
|
int_B[lvl + 1] = int_B[lvl].dup
|
175
191
|
int_B[lvl + 1][i] = 1
|
176
|
-
(i+1).upto(anzM - 1) do |k|
|
192
|
+
(i + 1).upto(anzM - 1) do |k|
|
177
193
|
if int_B[lvl + 1][k] != 1
|
178
194
|
temp = create_and_ary(ext_A[lvl + 1], m[k])
|
179
195
|
if temp == ext_A[lvl + 1]
|
@@ -187,7 +203,7 @@ class FormalContext
|
|
187
203
|
intension[anzCpt] = int_B[lvl]
|
188
204
|
break
|
189
205
|
end
|
190
|
-
end
|
206
|
+
end
|
191
207
|
end
|
192
208
|
end
|
193
209
|
|
@@ -207,7 +223,7 @@ class FormalContext
|
|
207
223
|
[c, intension, extension]
|
208
224
|
end
|
209
225
|
|
210
|
-
## Output arrayconsists of the following:
|
226
|
+
## Output arrayconsists of the following:
|
211
227
|
## r (subconcept superconcept relation)
|
212
228
|
## rt (trans. closure of r)
|
213
229
|
## s (ranked concepts)
|
@@ -224,11 +240,11 @@ class FormalContext
|
|
224
240
|
unless r[i]
|
225
241
|
r[i] = []
|
226
242
|
end
|
227
|
-
r[i][j] = 0
|
243
|
+
r[i][j] = 0
|
228
244
|
unless rt[i]
|
229
245
|
rt[i] = []
|
230
246
|
end
|
231
|
-
rt[i][j] = 0
|
247
|
+
rt[i][j] = 0
|
232
248
|
end
|
233
249
|
end
|
234
250
|
|
@@ -258,16 +274,15 @@ class FormalContext
|
|
258
274
|
s[rank[i]] = []
|
259
275
|
end
|
260
276
|
s[rank[i]] << i
|
261
|
-
end
|
277
|
+
end
|
262
278
|
s = s.collect do |i|
|
263
|
-
i
|
279
|
+
i || [0]
|
264
280
|
end
|
265
281
|
|
266
282
|
[r, rt, s]
|
267
|
-
end
|
283
|
+
end
|
268
284
|
|
269
285
|
def gammaMu(extent, intent, cxt)
|
270
|
-
|
271
286
|
gamma = []
|
272
287
|
mu = []
|
273
288
|
invcxt = []
|
@@ -281,31 +296,31 @@ class FormalContext
|
|
281
296
|
0.upto(intent.size - 1) do |j|
|
282
297
|
0.upto(cxt.size - 1) do |i|
|
283
298
|
gamma[i] = [] unless gamma[i]
|
284
|
-
if cxt[i] == intent[j]
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
299
|
+
gamma[i][j] = if cxt[i] == intent[j]
|
300
|
+
2
|
301
|
+
elsif (!@label_contraction && create_or_ary(cxt[i], intent[j]) == cxt[i])
|
302
|
+
1
|
303
|
+
else
|
304
|
+
0
|
305
|
+
end
|
291
306
|
end
|
292
|
-
|
307
|
+
|
293
308
|
0.upto(invcxt.size - 1) do |i|
|
294
309
|
# next unless invcxt[i]
|
295
310
|
mu[i] = [] unless mu[i]
|
296
|
-
if invcxt[i] == extent[j]
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
311
|
+
mu[i][j] = if invcxt[i] == extent[j]
|
312
|
+
2
|
313
|
+
elsif (!@label_contraction && create_or_ary(invcxt[i], extent[j]) == invcxt[i])
|
314
|
+
1
|
315
|
+
else
|
316
|
+
0
|
317
|
+
end
|
303
318
|
end
|
304
319
|
end
|
305
|
-
|
320
|
+
|
306
321
|
[gamma, mu]
|
307
322
|
end
|
308
|
-
|
323
|
+
|
309
324
|
def changecrosssymbol(char1, char2, lns)
|
310
325
|
rel = []
|
311
326
|
lns.each do |ln|
|
@@ -326,36 +341,36 @@ class FormalContext
|
|
326
341
|
## Generate Graphviz dot data (not creating a file)
|
327
342
|
## For options, see 'rubyfca'
|
328
343
|
def generate_dot(opts)
|
329
|
-
index_max_width = @concepts.size.to_s.split(//).size
|
330
|
-
|
331
|
-
|
332
|
-
clattice = RubyGraphviz.new("clattice", :
|
333
|
-
|
334
|
-
if opts[:circle]
|
335
|
-
legend
|
336
|
-
legend.node_default(:
|
337
|
-
legend.edge_default(:
|
344
|
+
# index_max_width = @concepts.size.to_s.split(//).size
|
345
|
+
nodesep = opts[:nodesep] ? opts[:nodesep].to_s : "0.4"
|
346
|
+
ranksep = opts[:ranksep] ? opts[:ranksep].to_s : "0.2"
|
347
|
+
clattice = RubyGraphviz.new("clattice", rankdir: "", nodesep: nodesep, ranksep: ranksep)
|
348
|
+
|
349
|
+
if opts[:circle] && opts[:legend]
|
350
|
+
legend = RubyGraphviz.new("legend", rankdir: "TB", lebelloc: "t", centered: "false")
|
351
|
+
legend.node_default(shape: "plaintext")
|
352
|
+
legend.edge_default(color: "gray60") if opts[:coloring]
|
338
353
|
legends = []
|
339
354
|
end
|
340
355
|
|
341
356
|
if opts[:circle]
|
342
|
-
clattice.node_default(:
|
343
|
-
clattice.edge_default(:
|
344
|
-
clattice.edge_default(:
|
357
|
+
clattice.node_default(shape: "circle", style: "filled")
|
358
|
+
clattice.edge_default(dir: "none", minlen: "2")
|
359
|
+
clattice.edge_default(color: "gray60") if opts[:coloring]
|
345
360
|
else
|
346
|
-
clattice.node_default(:
|
347
|
-
clattice.edge_default(:
|
348
|
-
clattice.edge_default(:
|
361
|
+
clattice.node_default(shape: "record", margin: "0.2,0.055")
|
362
|
+
clattice.edge_default(dir: "none")
|
363
|
+
clattice.edge_default(color: "gray60") if opts[:coloring]
|
349
364
|
end
|
350
|
-
|
365
|
+
|
351
366
|
0.upto(@concepts.size - 1) do |i|
|
352
367
|
objfull = []
|
353
|
-
attrfull = []
|
368
|
+
attrfull = []
|
354
369
|
0.upto(@gammaM.size - 1) do |j|
|
355
370
|
if @gammaM[j][i] == 2
|
356
|
-
|
371
|
+
# pointing finger does not appear correctly in eps...
|
357
372
|
# obj = opts[:full] ? @objects[j] + " " + [0x261C].pack("U") : @objects[j]
|
358
|
-
obj = opts[:full] ? @objects[j] + "*" : @objects[j]
|
373
|
+
obj = opts[:full] ? @objects[j].to_s + "*" : @objects[j].to_s
|
359
374
|
objfull << obj
|
360
375
|
elsif @gammaM[j][i] == 1
|
361
376
|
objfull << @objects[j]
|
@@ -363,9 +378,9 @@ class FormalContext
|
|
363
378
|
end
|
364
379
|
0.upto(@muM.size - 1) do |k|
|
365
380
|
if @muM[k][i] == 2
|
366
|
-
|
381
|
+
# pointing finger does not appear correctly in eps...
|
367
382
|
# att = opts[:full] ? @attributes[k] + " " + [0x261C].pack("U") : @attributes[k]
|
368
|
-
att = opts[:full] ? @attributes[k] + "*" : @attributes[k]
|
383
|
+
att = opts[:full] ? @attributes[k].to_s + "*" : @attributes[k].to_s
|
369
384
|
attrfull << att
|
370
385
|
elsif @muM[k][i] == 1
|
371
386
|
attrfull << @attributes[k]
|
@@ -374,21 +389,21 @@ class FormalContext
|
|
374
389
|
|
375
390
|
concept_id = i + 1
|
376
391
|
|
377
|
-
attr_str = attrfull.join(
|
392
|
+
attr_str = attrfull.join("<br />")
|
378
393
|
attr_str = attr_str == "" ? " " : attr_str
|
379
|
-
|
380
|
-
if opts[:coloring]
|
394
|
+
|
395
|
+
if opts[:coloring].zero? || /\A\s+\z/ =~ attr_str
|
381
396
|
attr_color = "white"
|
382
397
|
elsif opts[:coloring] == 1
|
383
398
|
attr_color = "lightblue"
|
384
399
|
elsif opts[:coloring] == 2
|
385
400
|
attr_color = "gray87"
|
386
401
|
end
|
387
|
-
|
388
|
-
obj_str = objfull.join(
|
402
|
+
|
403
|
+
obj_str = objfull.join("<br />")
|
389
404
|
obj_str = obj_str == "" ? " " : obj_str
|
390
405
|
|
391
|
-
if opts[:coloring]
|
406
|
+
if opts[:coloring].zero? || /\A\s+\z/ =~ obj_str
|
392
407
|
obj_color = "white"
|
393
408
|
elsif opts[:coloring] == 1
|
394
409
|
obj_color = "pink"
|
@@ -396,37 +411,36 @@ class FormalContext
|
|
396
411
|
obj_color = "gray92"
|
397
412
|
end
|
398
413
|
|
399
|
-
label = "<<table border=\"0\" cellborder=\"1\" cellspacing=\"0\">"
|
400
|
-
|
401
|
-
|
402
|
-
|
414
|
+
label = "<<table border=\"0\" cellborder=\"1\" cellspacing=\"0\">" \
|
415
|
+
"<tr><td balign=\"left\" align=\"left\" bgcolor=\"#{attr_color}\">#{attr_str}</td></tr>" \
|
416
|
+
"<tr><td balign=\"left\" align=\"left\" bgcolor=\"#{obj_color}\">#{obj_str}</td></tr>" \
|
417
|
+
"</table>>"
|
403
418
|
|
404
|
-
if opts[:circle]
|
419
|
+
if opts[:circle] && opts[:legend]
|
405
420
|
|
406
|
-
leg = "<<table border=\"0\" cellborder=\"1\" cellspacing=\"0\">"
|
407
|
-
|
408
|
-
|
409
|
-
|
421
|
+
leg = "<<table border=\"0\" cellborder=\"1\" cellspacing=\"0\">" \
|
422
|
+
"<tr><td rowspan=\"2\">#{concept_id}</td><td balign=\"left\" align=\"left\" bgcolor=\"#{attr_color}\">#{attr_str}</td></tr>" \
|
423
|
+
"<tr><td balign=\"left\" align=\"left\" bgcolor=\"#{obj_color}\">#{obj_str}</td></tr>" \
|
424
|
+
"</table>>"
|
410
425
|
|
411
|
-
if !attrfull.empty?
|
412
|
-
legend.node("cl#{concept_id}k", :
|
413
|
-
legend.node("cl#{concept_id}v", :
|
414
|
-
legend.rank("cl#{concept_id}k", "cl#{concept_id}v", :
|
426
|
+
if !attrfull.empty? || !objfull.empty?
|
427
|
+
legend.node("cl#{concept_id}k", label: concept_id, style: "invis")
|
428
|
+
legend.node("cl#{concept_id}v", label: leg, fillcolor: "white")
|
429
|
+
legend.rank("cl#{concept_id}k", "cl#{concept_id}v", style: "invis", length: "0.0")
|
415
430
|
if legends[-1]
|
416
|
-
legend.edge("cl#{legends[-1]}k", "cl#{concept_id}k", :
|
431
|
+
legend.edge("cl#{legends[-1]}k", "cl#{concept_id}k", style: "invis", length: "0.0")
|
417
432
|
end
|
418
433
|
legends << concept_id
|
419
434
|
end
|
420
435
|
end
|
421
|
-
|
436
|
+
|
422
437
|
if opts[:circle]
|
423
|
-
clattice.node("c#{i}", :
|
438
|
+
clattice.node("c#{i}", width: "0.5", fontsize: "14.0", label: concept_id)
|
424
439
|
else
|
425
|
-
clattice.node("c#{i}", :label
|
426
|
-
:height => "0.0", :width => "0.0", :margin => "0.0")
|
440
|
+
clattice.node("c#{i}", label: label, shape: "plaintext", height: "0.0", width: "0.0", margin: "0.0")
|
427
441
|
end
|
428
442
|
end
|
429
|
-
|
443
|
+
|
430
444
|
0.upto(@relM.size - 1) do |i|
|
431
445
|
0.upto(@relM.size - 1) do |j|
|
432
446
|
if @relM[i][j] == 1
|
@@ -434,11 +448,11 @@ class FormalContext
|
|
434
448
|
end
|
435
449
|
end
|
436
450
|
end
|
437
|
-
|
438
|
-
clattice.subgraph(legend) if opts[:circle]
|
451
|
+
|
452
|
+
clattice.subgraph(legend) if opts[:circle] && opts[:legend]
|
439
453
|
clattice.to_dot
|
440
454
|
end
|
441
|
-
|
455
|
+
|
442
456
|
## Generate an actual graphic file (Graphviz dot needs to be installed properly)
|
443
457
|
def generate_img(outfile, image_type, opts)
|
444
458
|
dot = generate_dot(opts)
|
@@ -446,15 +460,14 @@ class FormalContext
|
|
446
460
|
if isthere_dot !~ /dot.*version/i
|
447
461
|
showerror("Graphviz's dot program cannot be found.", 1)
|
448
462
|
else
|
449
|
-
if opts[:straight]
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
IO.popen(cmd,
|
463
|
+
cmd = if opts[:straight]
|
464
|
+
"dot | neato -n -T#{image_type} -o#{outfile} 2>rubyfca.log"
|
465
|
+
else
|
466
|
+
"dot -T#{image_type} -o#{outfile} 2>rubyfca.log"
|
467
|
+
end
|
468
|
+
IO.popen(cmd, "r+") do |io|
|
455
469
|
io.puts dot
|
456
470
|
end
|
457
471
|
end
|
458
|
-
end
|
472
|
+
end
|
459
473
|
end
|
460
|
-
|
data/rubyfca.gemspec
CHANGED
@@ -1,60 +1,25 @@
|
|
1
|
-
#
|
2
|
-
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
-
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
-
# -*- encoding: utf-8 -*-
|
1
|
+
# frozen_string_literal: true
|
5
2
|
|
6
|
-
|
7
|
-
s.name = %q{rubyfca}
|
8
|
-
s.version = "0.2.10"
|
3
|
+
require File.expand_path("lib/rubyfca/version", __dir__)
|
9
4
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
"LICENSE",
|
19
|
-
"README.rdoc"
|
20
|
-
]
|
21
|
-
s.files = [
|
22
|
-
".document",
|
23
|
-
".gitignore",
|
24
|
-
"LICENSE",
|
25
|
-
"README.rdoc",
|
26
|
-
"Rakefile",
|
27
|
-
"VERSION",
|
28
|
-
"bin/rubyfca",
|
29
|
-
"lib/ruby_graphviz.rb",
|
30
|
-
"lib/rubyfca.rb",
|
31
|
-
"lib/trollop.rb",
|
32
|
-
"rubyfca.gemspec",
|
33
|
-
"test/rubyfca_test.rb",
|
34
|
-
"test/test_data.cxt",
|
35
|
-
"test/test_helper.rb"
|
36
|
-
]
|
37
|
-
s.homepage = %q{http://github.com/yohasebe/rubyfca}
|
38
|
-
s.rdoc_options = ["--charset=UTF-8"]
|
39
|
-
s.require_paths = ["lib"]
|
40
|
-
s.rubygems_version = %q{1.3.5}
|
41
|
-
s.summary = %q{Command line Formal Concept Ananlysis (FCA) tool written in Ruby}
|
42
|
-
s.test_files = [
|
43
|
-
"test/rubyfca_test.rb",
|
44
|
-
"test/test_helper.rb"
|
45
|
-
]
|
5
|
+
Gem::Specification.new do |gem|
|
6
|
+
gem.name = "rubyfca"
|
7
|
+
gem.version = RubyFCA::VERSION
|
8
|
+
gem.authors = ["Yoichiro Hasebe", "Kow Kuroda"]
|
9
|
+
gem.email = ["yohasebe@gmail.com"]
|
10
|
+
gem.summary = "Command line FCA tool written in Ruby"
|
11
|
+
gem.description = "Command line Formal Concept Analysis (FCA) tool written in Ruby"
|
12
|
+
gem.homepage = "http://github.com/yohasebe/rubyfca"
|
46
13
|
|
47
|
-
|
48
|
-
|
49
|
-
|
14
|
+
gem.required_ruby_version = ">= 2.6.10"
|
15
|
+
gem.licenses = ["GPL-3.0"]
|
16
|
+
gem.files = `git ls-files`.split("\n")
|
17
|
+
gem.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
|
18
|
+
gem.require_paths = ["lib"]
|
50
19
|
|
51
|
-
|
52
|
-
s.add_development_dependency(%q<thoughtbot-shoulda>, [">= 0"])
|
53
|
-
else
|
54
|
-
s.add_dependency(%q<thoughtbot-shoulda>, [">= 0"])
|
55
|
-
end
|
56
|
-
else
|
57
|
-
s.add_dependency(%q<thoughtbot-shoulda>, [">= 0"])
|
58
|
-
end
|
59
|
-
end
|
20
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
60
21
|
|
22
|
+
gem.add_dependency "optimist"
|
23
|
+
gem.add_dependency "roo"
|
24
|
+
gem.add_development_dependency "minitest"
|
25
|
+
end
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|