rubyfca 0.2.10 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md ADDED
@@ -0,0 +1,129 @@
1
+ # 💭 RubyFCA
2
+
3
+ Command line tool for Formal Concept Analysis (FCA) written in Ruby.
4
+
5
+ <kbd>
6
+ <img src="./samples/fca-source-result.png" width="800px"/>
7
+ </kbd>
8
+
9
+
10
+ ## Change Log
11
+
12
+ - Microsoft Excel `.xlsx` files supported [June 18, 2023]
13
+ - Sample files added [June 18, 2023]
14
+
15
+ ## Features
16
+
17
+ * Converts data in XLSX (Microsoft Excel), CSV (comma-separated values), or CXT ([Conexp](https://github.com/fcatools) and generate a Graphviz DOT file, a SVG/EPS vector image file, or /a PNG/JPG bitmap file.
18
+ * Adopts the Ganter algorithm (with reference to its Perl implementation of [Fcastone](https://upriss.github.io/fcastone) by Uta Priss).
19
+
20
+ ## Dependencies
21
+
22
+ - [Graphviz](https://graphviz.org/)
23
+
24
+ For example, to install Graphviz using Homebrew on MacOS, execute the following command
25
+
26
+ brew install graphviz
27
+
28
+ ## Installation
29
+
30
+ Install the gem:
31
+
32
+ gem install rubyfca
33
+
34
+ ## How to Use
35
+
36
+ RubuFCA converts Conexp CXT data to Graphviz dot format.
37
+
38
+ Usage:
39
+ rubyfca [options] <source file> <output file>
40
+
41
+ where:
42
+ <source file>
43
+ ".xlsx", ".csv" ,".cxt"
44
+ <output file>
45
+ ."svg", ".png", ".jpg", ".eps", or ".dot"
46
+ [options]:
47
+ --full, -f: Do not contract concept labels
48
+ --coloring, -c <i>: Color concept nodes [0 = none (default), 1 =
49
+ lightblue/pink, 2 = monochrome] (default: 0)
50
+ --straight, -s: Straighten edges (available when output format is
51
+ either png, jpg, svg, pdf, or eps)
52
+ --nodesep, -n <f>: Size of separation between sister nodes (from 0.1 to
53
+ 5.0) (default: 0.4)
54
+ --ranksep, -r <f>: Size of separation between ranks (from 0.1 to 5.0)
55
+ (default: 0.2)
56
+ --legend, -l: Print the legend of concept nodes (available only when
57
+ using circle node shape)
58
+ --circle, -i: Use circle shaped concept nodes
59
+ --version, -v: Print version and exit
60
+ --help, -h: Show this message
61
+
62
+ ## Examples
63
+
64
+ ### Input Data
65
+
66
+ #### XLSX (Excel)
67
+
68
+ <kbd>
69
+ <img src="./samples/xlsx-sample.png" width="800px"/>
70
+ </kbd>
71
+
72
+ #### CSV
73
+
74
+ ```
75
+ , Ostrich , Sparrow , Eagle , Lion , Bonobo , Human being
76
+ bird , X , X , X , , ,
77
+ mammal , , , , X , X , X
78
+ ape , , , , , X , X
79
+ flying , , X , X , , ,
80
+ preying , , , X , X , ,
81
+ talking , , , , , , X
82
+ ```
83
+
84
+ #### CXT
85
+
86
+ ```
87
+ B
88
+
89
+ 6
90
+ 6
91
+
92
+ Ostrich
93
+ Sparrow
94
+ Eagle
95
+ Lion
96
+ Bonobo
97
+ Human being
98
+ bird
99
+ mammal
100
+ ape
101
+ flying
102
+ preying
103
+ talking
104
+ XXX...
105
+ ...XXX
106
+ ....XX
107
+ .XX...
108
+ ..XX..
109
+ .....X
110
+ ```
111
+
112
+ ## Output
113
+
114
+ `rubyfca input_file output_file --coloring 1 --full --nodesep 0.8 --ranksep 0.3 --straight`
115
+
116
+ <img src="./samples/sample_01.svg" width="500px"/>
117
+
118
+
119
+ `rubyfca input_file output_file --coloring 2 --nodesep 0.5 --ranksep 0.3`
120
+
121
+ <img src="./samples/sample_02.svg" width="400px"/>
122
+
123
+ `rubyfca input_file output_file --circle --legend --coloring 1 --full --nodesep 0.8 --ranksep 0.3 --straight`
124
+
125
+ <img src="./samples/sample_03.svg" width="800px"/>
126
+
127
+ ## Copyright
128
+
129
+ Copyright (c) 2009-2023 Yoichiro Hasebe and Kow Kuroda. See LICENSE for details.
data/Rakefile CHANGED
@@ -1,56 +1,13 @@
1
- require 'rubygems'
2
- require 'rake'
1
+ #!/usr/bin/env rake
3
2
 
4
- begin
5
- require 'jeweler'
6
- Jeweler::Tasks.new do |gem|
7
- gem.name = "rubyfca"
8
- gem.summary = %Q{Command line Formal Concept Ananlysis (FCA) tool written in Ruby}
9
- gem.description = %Q{Command line Formal Concept Ananlysis (FCA) tool written in Ruby}
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
3
+ # frozen_string_literal: true
19
4
 
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
5
+ require "bundler/gem_tasks"
6
+ require "rake/testtask"
26
7
 
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
8
+ Rake::TestTask.new do |t|
9
+ t.libs << "test"
10
+ t.test_files = FileList["test/**/*_test.rb"]
38
11
  end
39
12
 
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
13
+ task default: :test
data/bin/rubyfca CHANGED
@@ -1,88 +1,92 @@
1
1
  #!/usr/bin/env ruby
2
- # -*- coding: utf-8 -*-
3
2
 
4
- $:.unshift File.join(File.dirname(__FILE__), '..', 'lib')
5
- require 'trollop'
6
- require 'rubyfca'
7
- require 'ruby_graphviz'
3
+ # frozen_string_literal: true
4
+
5
+ Encoding.default_external = "UTF-8"
6
+
7
+ require_relative "../lib/rubyfca"
8
8
 
9
9
  ################ parse options ##########
10
10
 
11
- opts = Trollop::options do
12
- version = File.read(File.dirname(__FILE__) + "/../VERSION")
13
- banner <<-EOS
14
-
15
- RubuFCA converts Conexp CXT data to Graphviz dot format.
16
-
17
- Usage:
18
- rubyfca [options] <source file> <output file>
19
-
20
- where:
21
- <source file>
22
- ".cxt", ".csv"
23
- <output file>
24
- ."dot", ".png", ".jpg", or ".eps"
25
- [options]:
26
- EOS
27
-
28
- opt :full, "Do not contract concept labels", :default=> false
29
- opt :coloring, "Color concept nodes [0 = none (default), 1 = lightblue/pink, 2 = monochrome]", :default => 0
30
- opt :straight, "Straighten edges (available when output format is either png, jpg, svg, pdf, or eps)", :default => false
31
- opt :nodesep, "Size of separation between sister nodes (from 0.1 to 5.0)", :default => 0.4
32
- opt :ranksep, "Size of separation between ranks (from 0.1 to 5.0)", :default => 0.2
33
- opt :legend, "Print the legend of concept nodes (available only when using circle node shape)", :default => false
34
- opt :circle, "Use circle shaped concept nodes", :default=> false
11
+ opts = Optimist.options do
12
+ version RubyFCA::VERSION
13
+ banner <<~USAGE
14
+ RubuFCA converts Conexp CXT data to Graphviz dot format.
35
15
 
36
- end
37
- Trollop::die :coloring, "must be 0, 1, or 2" if (opts[:coloring] > 2 || opts[:coloring] < 0)
38
- Trollop::die :ranksep, "must be within 0.1 - 5.0" if (opts[:ranksep] < 0.1 || opts[:ranksep] > 5.0)
39
- Trollop::die :nodesep, "must be within 0.1 - 5.0" if (opts[:nodesep] < 0.1 || opts[:nodesep] > 5.0)
40
- ############### main program ###############
16
+ Usage:
17
+ rubyfca [options] <source file> <output file>
41
18
 
42
- if ARGV.size != 2
43
- showerror("Input and output files are not set properly", 1)
19
+ where:
20
+ <source file>
21
+ ".xlsx", ".csv", ".cxt"
22
+ <output file>
23
+ ".svg", ".png", ".jpg", ".eps", or ."dot"
24
+ [options]:
25
+ USAGE
26
+
27
+ opt :full, "Do not contract concept labels", default: false
28
+ opt :coloring, "Color concept nodes [0 = none (default), 1 = lightblue/pink, 2 = monochrome]", default: 0
29
+ opt :straight, "Straighten edges (available when output format is either png, jpg, svg, pdf, or eps)", default: false
30
+ opt :nodesep, "Size of separation between sister nodes (from 0.1 to 5.0)", default: 0.4
31
+ opt :ranksep, "Size of separation between ranks (from 0.1 to 5.0)", default: 0.2
32
+ opt :legend, "Print the legend of concept nodes (available only when using circle node shape)", default: false
33
+ opt :circle, "Use circle shaped concept nodes", default: false
44
34
  end
45
35
 
46
- filename1 = ARGV[0] #input filename
47
- filename2 = ARGV[1] #output filename
36
+ Trollop.die :coloring, "must be 0, 1, or 2" if opts[:coloring] > 2 || opts[:coloring].negative?
37
+ Trollop.die :ranksep, "must be within 0.1 - 5.0" if opts[:ranksep] < 0.1 || opts[:ranksep] > 5.0
38
+ Trollop.die :nodesep, "must be within 0.1 - 5.0" if opts[:nodesep] < 0.1 || opts[:nodesep] > 5.0
39
+
40
+ ############### main program ###############
41
+
42
+ ARGV.size != 2 && showerror("Input and output files are not set properly", 1)
43
+
44
+ filename1 = ARGV[0] # input filename
45
+ filename2 = ARGV[1] # output filename
48
46
 
49
47
  #
50
48
  # extract input and output file types
51
49
  #
52
- input_type = filename1.slice(/\.[^\.]+\z/).split(//)[1..-1].join("")
53
- output_type = filename2.slice(/\.[^\.]+\z/).split(//)[1..-1].join("")
50
+ input_type = filename1.slice(/\.[^.]+\z/).split(//)[1..].join("")
51
+ output_type = filename2.slice(/\.[^.]+\z/).split(//)[1..].join("")
54
52
 
55
- if (input_type !~ /\A(cxt|csv)\z/ || output_type !~ /\A(dot|png|jpg|svg|pdf|eps)\z/)
53
+ if (input_type !~ /\A(cxt|csv|xlsx)\z/ ||
54
+ output_type !~ /\A(dot|png|jpg|svg|pdf|eps)\z/)
56
55
  showerror("These file extensions are not (yet) supported.", 1)
57
56
  end
58
57
 
59
58
  #
60
- # input data is kept as plain text
59
+ # input data is kept as plain text unless file type is "xlsx"
61
60
  #
62
- f = File.open(filename1, "r")
63
- inputdata = f.read
64
- f.close
61
+
62
+ if input_type == "xlsx"
63
+ inputdata = filename1
64
+ else
65
+ f = File.open(filename1, "r:UTF-8:UTF-8")
66
+ inputdata = f.read
67
+ inputdata.gsub!(/\r\n?/) { "\n" }
68
+ f.close
69
+ end
65
70
 
66
71
  #
67
72
  # ask for confirmation of overwriting an exisiting file
68
73
  #
69
- if (File.exist?(filename2) && !opts[:sil])
74
+ if File.exist?(filename2) && !opts[:sil]
70
75
  print "#{filename2} exists and will be overwritten, OK? [y/n]"
71
- var1 = STDIN.gets;
72
- if /y/i !~ var1
73
- exit;
74
- end
76
+ var1 = $stdin.gets
77
+ exit if /y/i !~ var1
75
78
  end
76
79
 
77
80
  #
78
81
  # context data is converted to a hash table
79
82
  #
80
83
  begin
81
- ctxt = FormalContext.new(inputdata, input_type, !opts[:full])
82
- ctxt.calculate
83
- # rescue => e
84
- # puts e
85
- # showerror("Source data may have problems. Process aborted.", 1)
84
+ ctxt = FormalContext.new(inputdata, input_type, label_contraction: !opts[:full])
85
+ ctxt.calcurate
86
+ rescue StandardError => e
87
+ pp e.message
88
+ pp e.backtrace
89
+ showerror("Source data may have problems. Process aborted.", 1)
86
90
  end
87
91
 
88
92
  #
@@ -90,8 +94,8 @@ end
90
94
  #
91
95
  case output_type
92
96
  when "dot"
93
- File.open(filename2, "w") do |f|
94
- f.write(ctxt.generate_dot(opts))
97
+ File.open(filename2, "w") do |file|
98
+ file.write(ctxt.generate_dot(opts))
95
99
  end
96
100
  when "png"
97
101
  ctxt.generate_img(filename2, "png", opts)
@@ -1,11 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  ## lib/ruby_graphviz.rb -- graphviz dot generator library
2
4
  ## Author:: Yoichiro Hasebe (mailto: yohasebe@gmail.com)
3
- ## Copyright:: Copyright 2010 Yoichiro Hasebe
5
+ ## Copyright:: Copyright 2009 Yoichiro Hasebe
4
6
  ## License:: GNU GPL version 3
5
7
 
6
8
  class RubyGraphviz
7
-
8
- ## Example:
9
+ ## Example:
9
10
  ##
10
11
  ## g = RubyGraphviz.new("newgraph", {:rankdir => "LR", :nodesep => "0.4", :ranksep => "0.2"})
11
12
  ##
@@ -14,7 +15,7 @@ class RubyGraphviz
14
15
  @graph_data = graph_hash
15
16
  @nodes = []
16
17
  @edges = []
17
- @dot = ""
18
+ @dot = +""
18
19
  create_graph
19
20
  end
20
21
 
@@ -33,31 +34,31 @@ class RubyGraphviz
33
34
  end
34
35
  @dot << "]"
35
36
  end
36
- @dot << ";\n"
37
+ @dot << ";\n"
37
38
  end
38
-
39
+
39
40
  def finish_graph
40
41
  @dot << "}\n"
41
42
  end
42
43
 
43
44
  def create_edge(edgetype, nid1, nid2, edge_hash = nil)
44
- temp = " #{nid1.to_s} #{edgetype} #{nid2.to_s}"
45
+ temp = " #{nid1} #{edgetype} #{nid2}"
45
46
  index = 0
46
47
  if edge_hash
47
- temp << " ["
48
+ temp << " ["
48
49
  edge_hash.each do |k, v|
49
50
  k = k.to_s
50
51
  temp << "#{k} = \"#{v}\""
51
52
  index += 1
52
53
  temp << ", " unless index == edge_hash.size
53
54
  end
54
- temp << "]"
55
+ temp << "]"
55
56
  end
56
- return temp
57
+ temp
57
58
  end
58
-
59
+
59
60
  public
60
-
61
+
61
62
  ## Add a subgraph to a graph (recursively)
62
63
  ##
63
64
  ## Example:
@@ -67,7 +68,7 @@ class RubyGraphviz
67
68
  def subgraph(graph)
68
69
  @dot << graph.to_dot.sub(/\Agraph/, "subgraph")
69
70
  end
70
-
71
+
71
72
  ## Set default options for nodes
72
73
  ##
73
74
  ## Example:
@@ -105,7 +106,7 @@ class RubyGraphviz
105
106
  @dot << "];\n"
106
107
  self
107
108
  end
108
-
109
+
109
110
  ## Create a node with its options
110
111
  ##
111
112
  ## Example:
@@ -113,7 +114,7 @@ class RubyGraphviz
113
114
  ## graph.node("node-01", :label => "Node 01", :fillcolor => "pink")
114
115
  ##
115
116
  def node(node_id, node_hash = nil)
116
- @dot << " #{node_id.to_s}"
117
+ @dot << " #{node_id}"
117
118
  index = 0
118
119
  if node_hash
119
120
  @dot << " ["
@@ -129,22 +130,22 @@ class RubyGraphviz
129
130
  self
130
131
  end
131
132
 
132
- ## Create a non-directional edge (connection line between nodes) with its options
133
+ ## Create a non-directional edge (connection line between nodes) with its options
133
134
  ##
134
135
  ## Example:
135
136
  ##
136
137
  ## graph.edge("node-01", "node-02", :label => "connecting 1 and 2", :color => "lightblue")
137
- ##
138
+ ##
138
139
  def edge(nid1, nid2, edge_hash = nil)
139
140
  @dot << create_edge("--", nid1, nid2, edge_hash) + ";\n"
140
141
  self
141
142
  end
142
143
 
143
- ## Create a directional edge (arrow from node to node) with its options
144
+ ## Create a directional edge (arrow from node to node) with its options
144
145
  ##
145
146
  ## Example:
146
147
  ## graph.arrow_edge("node-01", "node-02", :label => "from 1 to 2", :color => "lightblue")
147
- ##
148
+ ##
148
149
  def arrow_edge(nid1, nid2, edge_hash = nil)
149
150
  @dot << create_edge("->", nid1, nid2, edge_hash) + ";\n"
150
151
  self
@@ -156,12 +157,11 @@ class RubyGraphviz
156
157
  @dot << "{rank=same " + create_edge("--", nid1, nid2, edge_hash) + "}\n"
157
158
  self
158
159
  end
159
-
160
+
160
161
  ## Convert graph into dot formatted data
161
162
  ##
162
163
  def to_dot
163
164
  finish_graph
164
- @dot = @dot.gsub(/\"\</m, "<").gsub(/\>\"/m, ">")
165
- return @dot
165
+ @dot.gsub(/"</m, "<").gsub(/>"/m, ">")
166
166
  end
167
167
  end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyFCA
4
+ VERSION = "0.3.0"
5
+ end