rubypivot 0.0.1 → 0.0.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2976ecf2bef2b1fcc45d0044cbf553ab7b07e0ee8ecd48aca334f13836399016
4
- data.tar.gz: 92c6cf0c117a8dbbff68b55a72c760bae6bc62992b1ca7744e9bfe35ebc731eb
3
+ metadata.gz: 9a08da64e0fde946fe250518330143484176d4aae03f14d01d8c0597ec1b9631
4
+ data.tar.gz: 920c3745a938d8529a56acd051801ac18b9ec76d00ca62bb498e3043d080f446
5
5
  SHA512:
6
- metadata.gz: 9575159c5670c6831c9777e98bede1a93e19347f3b9ae6d3f9a848450d78029dc3810323857de554d98d0fa5a72edfd5930173fc049c73b8ee0b28a00b9c4e7c
7
- data.tar.gz: a9a32709eec385451e3bd1398e412cc874e7020daf02ad25ca85ed4905606c15ee300e4884dc8a9be906b0da8188d6ec5bb498cd2c3d180b6e90ad22cd5fd4f7
6
+ metadata.gz: 2b27cc0df34f5bdf1c08e44633f2f85204ebf78ee139bd56162841f7dba07f063976bdf1b31635a1f2315aab782f9d848fd710b7895c915516e2fc52460d475a
7
+ data.tar.gz: 904336637ab697e188b75165b56f32a318b5036aec8d141f8db8bd84d0648f1d169ecc990670f561af9b60c26c9102314780fc7a27dda51ddfdc24b958618c9c
data/README.md CHANGED
@@ -10,22 +10,55 @@ Suggestions and pull requests are welcome.
10
10
  ```
11
11
  gem install rubypivot
12
12
  ```
13
- Not yet ready, please clone the repository for the moment.
13
+
14
14
  ## Usage
15
15
 
16
+ Pivot class transform database record type array into pivot table Hash or Array
17
+
16
18
  ```ruby
17
19
  require "rubypivot"
18
20
  pivot = Rubypivot::Pivot.new(source_data, :month, :name, :value, data_type: :integer)
19
- pivot.build.each do |line|
21
+ pivot_array = pivot.build
22
+ pivot_array.each do |line|
20
23
  p line
21
24
  end
22
25
  ```
26
+
27
+ SpreadTable class makes a Hash table from pivot hash, which makes it easy to build HTML table with totals, layouts, colors.
28
+
29
+ ```ruby
30
+ require "rubypivot"
31
+ pivot = Rubypivot::Pivot.new(source_data, :month, :name, :value, data_type: :integer)
32
+ pivot_hash = pivot.build_hash
33
+ spread = Rubypivot::SpreadTable.new(pivot_hash, data_type: :integer)
34
+ spread.rows.each do |row|
35
+ puts row.to_s
36
+ end
37
+ ```
38
+
39
+ ```ruby
40
+ require "rubypivot"
41
+ spread = Rubypivot::SpreadTable.new(DATA_SOURCE, data_type: :integer)
42
+ puts spread.to_html(class: "table table-striped", line_end: :cr)
43
+ ```
44
+ ![SpreadTable](./examples/spread_table.png)
45
+
23
46
  See sample scripts in examples folder.
24
47
 
25
48
  Supported data aggregation is only SUM for numeric values.
26
49
 
27
50
  Total calculation supported.
28
51
 
52
+
53
+ ## To do-s
54
+
55
+ - Bootstrap Grid output
56
+
57
+ ## History
58
+
59
+ - Ver. 0.0.2 : 2021-01-01 New class SpreadTable supports to_s, to_html (HTML table), to_grid (Bootstrap grid)
60
+ - Ver. 0.0.1 : 2020-12-28 First release. Making pivot array
61
+
29
62
  ## License
30
63
 
