rubypivot 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +35 -2
- data/examples/html_table.html +61 -0
- data/examples/html_tag.rb +15 -0
- data/examples/simple.rb +11 -7
- data/examples/spread_table.png +0 -0
- data/examples/spread_table.rb +75 -0
- data/lib/rubypivot.rb +3 -2
- data/lib/rubypivot/html_tag.rb +81 -0
- data/lib/rubypivot/pivot.rb +42 -20
- data/lib/rubypivot/pivot_row.rb +0 -5
- data/lib/rubypivot/spread_table.rb +334 -0
- data/lib/rubypivot/table.rb +95 -0
- data/lib/rubypivot/version.rb +1 -1
- metadata +9 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9a08da64e0fde946fe250518330143484176d4aae03f14d01d8c0597ec1b9631
|
4
|
+
data.tar.gz: 920c3745a938d8529a56acd051801ac18b9ec76d00ca62bb498e3043d080f446
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
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
|
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')
|
data/examples/simple.rb
CHANGED
@@ -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])
|
data/lib/rubypivot.rb
CHANGED
@@ -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
|
data/lib/rubypivot/pivot.rb
CHANGED
@@ -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
|
-
#
|
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
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
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 <<
|
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 <<
|
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
|
data/lib/rubypivot/pivot_row.rb
CHANGED
@@ -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
|
data/lib/rubypivot/version.rb
CHANGED
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.
|
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:
|
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
|