rubyfca 0.2.10 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|