31
64
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,61 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
6
+ <meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
7
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-giJF6kkoqNQ00vy+HMDP7azOuL0xtbfIcaT9wjKHr8RbDVddVHyTfAAsrekwKmP1" crossorigin="anonymous">
8
+ <title>RubyPivot Example</title>
9
+ <style>
10
+ .container {
11
+ max-width: 960px;
12
+ }
13
+ .title-header {
14
+ max-width: 700px;
15
+ }
16
+ tr.header {
17
+ background-color: #eeeeff;
18
+ text-align: right;
19
+ font-weight: bold;
20
+ }
21
+ tr.data-row {
22
+ text-align: right;
23
+ }
24
+ tr.data-girl {
25
+ background-color: #ffeeee;
26
+ text-align: right;
27
+ }
28
+ td.header-title {
29
+ text-align: left;
30
+ }
31
+ td.data-title {
32
+ background-color: #ffffee;
33
+ text-align: left;
34
+ }
35
+ td.data {
36
+ color:#3333aa;
37
+ }
38
+ td.zero {
39
+ color: #aaaaaa;
40
+ }
41
+ td.negative {
42
+ color:#aa3333;
43
+ }
44
+ </style>
45
+ </head>
46
+ <body>
47
+ <div class="container">
48
+
49
+ <div class="title_header" name="title-header" data-bs-target="target">HTML Table Creator for pivot array</div>
50
+
51
+ <table class="table table-striped">
52
+ <tr class="header"><td class="header-title"></td><td>A</td><td>B</td><td>C</td></tr>
53
+ <tr class="data-girl"><td class="data-title">Wendy</td><td>1</td><td>2</td><td class="negative">-3</td></tr>
54
+ <tr class="data-row"><td class="data-title">John</td><td>4</td><td class="zero">0</td><td>6</td></tr>
55
+ <tr class="data-row"><td class="data-title">David</td><td class="negative">-2</td><td>3</td><td class="zero">0</td></tr>
56
+ <tr class="header"><td class="header-title">Total</td><td>3</td><td>5</td><td>3</td></tr>
57
+ </table>
58
+
59
+ </div>
60
+ </body>
61
+ </html>
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ # coding: utf-8
3
+ #
4
+ # Make HTML table from spread sheet array
5
+ #
6
+ APP_ROOT = File.dirname(__FILE__)
7
+ $LOAD_PATH << "#{APP_ROOT}/../lib"
8
+ require "rubypivot/html_tag"
9
+
10
+ div = Rubypivot::HtmlTag.new('div', class: 'section section-info', name: 'title-header')
11
+ div.add_key('data-bs-target', 'target')
12
+ puts div.build { "HTML Tag generator" }
13
+ puts div.build(body: "HTML Tag generator")
14
+
15
+ tr = Rubypivot::HtmlTag.new('tr', class: 'header')
@@ -18,18 +18,22 @@ source_data = [
18
18
 
19
19
  pivot = Rubypivot::Pivot.new(source_data, :month, :name, :value, data_type: :integer)
20
20
  pivot.column_titles = ['Jan', 'Feb', 'Mar']
21
+ pivot.options[:row_total] = 'Total'
21
22
  # pivot.options[:header] = true
22
23
  # pivot.options[:row_header] = false
23
- # pivot.column_titles.sort!
24
- # p pivot.column_titles
25
24
  # p pivot.row_titles
25
+ # p pivot.header_row('')
26
+ # p pivot.column_titles.sort! # sorting columns
27
+ p pivot.column_titles
28
+ puts "------------"
29
+ pivot.build_data.sort.each do |title, line|
30
+ puts "#{title}: #{line.join(", ")}"
31
+ end
32
+
33
+ puts
34
+ puts "------------"
26
35
  pivot.build.each do |line|
27
36
  p line
28
37
  end
29
38
  puts "------------"
30
39
  p pivot.total_row('Total')
31
-
32
- #["", "Jan", "Feb", "Mar"]
33
- #["David", 3, nil, nil]
34
- #["John", 123, 23, nil]
35
- #["Wendy", 33, 100, nil]
Binary file
@@ -0,0 +1,75 @@
1
+ #!/usr/bin/env ruby
2
+ # coding: utf-8
3
+ #
4
+ # Create spread sheet array, having row (tr) and cell (td) attributes
5
+ # to help HTML table
6
+ #
7
+ APP_ROOT = File.dirname(__FILE__)
8
+ $LOAD_PATH << "#{APP_ROOT}/../lib"
9
+ require "rubypivot"
10
+ # require "pry"
11
+ # binding.pry
12
+
13
+ DATA_SOURCE = [
14
+ ["", "A", "B", "C"],
15
+ ["Wendy", 1, 2, -3],
16
+ ["John", 4, 0, 6],
17
+ ["David", -2, 3, 0],
18
+ ["Total", 3, 5, 3],
19
+ ]
20
+ # Callback function to control cell class
21
+ # data is cell data, line_data is an array of the line cells
22
+ def cell_class_callback(data, line_title)
23
+ if data.to_i < 0
24
+ "negative"
25
+ elsif data.to_i == 0
26
+ "zero"
27
+ else
28
+ nil
29
+ end
30
+ end
31
+
32
+ def cell_callback(data, line_title)
33
+ if line_title == 'Total'
34
+ "<td class=\"my-total\">#{"%03d" % data}</td>"
35
+ else
36
+ "<td class=\"my-cell\">#{"%03d" % data}</td>"
37
+ end
38
+ end
39
+
40
+ # spread = DATA_SOURCE.to_spread( # Alternate method to create new
41
+ spread = Rubypivot::SpreadTable.new(DATA_SOURCE,
42
+ header: :first, # Consider the first row as header row
43
+ title: :first, # Consider the first column as row header
44
+ total: :last, # Consider the last row as total row
45
+ data_type: :integer, # or :float, :string, nil
46
+ header_line_class: 'header', # tr class for header and total row(s), grid size for Bootstrap
47
+ header_title_class: 'header-title', # td class for title of header row
48
+ data_line_class: 'data-row', # tr class for data row(s), grid size for Bootstrap
49
+ data_title_class: 'data-title', # td class for title of data row
50
+ data_format: '%02d', # format for data cells
51
+ )
52
+ # spread.each_header_line {|row| row.set_line_class("header") }
53
+ spread.line(1).set_line_class("data-girl") # Set TR class
54
+ spread.set_cell_class(method(:cell_class_callback))
55
+ # spread.each_data_line {|line| line.set_cell_class(method(:cell_class_callback)) } # Same effects like above line
56
+ # spread.set_cell_class('data-class')
57
+ spread.get_row(:last).set_cell_callback(method(:cell_callback))
58
+ # spread.set_cell_callback(method(:cell_callback))
59
+
60
+ puts "--- Created array ------------"
61
+ spread.rows.each do |row|
62
+ puts row.to_s
63
+ end
64
+ puts "--- Some calculated attrib ---"
65
+ puts "Total width: #{spread.total_width}"
66
+ puts "Total height: #{spread.total_height}"
67
+ puts "Data width: #{spread.data_width}"
68
+ puts "Data height: #{spread.data_height}"
69
+
70
+ puts "--- HTML table ---------------"
71
+ puts spread.to_html(class: "table table-striped", line_end: :cr)
72
+
73
+ puts "--- Bootstrap grid -----------"
74
+ spread.set_line_class("md")
75
+ puts spread.to_grid(:bootstrap, [2, 1, 1, 1, 1])
@@ -1,9 +1,10 @@
1
1
  # require "rubypivot/version"
2
+ require "rubypivot/version"
2
3
  require "rubypivot/pivot"
3
4
  require "rubypivot/pivot_row"
5
+ require "rubypivot/spread_table"
6
+ require "rubypivot/html_tag"
4
7
 
5
8
  module Rubypivot
6
- VERSION = "0.0.1" # Pre-release
7
-
8
9
  class PivotError < StandardError; end
9
10
  end
@@ -0,0 +1,81 @@
1
+ module Rubypivot
2
+ class HtmlTag
3
+ def initialize(tag_name, options = {})
4
+ @tag_name = tag_name
5
+ @options = options
6
+ @class_strings = []
7
+ end
8
+
9
+ def add_key(key, value)
10
+ @options[key] = value
11
+ self
12
+ end
13
+
14
+ def sanitize(value)
15
+ return nil unless value
16
+ array = value.split(/ +/)
17
+ array.uniq!
18
+ array.join(" ")
19
+ end
20
+
21
+ def add_class(class_string)
22
+ if class_string
23
+ class_string.to_s.split(/ +/).each do |str|
24
+ next if str.nil? || @class_strings.include?(str)
25
+ @class_strings << str
26
+ end
27
+ end
28
+ self
29
+ end
30
+
31
+ def build_class
32
+ return '' if @class_strings.empty?
33
+ " class=\"#{@class_strings.join(' ')}\""
34
+ end
35
+
36
+ def open
37
+ add_class(@options[:class])
38
+ res = "<#{@tag_name}"
39
+ res << build_class
40
+ @options.each do |key, value|
41
+ next if value.nil?
42
+ case key
43
+ when :class
44
+ # class was handled first
45
+ else
46
+ res << " #{key}=\"#{value}\""
47
+ end
48
+ end
49
+ res << ">"
50
+ res
51
+ end
52
+
53
+ def close
54
+ "</#{@tag_name}>"
55
+ end
56
+
57
+ def build(options = {})
58
+ res = open
59
+ # res << "\n" unless options[:compact]
60
+ if block_given?
61
+ res << yield.to_s
62
+ # res << "\n" unless options[:compact]
63
+ res << close
64
+ # res << "\n" unless options[:compact]
65
+ elsif options[:body]
66
+ res << options[:body]
67
+ # res << "\n" unless options[:compact]
68
+ res << close
69
+ # res << "\n" unless options[:compact]
70
+ end
71
+ res
72
+ end
73
+ alias :to_html :build
74
+
75
+ def self.to_html(tag_name, options = {})
76
+ instance = self.new(tag_name, body, options = {})
77
+ instance.build(body)
78
+ end
79
+
80
+ end
81
+ end
@@ -1,5 +1,8 @@
1
1
  module Rubypivot
2
-
2
+ #
3
+ # 2020-12-28 First
4
+ # 2020-12-30 Build an hash instead array
5
+ #
3
6
  class Pivot
4
7
  DEFAULT_OPTIONS = {
5
8
  data_type: :integer, # :integer, :float or :string
@@ -9,7 +12,7 @@ module Rubypivot
9
12
  row_header: true,
10
13
  # column_lookup: HashName
11
14
  # row_lookup: HashName
12
- # :row_total: 'Title for total column'
15
+ # row_total: 'Title for total column, if nil row total not calculated'
13
16
  }.freeze
14
17
 
15
18
  attr_accessor :options
@@ -50,6 +53,10 @@ module Rubypivot
50
53
  @row_titles || make_row_list
51
54
  end
52
55
 
56
+ def column_head(row_title)
57
+ @options[:row_lookup] ? @options[:row_lookup][row_title] : row_title
58
+ end
59
+
53
60
  def make_column_list
54
61
  @column_titles = [] unless @column_titles
55
62
  @source_data.each do |each_line|
@@ -84,35 +91,50 @@ module Rubypivot
84
91
  self
85
92
  end
86
93
 
87
- def column_header
88
- res = []
89
- res << '' if @options[:row_header]
90
- if @options[:column_lookup]
91
- @column_titles.each do |column|
92
- res << @options[:column_lookup][column]
93
- end
94
- else
95
- res += @column_titles
94
+ # return an pivot hash. data rows only
95
+ def build_data
96
+ parse_data unless @rows_parsed
97
+ res = {}
98
+ @row_titles.each do |row_title|
99
+ row = @rows_parsed.get_row(row_title)
100
+ title = column_head(row_title)
101
+ res[title] = row.to_a(column_titles)
96
102
  end
97
- res << @options[:row_total] if @options[:row_total]
98
103
  res
99
104
  end
105
+ alias :build_hash :build_data
100
106
 
101
- # return an pivot array
107
+ # return an pivot array with titles
102
108
  def build
103
109
  parse_data unless @rows_parsed
104
110
  res = []
105
- res << column_header if @options[:header]
111
+ res << header_row if @options[:header]
106
112
  @row_titles.each do |row_title|
107
113
  row = @rows_parsed.get_row(row_title)
108
114
  data_array = []
109
- data_array << @rows_parsed.header(row_title) if @options[:row_header]
115
+ data_array << column_head(row_title) if @options[:row_header]
110
116
  data_array += row.to_a(column_titles)
111
117
  data_array << row.total(column_titles) if @options[:row_total]
112
118
  res << data_array
113
119
  end
114
120
  res
115
121
  end
122
+ alias :build_array :build
123
+
124
+ # Make an array
125
+ def header_row(title = nil)
126
+ res = []
127
+ res << "#{title}" if @options[:row_header]
128
+ if @options[:column_lookup]
129
+ @column_titles.each do |column|
130
+ res << @options[:column_lookup][column]
131
+ end
132
+ else
133
+ res += @column_titles
134
+ end
135
+ res << @options[:row_total] if @options[:row_total]
136
+ res
137
+ end
116
138
 
117
139
  def total_row(title = nil)
118
140
  parse_data unless @rows_parsed
@@ -122,10 +144,6 @@ module Rubypivot
122
144
  res += @rows_parsed.total(@column_titles, @options[:row_total])
123
145
  end
124
146
 
125
- def self.build(data, column_name, row_name, data_name, options = {})
126
- obj = self.new(data, column_name, row_name, data_name, options)
127
- obj.build
128
- end
129
147
  # get column title or row title
130
148
  def self.get_title(name, line)
131
149
  if line.is_a?(Hash)
@@ -144,6 +162,10 @@ module Rubypivot
144
162
  line.send(name)
145
163
  end
146
164
  end
147
-
165
+
166
+ def self.build(data, column_name, row_name, data_name, options = {})
167
+ obj = self.new(data, column_name, row_name, data_name, options)
168
+ obj.build
169
+ end
148
170
  end
149
171
  end
@@ -4,7 +4,6 @@ module Rubypivot
4
4
  def initialize(options = {})
5
5
  @options = options
6
6
  @data_type = options[:data_type]
7
- @lookup = options[:row_lookup]
8
7
  @rows = {}
9
8
  end
10
9
 
@@ -13,10 +12,6 @@ module Rubypivot
13
12
  end
14
13
  alias :add_row :get_row
15
14
 
16
- def header(row_title)
17
- @lookup ? @lookup[row_title] : row_title
18
- end
19
-
20
15
  def total(column_titles = [], show_grand_total = false)
21
16
  return ['Total', 'row', 'can', 'not', 'create', "type :#{@data_type}"] unless [:integer, :float].include?(@data_type)
22
17
  grand_total = @data_type == :float ? 0.0 : 0
@@ -0,0 +1,334 @@
1
+ # Create spread sheet array of row objects (SpreadTableLine) from array
2
+ # having row (tr) and cell (td) attributes
3
+ # to help HTML table
4
+ #
5
+ # 2020-12-31
6
+ #
7
+
8
+ class Array
9
+ def to_spread(options = {})
10
+ spread_array = SpreadTable.new(self, options)
11
+ end
12
+ end
13
+
14
+ module Rubypivot
15
+ class SpreadTableError < StandardError; end
16
+
17
+ class SpreadTableLine
18
+ LINE_TYPES = {
19
+ header: 'Header',
20
+ data: 'Data ',
21
+ total: 'Total ',
22
+ }.freeze
23
+
24
+ attr_reader :line_type, :options
25
+ attr_accessor :line_data, :title, :attribut
26
+ def initialize(line_type, line_data = [], options = {})
27
+ @options = options
28
+ @attribute = {}
29
+ @attribs = []
30
+ set_line_type(line_type, @options)
31
+ # Desparate to have an array, convert it if not
32
+ if line_data.is_a?(Array)
33
+ @line_data = line_data
34
+ elsif line_data.is_a?(Hash)
35
+ @line_data = []
36
+ line_data.each do |key, value|
37
+ @line_data << value
38
+ end
39
+ elsif line_data.is_a?(String)
40
+ @line_data = line_data.split(/[ \t]+/)
41
+ else
42
+ @line_data = [line_data]
43
+ end
44
+ if options[:title]
45
+ if options[:title] == :first
46
+ @title = @line_data.shift
47
+ else
48
+ @title = options[:title].to_s
49
+ end
50
+ end
51
+ end
52
+
53
+ def set_line_type(line_type, options = {})
54
+ @line_type = line_type
55
+ @line_type = :data unless LINE_TYPES[@line_type] # at least vaid must be set
56
+ if @line_type == :data
57
+ @attribute[:line_class] = options[:data_line_class] if options[:data_line_class]
58
+ @attribute[:title_class] = options[:data_title_class] if options[:data_title_class]
59
+ else
60
+ @attribute[:line_class] = options[:header_line_class] if options[:header_line_class]
61
+ @attribute[:title_class] = options[:header_title_class] if options[:header_title_class]
62
+ end
63
+ if @line_type == :header
64
+ #
65
+ else
66
+ @attribute[:data_format] = options[:data_format] if options[:data_format]
67
+ end
68
+ end
69
+
70
+ def data_width
71
+ @line_data.size
72
+ end
73
+
74
+ def set_line_class(klass)
75
+ @attribute[:line_class] = klass
76
+ end
77
+
78
+ def set_td_class(klass)
79
+ @attribute[:cell_class] = klass
80
+ end
81
+
82
+ def set_title_class(klass)
83
+ @attribute[:cell_class] = klass
84
+ end
85
+
86
+ def set_cell_class(callback)
87
+ return unless callback
88
+ if callback.is_a?(Method)
89
+ @line_data.each_with_index do |cell_data, idx|
90
+ klass = callback.call(cell_data, @title)
91
+ @attribs[idx] = klass if klass
92
+ end
93
+ else
94
+ @line_data.each_with_index do |cell_data, idx|
95
+ @attribs[idx] = callback.to_s
96
+ end
97
+ end
98
+ end
99
+
100
+ def set_cell_callback(callback)
101
+ return if callback.nil? || !callback.is_a?(Method)
102
+ if @line_type == :header
103
+ @attribute[:cell_callback] = callback
104
+ else
105
+ @attribute[:cell_callback] = callback
106
+ end
107
+ end
108
+
109
+ def line_data_formatted
110
+ return @line_data if @line_type == :header || @attribute[:data_format].nil?
111
+ res = []
112
+ @line_data.each do |data|
113
+ res << @attribute[:data_format] % data
114
+ end
115
+ res
116
+ end
117
+
118
+ def to_s
119
+ res = ""
120
+ res << "#{LINE_TYPES[@line_type]}: "
121
+ res << "#{@title}: " if @title
122
+ res << line_data_formatted.join(', ')
123
+ res << " : Line Class: #{@attribute[:line_class]}"
124
+ res << " : Cell Class: #{@attribute[:cell_class]}"
125
+ res << " : Title Class: #{@attribute[:title_class]}"
126
+ res
127
+ end
128
+
129
+ def to_html(options = {})
130
+ tr = Rubypivot::HtmlTag.new('tr', class: @attribute[:line_class])
131
+ td_str = ""
132
+ if @title
133
+ td = Rubypivot::HtmlTag.new('td', class: @attribute[:title_class] || @attribute[:cell_class])
134
+ td_str << td.build{ @title }
135
+ end
136
+ line_data_formatted.each_with_index do |cell_data, idx|
137
+ if @attribute[:cell_callback]
138
+ td_str << @attribute[:cell_callback].call(cell_data, @title)
139
+ else
140
+ td = Rubypivot::HtmlTag.new('td', class: @attribs[idx])
141
+ td_str << td.build{ cell_data }
142
+ end
143
+ end
144
+ res = tr.build { td_str }
145
+ res << "\n" if options[:line_end] == :cr
146
+ res
147
+ end
148
+
149
+ def make_col_class(options = {})
150
+ klass = "col"
151
+ klass << "-#{@attribute[:line_class]}" if @attribute[:line_class]
152
+ klass << "-#{options[:width]}" if options[:width]
153
+ # klass << " #{options[:klass]}" if options[:klass]
154
+ klass
155
+ end
156
+
157
+ def to_grid(framework = :bootstrap, widths = [], options = {})
158
+ res = "<div class=\"row\">"
159
+ if @title
160
+ div = Rubypivot::HtmlTag.new('div', class: make_col_class(width: widths[0])) # klass: @attribute[:title_class]
161
+ res << div.build{ @title }
162
+ end
163
+ line_data_formatted.each_with_index do |cell_data, idx|
164
+ div = Rubypivot::HtmlTag.new('div', class: make_col_class(width: widths[idx + 1])) # klass: @attribute[:title_class]
165
+ res << div.build{ cell_data }
166
+ end
167
+ res << "</div>/n"
168
+ res
169
+ end
170
+ end
171
+
172
+ class SpreadTable
173
+ attr_reader :data_width, :data_height, :total_width
174
+ attr_accessor :options, :rows
175
+ def initialize(data_source, options = {})
176
+ @rows = []
177
+ @options = {}
178
+ @options_for_line = {}
179
+ options.each do |k, v|
180
+ if [:title, :data_line_class, :header_line_class, :data_title_class, :header_title_class, :data_format].include?(k)
181
+ @options_for_line[k] = v
182
+ else
183
+ @options[k] = v
184
+ end
185
+ end
186
+ @attribs = []
187
+
188
+ if data_source.is_a? Array
189
+ data_source.each do |line|
190
+ @rows << SpreadTableLine.new(:data, line, @options_for_line)
191
+ end
192
+ elsif data_source.is_a? Hash
193
+ data_source.each do |title, values|
194
+ @rows << SpreadTableLine.new(:data, values, title: title)
195
+ end
196
+ else
197
+ @rows << SpreadTableLine.new(:data, line, line_options)
198
+ end
199
+ set_line_type(:header, @options[:header], false)
200
+ set_line_type(:total, @options[:total], false)
201
+ calc_data_size
202
+ end
203
+
204
+ def set_line_type(line_type, position = nil, recalc = true)
205
+ return if line_type.nil? || position.nil?
206
+ return unless SpreadTableLine::LINE_TYPES[line_type]
207
+ case position
208
+ when :first, :top
209
+ @rows.first.set_line_type(line_type, @options_for_line)
210
+ when :last, :bottom
211
+ @rows.last.set_line_type(line_type, @options_for_line)
212
+ else
213
+ row = get_row(position)
214
+ if row
215
+ @rows[pos].set_line_type(line_type, @options_for_line)
216
+ end
217
+ end
218
+ calc_data_size if recalc
219
+ end
220
+
221
+ def add_line(line_type = :data, line = [], line_options = {})
222
+ @rows << SpreadTableLine.new(line_type, line, line_options)
223
+ calc_data_size
224
+ end
225
+
226
+ def get_row(position)
227
+ if position.is_a?(Symbol)
228
+ if position == :last
229
+ @rows.last
230
+ else
231
+ @rows.first
232
+ end
233
+ else
234
+ pos = position.to_i
235
+ @rows[pos] if pos >= 0 && pos < @rows.size - 1
236
+ end
237
+ end
238
+ alias :line :get_row
239
+
240
+ def total_height
241
+ @rows.size
242
+ end
243
+
244
+ def calc_data_size
245
+ @total_width = 0
246
+ @data_width = 0
247
+ @data_height = 0
248
+ @rows.each do |row|
249
+ w = row.data_width
250
+ @total_width = w if @total_width < w
251
+ next if row.line_type != :data
252
+ @data_width = w if @data_width < w
253
+ @data_height += 1
254
+ end
255
+ @total_width += 1 # Including title column
256
+ self
257
+ end
258
+
259
+ def set_cell_class(callback)
260
+ return unless callback
261
+ each_data_line do |line|
262
+ line.set_cell_class(callback)
263
+ end
264
+ end
265
+
266
+ def set_line_class(klass)
267
+ return unless klass
268
+ each do |line|
269
+ line.set_line_class(klass)
270
+ end
271
+ end
272
+
273
+ def set_cell_callback(callback)
274
+ return if callback.nil? || !callback.is_a?(Method)
275
+ each_non_header_line do |line|
276
+ line.set_cell_callback(callback)
277
+ end
278
+ end
279
+
280
+ def each
281
+ @rows.each do |row|
282
+ yield row
283
+ end
284
+ end
285
+
286
+ def each_data_line
287
+ @rows.each do |row|
288
+ next if row.line_type != :data
289
+ yield row
290
+ end
291
+ end
292
+
293
+ def each_header_line
294
+ @rows.each do |row|
295
+ next if row.line_type == :data
296
+ yield row
297
+ end
298
+ end
299
+
300
+ def each_non_header_line
301
+ @rows.each do |row|
302
+ next if row.line_type == :header
303
+ yield row
304
+ end
305
+ end
306
+
307
+ def to_s
308
+ @rows.each do |row|
309
+ puts row.to_s
310
+ end
311
+ end
312
+
313
+ def to_html(options = {})
314
+ line_end = options.delete(:line_end)
315
+ res = HtmlTag.new('table', options).open
316
+ res << "\n" if line_end == :cr
317
+ @rows.each do |row|
318
+ res << row.to_html(line_end: line_end)
319
+ end
320
+ res << "</table>\n"
321
+ res
322
+ end
323
+ # Bootstrap grid
324
+ def to_grid(framework = :bootstrap, widths = [], options = {})
325
+ res = ""
326
+ @rows.each do |row|
327
+ res << row.to_grid(framework, widths)
328
+ end
329
+ res
330
+ end
331
+
332
+ end
333
+
334
+ end
@@ -0,0 +1,95 @@
1
+ module Rubypivot
2
+
3
+ # Create HTML table from array
4
+ # having row (tr) and cell (td) attribute control
5
+ # rubypivot main gem does not include this, maybe won't be using
6
+ #
7
+ class TableBuilder
8
+ attr_accessor :options
9
+ attr_reader :x_size, :y_size
10
+ def initialize(data_array, options = {})
11
+ raise StandardError, "Data source must be an two dimension array" if !data_array.is_a?(Array) || !data_array.first.is_a?(Array)
12
+ @options = options
13
+ @data_array = data_array
14
+ @attributes = []
15
+ @data_array.each do |line|
16
+ @attributes << [options[:tr_class]]
17
+ end
18
+ @x_size = @data_array.first.size
19
+ @y_size = @data_array.size
20
+ end
21
+
22
+ def build(options = {})
23
+ tr_classes(options[:tr_class]) if options[:tr_class]
24
+ res = open
25
+ @data_array.each_with_index do |line, y|
26
+ tr = HtmlTag.new("tr", class: @attributes[y][0])
27
+ res << tr.open
28
+ line.each_with_index do |td_data, x|
29
+ res << HtmlTag.new("td", class: @attributes[y][x + 1]).build{ td_data }
30
+ end
31
+ res << tr.close + "\n"
32
+ end
33
+ # res << "\n"
34
+ res << close
35
+ res
36
+ end
37
+
38
+ def open
39
+ res = HtmlTag.new('table', @options).open
40
+ res << "\n"
41
+ res
42
+ end
43
+
44
+ def close(options = {})
45
+ "</table>\n"
46
+ end
47
+
48
+ def range_check(y)
49
+ if y.is_a?(Symbol)
50
+ if y == :bottom
51
+ return @y_size - 1
52
+ else
53
+ return 0
54
+ end
55
+ end
56
+ raise StandardError, "Class set out of range: #{y} > #{@y_size}" if y > @y_size
57
+ y
58
+ end
59
+
60
+ def tr_class(klass, y)
61
+ return unless klass
62
+ y = range_check(y)
63
+ @attributes[y][0] = klass
64
+ end
65
+
66
+ def tr_classes(klass)
67
+ return unless klass
68
+ 0.upto(@attributes.size - 1) do |y|
69
+ @attributes[y][0] = klass
70
+ end
71
+ end
72
+
73
+ def set_class(klass, y, x)
74
+ return unless klass
75
+ y = range_check(y)
76
+ raise StandardError, "Class set out of range: #{} > #{@x_size}" if x >= @x_size
77
+ @attributes[y][x + 1] = klass
78
+ end
79
+
80
+ def row_attributes(klass, y)
81
+ return unless klass
82
+ y = range_check(y)
83
+ 1.upto(@x_size) do |pos|
84
+ @attributes[y][pos] = klass
85
+ end
86
+ end
87
+
88
+ def column_attributes(klass, x)
89
+ return unless klass
90
+ 0.upto(@attributes.size - 1) do |y|
91
+ @attributes[y][x + 1] = klass
92
+ end
93
+ end
94
+ end
95
+ end
@@ -1,3 +1,3 @@
1
1
  module Rubypivot
2
- VERSION = "0.0.1" # Pre-release
2
+ VERSION = "0.0.2"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubypivot
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - hiro utsumi
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-12-28 00:00:00.000000000 Z
11
+ date: 2021-01-01 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Transforming a dataset or array of hashes into a spreadsheet-style array
14
14
  email:
@@ -26,12 +26,19 @@ files:
26
26
  - bin/console
27
27
  - bin/setup
28
28
  - examples/active_record_like.rb
29
+ - examples/html_table.html
30
+ - examples/html_tag.rb
29
31
  - examples/lookup.rb
30
32
  - examples/simple.rb
33
+ - examples/spread_table.png
34
+ - examples/spread_table.rb
31
35
  - lib/rubypivot.rb
36
+ - lib/rubypivot/html_tag.rb
32
37
  - lib/rubypivot/pivot.rb
33
38
  - lib/rubypivot/pivot_column.rb
34
39
  - lib/rubypivot/pivot_row.rb
40
+ - lib/rubypivot/spread_table.rb
41
+ - lib/rubypivot/table.rb
35
42
  - lib/rubypivot/version.rb
36
43
  - rubypivot.gemspec
37
44
  homepage: https://github.com/gambldia/rubypivot