spout 0.10.0.beta9 → 0.10.0.beta10

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
  SHA1:
3
- metadata.gz: 1510bf903f7dada97ac4f63538881fe5cddb0825
4
- data.tar.gz: 8678041008439bd0eaea5e163eb6d4fc6337469b
3
+ metadata.gz: cb6c2e9b16149cba7a217e1e5dfec5764ac33e8a
4
+ data.tar.gz: e1a7fbd00c9c5678089d8d6266e791121fc2d468
5
5
  SHA512:
6
- metadata.gz: 390ca815b08364e3d3f8e068dd1104d784bdc48eb85bd2da9fc6e3d44562aec5ac75e22524d0580c1f0dba8ffec341021e463ac9b57d627ac7ae6b8e85d846f4
7
- data.tar.gz: fa7bf7eb06e667c972354bd7b81f4d32e7f0c37de0a7115c06f484361943a7a9b737020b9434c4b7d132403035aa9c9f1caf89fccdfd3cf87e8b0357163fe935
6
+ metadata.gz: b93e28aa28e19ef8d3bec0fbc5107e8bae5b835eaca307bac36dec4c151ee3c7c7e27ba0ff8f050d81d1538256490b8cb786936b4d61acb1c71980da7860b662
7
+ data.tar.gz: 9e7d49b85dc352e0289de396ef75bfdebec45064a05ef992c2efe872519c61c901cc4811f60ea48c123dfa4b67c86aa634270e54c8f7a4dd1bec08f6281d143c
data/CHANGELOG.md CHANGED
@@ -15,6 +15,11 @@
15
15
  - **Testing Changes**
16
16
  - Tests now include checks to assure that variable display_name fields don't exceed 255 length requirement
17
17
  - `include Spout::Tests::VariableDisplayNameLength`
18
+ - **Gem Changes**
19
+ - Use of Ruby 2.1.4 is now recommended
20
+
21
+ ### Refactoring
22
+ - Removing ChartTypes class in favor of a more modular Graph and Table class
18
23
 
19
24
  ## 0.9.1 (October 14, 2014)
20
25
 
@@ -7,6 +7,9 @@ require 'colorize'
7
7
 
8
8
  require 'spout/helpers/subject_loader'
9
9
  require 'spout/helpers/chart_types'
10
+ require 'spout/models/variable'
11
+ require 'spout/models/graphables'
12
+ require 'spout/models/tables'
10
13
  require 'spout/helpers/config_reader'
11
14
  require 'spout/helpers/send_file'
12
15
  require 'spout/version'
@@ -27,7 +30,9 @@ module Spout
27
30
 
28
31
  @config = Spout::Helpers::ConfigReader.new
29
32
 
30
- if Spout::Helpers::ChartTypes::get_json(@config.visit, 'variable') == nil
33
+ @stratification_variable = Spout::Models::Variable.find_by_id @config.visit
34
+
35
+ if @stratification_variable == nil
31
36
  if @config.visit == ''
32
37
  puts "The visit variable in .spout.yml can't be blank."
33
38
  else
@@ -36,7 +41,7 @@ module Spout
36
41
  return self
37
42
  end
38
43
 
39
- missing_variables = @config.charts.select{|c| Spout::Helpers::ChartTypes::get_json(c['chart'], 'variable') == nil}
44
+ missing_variables = @config.charts.select{|c| Spout::Models::Variable.find_by_id(c['chart']) == nil}
40
45
  if missing_variables.count > 0
41
46
  puts "Could not find the following chart variable#{'s' unless missing_variables.size == 1}: #{missing_variables.join(', ')}"
42
47
  return self
@@ -54,7 +59,8 @@ module Spout
54
59
 
55
60
  @chart_variables = @config.charts.unshift( { "chart" => @config.visit, "title" => 'Histogram' } )
56
61
 
57
- @variable_files = Dir.glob('variables/**/*.json')
62
+ @dictionary_root = Dir.pwd
63
+ @variable_files = Dir.glob(File.join(@dictionary_root, 'variables', '**', '*.json'))
58
64
 
59
65
  t = Time.now
60
66
  @graphs_folder = File.join("graphs", @standard_version)
@@ -87,22 +93,23 @@ module Spout
87
93
 
88
94
  def compute_tables_and_charts
89
95
  variable_files_count = @variable_files.count
96
+
90
97
  @variable_files.each_with_index do |variable_file, file_index|
91
- json = JSON.parse(File.read(variable_file)) rescue json = nil
92
- next unless json
93
- next unless @valid_ids.include?(json["id"].to_s.downcase) or @valid_ids.size == 0
94
- next unless ["numeric", "integer", "choices"].include?(json["type"])
95
- variable_name = json['id'].to_s.downcase
96
- next unless Spout::Models::Subject.method_defined?(variable_name)
98
+ variable = Spout::Models::Variable.new(variable_file, @dictionary_root)
99
+
100
+ next unless variable.errors.size == 0
101
+ next unless @valid_ids.include?(variable.id) or @valid_ids.size == 0
102
+ next unless ["numeric", "integer", "choices"].include?(variable.type)
103
+ next unless Spout::Models::Subject.method_defined?(variable.id)
97
104
 
98
105
  if @deploy_mode
99
106
  print "\r Graph Generation: " + "#{"% 3d" % ((file_index+1)*100/variable_files_count)}% Uploaded".colorize(:white)
100
107
  else
101
- puts "#{file_index+1} of #{variable_files_count}: #{variable_file.gsub(/(^variables\/|\.json$)/, '').gsub('/', ' / ')}"
108
+ puts "#{file_index+1} of #{variable_files_count}: #{variable.folder}#{variable.id}"
102
109
  end
103
110
 
104
- @progress[variable_name] ||= {}
105
- next if (not @deploy_mode and @progress[variable_name]['generated'] == true) or (@deploy_mode and @progress[variable_name]['uploaded'] == true)
111
+ @progress[variable.id] ||= {}
112
+ next if (not @deploy_mode and @progress[variable.id]['generated'] == true) or (@deploy_mode and @progress[variable.id]['uploaded'] == true)
106
113
 
107
114
  stats = {
108
115
  charts: {},
@@ -112,38 +119,43 @@ module Spout
112
119
  @chart_variables.each do |chart_type_hash|
113
120
  chart_type = chart_type_hash["chart"]
114
121
  chart_title = chart_type_hash["title"].downcase.gsub(' ', '-')
122
+ chart_variable = Spout::Models::Variable.find_by_id(chart_type)
115
123
 
116
124
  if chart_type == @config.visit
117
- filtered_subjects = @subjects.select{ |s| s.send(chart_type) != nil } # and s.send(variable_name) != nil
125
+ filtered_subjects = @subjects.select{ |s| s.send(chart_type) != nil }
118
126
  if filtered_subjects.count > 0
119
- stats[:charts][chart_title] = Spout::Helpers::ChartTypes::chart_histogram(chart_type, filtered_subjects, json, variable_name)
120
- stats[:tables][chart_title] = Spout::Helpers::ChartTypes::table_arbitrary(chart_type, filtered_subjects, json, variable_name)
127
+ graph = Spout::Models::Graphables.for(variable, chart_variable, nil, filtered_subjects)
128
+ stats[:charts][chart_title] = graph.to_hash
129
+ table = Spout::Models::Tables.for(variable, chart_variable, filtered_subjects, nil)
130
+ stats[:tables][chart_title] = table.to_hash
121
131
  end
122
132
  else
123
- filtered_subjects = @subjects.select{ |s| s.send(chart_type) != nil } # and s.send(variable_name) != nil
124
- if filtered_subjects.collect(&variable_name.to_sym).compact.count > 0
125
- stats[:charts][chart_title] = Spout::Helpers::ChartTypes::chart_arbitrary(chart_type, filtered_subjects, json, variable_name, visits)
126
- stats[:tables][chart_title] = visits.collect do |visit_display_name, visit_value|
127
- visit_subjects = filtered_subjects.select{ |s| s._visit == visit_value }
128
- unknown_subjects = visit_subjects.select{ |s| s.send(variable_name) == nil }
129
- (visit_subjects.count > 0 && visit_subjects.count != unknown_subjects.count) ? Spout::Helpers::ChartTypes::table_arbitrary(chart_type, visit_subjects, json, variable_name, visit_display_name) : nil
133
+ filtered_subjects = @subjects.select{ |s| s.send(chart_type) != nil }
134
+ if filtered_subjects.collect(&variable.id.to_sym).compact.count > 0
135
+ graph = Spout::Models::Graphables.for(variable, chart_variable, @stratification_variable, filtered_subjects)
136
+ stats[:charts][chart_title] = graph.to_hash
137
+ stats[:tables][chart_title] = @stratification_variable.domain.options.collect do |option|
138
+ visit_subjects = filtered_subjects.select{ |s| s._visit == option.value }
139
+ unknown_subjects = visit_subjects.select{ |s| s.send(variable.id) == nil }
140
+ table = Spout::Models::Tables.for(variable, chart_variable, visit_subjects, option.display_name)
141
+ (visit_subjects.count > 0 && visit_subjects.count != unknown_subjects.count) ? table.to_hash : nil
130
142
  end.compact
131
143
  end
132
144
  end
133
145
  end
134
146
 
135
- chart_json_file = File.join(@graphs_folder, "#{json['id']}.json")
147
+ chart_json_file = File.join(@graphs_folder, "#{variable.id}.json")
136
148
  File.open(chart_json_file, 'w') { |file| file.write( JSON.pretty_generate(stats) + "\n" ) }
137
149
 
138
- @progress[variable_name]['generated'] = true
150
+ @progress[variable.id]['generated'] = true
139
151
 
140
- if @deploy_mode and not @progress[variable_name]['uploaded'] == true
152
+ if @deploy_mode and not @progress[variable.id]['uploaded'] == true
141
153
  response = send_to_server(chart_json_file)
142
154
  if response.kind_of?(Hash) and response['upload'] == 'success'
143
- @progress[variable_name]['uploaded'] = true
155
+ @progress[variable.id]['uploaded'] = true
144
156
  else
145
157
  puts "\nUPLOAD FAILED: ".colorize(:red) + File.basename(chart_json_file)
146
- @progress[variable_name]['uploaded'] = false
158
+ @progress[variable.id]['uploaded'] = false
147
159
  end
148
160
  end
149
161
 
@@ -156,13 +168,6 @@ module Spout
156
168
  response = Spout::Helpers::SendFile.post("#{@url}/datasets/#{@slug}/upload_graph.json", chart_json_file, @standard_version, @token)
157
169
  end
158
170
 
159
- # [["Visit 1", "1"], ["Visit 2", "2"], ["CVD Outcomes", "3"]]
160
- def visits
161
- @visits ||= begin
162
- Spout::Helpers::ChartTypes::domain_array(@config.visit)
163
- end
164
- end
165
-
166
171
  end
167
172
  end
168
173
  end
@@ -4,6 +4,8 @@ require 'rubygems'
4
4
  require 'json'
5
5
  require 'yaml'
6
6
 
7
+ require 'spout/models/variable'
8
+ require 'spout/models/graphables'
7
9
  require 'spout/helpers/subject_loader'
8
10
  require 'spout/helpers/chart_types'
9
11
  require 'spout/helpers/config_reader'
@@ -68,7 +70,9 @@ module Spout
68
70
  FileUtils.mkpath( options_folder )
69
71
  tmp_options_file = File.join( options_folder, 'options.json' )
70
72
 
73
+ chart_variable = Spout::Models::Variable.find_by_id(@config.visit)
71
74
  variable_files_count = @variable_files.count
75
+
72
76
  @variable_files.each_with_index do |variable_file, file_index|
73
77
  json = JSON.parse(File.read(variable_file)) rescue json = nil
74
78
  next unless json
@@ -90,9 +94,10 @@ module Spout
90
94
 
91
95
  filtered_subjects = @subjects.select{ |s| s.send(@config.visit) != nil }
92
96
 
93
- chart_json = Spout::Helpers::ChartTypes::chart_histogram(@config.visit, filtered_subjects, json, variable_name)
97
+ variable = Spout::Models::Variable.find_by_id variable_name
98
+ graph = Spout::Models::Graphables.for(variable, chart_variable, nil, filtered_subjects)
94
99
 
95
- if chart_json
100
+ if graph.valid?
96
101
  File.open(tmp_options_file, "w") do |outfile|
97
102
  outfile.puts <<-eos
98
103
  {
@@ -106,21 +111,21 @@ module Spout
106
111
  "text": ""
107
112
  },
108
113
  "xAxis": {
109
- "categories": #{chart_json[:categories].to_json}
114
+ "categories": #{graph.categories.to_json}
110
115
  },
111
116
  "yAxis": {
112
117
  "title": {
113
- "text": #{chart_json[:units].to_json}
118
+ "text": #{graph.units.to_json}
114
119
  }
115
120
  },
116
121
  "plotOptions": {
117
122
  "column": {
118
123
  "pointPadding": 0.2,
119
124
  "borderWidth": 0,
120
- "stacking": #{chart_json[:stacking].to_json}
125
+ "stacking": #{graph.stacking.to_json}
121
126
  }
122
127
  },
123
- "series": #{chart_json[:series].to_json}
128
+ "series": #{graph.series.to_json}
124
129
  }
125
130
  eos
126
131
  end
@@ -33,389 +33,6 @@ module Spout
33
33
  end
34
34
  buckets
35
35
  end
36
-
37
- def self.get_json(file_name, file_type)
38
- file = Dir.glob("#{file_type.to_s.downcase}s/**/#{file_name.to_s.downcase}.json", File::FNM_CASEFOLD).first
39
- json = JSON.parse(File.read(file)) rescue json = nil
40
- json
41
- end
42
-
43
- def self.get_variable(variable_name)
44
- get_json(variable_name, 'variable')
45
- end
46
-
47
- def self.get_domain(json)
48
- get_json(json['domain'], 'domain')
49
- end
50
-
51
- def self.domain_array(variable_name)
52
- variable_file = Dir.glob("variables/**/#{variable_name.to_s.downcase}.json", File::FNM_CASEFOLD).first
53
- json = JSON.parse(File.read(variable_file)) rescue json = nil
54
- if json
55
- domain_json = get_domain(json)
56
- domain_json ? domain_json.collect{|option_hash| [option_hash['display_name'], option_hash['value']]} : []
57
- else
58
- []
59
- end
60
- end
61
-
62
- def self.chart_arbitrary_choices_by_quartile(chart_type, subjects, json, method)
63
- # CHART TYPE IS THE QUARTILE VARIABLE
64
- return unless chart_variable_json = get_variable(chart_type)
65
- return unless domain_json = get_domain(json)
66
-
67
- title = "#{json['display_name']} by #{chart_variable_json['display_name']}"
68
- subtitle = "By Visit"
69
- # categories = ["Quartile One", "Quartile Two", "Quartile Three", "Quartile Four"]
70
- units = 'percent'
71
- series = []
72
-
73
- filtered_subjects = subjects.select{ |s| s.send(method) != nil and s.send(chart_type) != nil }.sort_by(&chart_type.to_sym)
74
-
75
- all_subject_values = filtered_subjects.collect(&method.to_sym).compact.sort
76
- domain_json = remove_unused_missing_codes_from_domain(domain_json, all_subject_values.uniq)
77
-
78
- categories = [:quartile_one, :quartile_two, :quartile_three, :quartile_four].collect do |quartile|
79
- bucket = filtered_subjects.send(quartile).collect(&chart_type.to_sym)
80
- "#{bucket.min} to #{bucket.max}"
81
- end
82
-
83
- domain_json.each do |option_hash|
84
- data = [:quartile_one, :quartile_two, :quartile_three, :quartile_four].collect do |quartile|
85
- filtered_subjects.send(quartile).select{ |s| s.send(method) == option_hash['value'] }.count
86
- end
87
-
88
- series << { name: option_hash['display_name'], data: data } unless filtered_subjects.size == 0
89
- end
90
-
91
- { title: title, subtitle: subtitle, categories: categories, units: units, series: series, stacking: 'percent' }
92
- end
93
-
94
- def self.chart_arbitrary_by_quartile(chart_type, subjects, json, method, visits)
95
- # CHART TYPE IS THE QUARTILE VARIABLE
96
- return unless chart_variable_json = get_variable(chart_type)
97
- return chart_arbitrary_choices_by_quartile(chart_type, subjects, json, method) if json['type'] == 'choices'
98
-
99
- title = "#{json['display_name']} by #{chart_variable_json['display_name']}"
100
- subtitle = "By Visit"
101
- categories = ["Quartile One", "Quartile Two", "Quartile Three", "Quartile Four"]
102
- units = json["units"]
103
- series = []
104
-
105
- series = []
106
-
107
- visits.each do |visit_display_name, visit_value|
108
- data = []
109
- filtered_subjects = subjects.select{ |s| s._visit == visit_value and s.send(method) != nil and s.send(chart_type) != nil }.sort_by(&chart_type.to_sym)
110
-
111
- [:quartile_one, :quartile_two, :quartile_three, :quartile_four].each do |quartile|
112
- array = filtered_subjects.send(quartile).collect(&method.to_sym)
113
- data << { y: (array.mean.round(1) rescue 0.0),
114
- stddev: ("%0.1f" % array.standard_deviation rescue ''),
115
- median: ("%0.1f" % array.median rescue ''),
116
- min: ("%0.1f" % array.min rescue ''),
117
- max: ("%0.1f" % array.max rescue ''),
118
- n: array.n }
119
- end
120
-
121
- series << { name: visit_display_name, data: data } unless filtered_subjects.size == 0
122
- end
123
-
124
- { title: title, subtitle: subtitle, categories: categories, units: units, series: series }
125
- end
126
-
127
- def self.table_arbitrary_by_quartile(chart_type, subjects, json, method, subtitle = nil)
128
- return table_arbitrary_choices_by_quartile(chart_type, subjects, json, method, subtitle) if json['type'] == 'choices'
129
- # CHART TYPE IS THE QUARTILE VARIABLE
130
- return unless chart_variable_json = get_variable(chart_type)
131
-
132
-
133
- headers = [
134
- [""] + Spout::Helpers::ArrayStatistics::calculations.collect{|calculation_label, calculation_method| calculation_label} + ["Total"]
135
- ]
136
-
137
- filtered_subjects = subjects.select{ |s| s.send(method) != nil and s.send(chart_type) != nil }.sort_by(&chart_type.to_sym)
138
-
139
- rows = [:quartile_one, :quartile_two, :quartile_three, :quartile_four].collect do |quartile|
140
- bucket = filtered_subjects.send(quartile)
141
- row_subjects = bucket.collect(&method.to_sym)
142
- data = Spout::Helpers::ArrayStatistics::calculations.collect do |calculation_label, calculation_method, calculation_type, calculation_format|
143
- TableFormatting::format_number(row_subjects.send(calculation_method), calculation_type, calculation_format)
144
- end
145
-
146
- row_name = if row_subjects.size == 0
147
- quartile.to_s.capitalize.gsub('_one', ' One').gsub('_two', ' Two').gsub('_three', ' Three').gsub('_four', ' Four')
148
- else
149
- "#{bucket.collect(&chart_type.to_sym).min} to #{bucket.collect(&chart_type.to_sym).max} #{chart_variable_json['units']}"
150
- end
151
-
152
- [row_name] + data + [{ text: TableFormatting::format_number(row_subjects.count, :count), style: 'font-weight:bold'}]
153
- end
154
-
155
- total_values = Spout::Helpers::ArrayStatistics::calculations.collect do |calculation_label, calculation_method, calculation_type, calculation_format|
156
- total_count = filtered_subjects.collect(&method.to_sym).send(calculation_method)
157
- { text: TableFormatting::format_number(total_count, calculation_type, calculation_format), style: "font-weight:bold" }
158
- end
159
-
160
- footers = [
161
- [{ text: "Total", style: "font-weight:bold" }] + total_values + [{ text: TableFormatting::format_number(filtered_subjects.count, :count), style: 'font-weight:bold'}]
162
- ]
163
-
164
- { title: "#{chart_variable_json['display_name']} vs #{json['display_name']}", subtitle: subtitle, headers: headers, footers: footers, rows: rows }
165
-
166
- end
167
-
168
- def self.table_arbitrary_choices_by_quartile(chart_type, subjects, json, method, subtitle)
169
- # CHART TYPE IS THE QUARTILE VARIABLE
170
- return unless chart_variable_json = get_variable(chart_type)
171
- return unless domain_json = get_domain(json)
172
-
173
- filtered_subjects = subjects.select{ |s| s.send(method) != nil and s.send(chart_type) != nil }.sort_by(&chart_type.to_sym)
174
- all_subject_values = filtered_subjects.collect(&method.to_sym).compact.sort
175
- domain_json = remove_unused_missing_codes_from_domain(domain_json, all_subject_values.uniq)
176
-
177
- categories = [:quartile_one, :quartile_two, :quartile_three, :quartile_four].collect do |quartile|
178
- bucket = filtered_subjects.send(quartile).collect(&chart_type.to_sym)
179
- "#{bucket.min} to #{bucket.max} #{chart_variable_json['units']}"
180
- end
181
-
182
- headers = [
183
- [""] + categories + ["Total"]
184
- ]
185
-
186
- rows = []
187
-
188
- rows = domain_json.collect do |option_hash|
189
- row_subjects = filtered_subjects.select{ |s| s.send(method) == option_hash['value'] }
190
-
191
- data = [:quartile_one, :quartile_two, :quartile_three, :quartile_four].collect do |quartile|
192
- bucket = filtered_subjects.send(quartile).select{ |s| s.send(method) == option_hash['value'] }
193
- TableFormatting::format_number(bucket.count, :count)
194
- end
195
-
196
- [option_hash['display_name']] + data + [{ text: TableFormatting::format_number(row_subjects.count, :count), style: 'font-weight:bold'}]
197
- end
198
-
199
-
200
- total_values = [:quartile_one, :quartile_two, :quartile_three, :quartile_four].collect do |quartile|
201
- { text: TableFormatting::format_number(filtered_subjects.send(quartile).count, :count), style: "font-weight:bold" }
202
- end
203
-
204
- footers = [
205
- [{ text: "Total", style: "font-weight:bold" }] + total_values + [{ text: TableFormatting::format_number(filtered_subjects.count, :count), style: 'font-weight:bold'}]
206
- ]
207
-
208
- { title: "#{json['display_name']} vs #{chart_variable_json['display_name']}", subtitle: subtitle, headers: headers, footers: footers, rows: rows }
209
- end
210
-
211
-
212
- def self.chart_arbitrary_choices(chart_type, subjects, json, method)
213
- return unless chart_variable_json = get_variable(chart_type)
214
- return unless chart_variable_domain = domain_array(chart_type)
215
- return unless domain_json = get_domain(json)
216
-
217
-
218
- title = "#{json['display_name']} by #{chart_variable_json['display_name']}"
219
- subtitle = "By Visit"
220
- categories = chart_variable_domain.collect{|a| a[0]}
221
- units = 'percent'
222
- series = []
223
-
224
- all_subject_values = subjects.collect(&method.to_sym).compact.sort
225
- domain_json = remove_unused_missing_codes_from_domain(domain_json, all_subject_values.uniq)
226
-
227
- domain_json.each do |option_hash|
228
- domain_values = subjects.select{ |s| s.send(method) == option_hash['value'] }
229
-
230
- data = chart_variable_domain.collect do |display_name, value|
231
- domain_values.select{ |s| s.send(chart_type) == value }.count
232
- end
233
- series << { name: option_hash['display_name'], data: data }
234
- end
235
-
236
- { title: title, subtitle: subtitle, categories: categories, units: units, series: series, stacking: 'percent' }
237
- end
238
-
239
- def self.chart_arbitrary(chart_type, subjects, json, method, visits)
240
- return unless chart_variable_json = get_variable(chart_type)
241
- return unless chart_variable_domain = domain_array(chart_type)
242
- return chart_arbitrary_by_quartile(chart_type, subjects, json, method, visits) if ['numeric', 'integer'].include?(chart_variable_json['type'])
243
-
244
- return chart_arbitrary_choices(chart_type, subjects, json, method) if json['type'] == 'choices'
245
-
246
- title = "#{json['display_name']} by #{chart_variable_json['display_name']}"
247
- subtitle = "By Visit"
248
- categories = []
249
- units = json["units"]
250
- series = []
251
-
252
- data = []
253
-
254
- visits.each do |visit_display_name, visit_value|
255
- visit_subjects = subjects.select{ |s| s._visit == visit_value and s.send(method) != nil }
256
- if visit_subjects.count > 0
257
- chart_variable_domain.each_with_index do |(display_name, value), index|
258
- values = visit_subjects.select{|s| s.send(chart_type) == value }.collect(&method.to_sym)
259
- data[index] ||= []
260
- data[index] << (values.mean.round(2) rescue 0.0)
261
- end
262
- categories << visit_display_name
263
- end
264
- end
265
-
266
- chart_variable_domain.each_with_index do |(display_name, value), index|
267
- series << { name: display_name, data: data[index] }
268
- end
269
-
270
- { title: title, subtitle: subtitle, categories: categories, units: units, series: series }
271
- end
272
-
273
-
274
- def self.table_arbitrary(chart_type, subjects, json, method, subtitle = nil)
275
- return unless chart_variable_json = get_variable(chart_type)
276
- return unless chart_variable_domain = domain_array(chart_type)
277
- return table_arbitrary_by_quartile(chart_type, subjects, json, method, subtitle) if ['numeric', 'integer'].include?(chart_variable_json['type'])
278
- return table_arbitrary_choices(chart_type, subjects, json, method, subtitle) if json['type'] == 'choices'
279
-
280
- headers = [
281
- [""] + Spout::Helpers::ArrayStatistics::calculations.collect{|calculation_label, calculation_method| calculation_label} + ["Total"]
282
- ]
283
-
284
- filtered_subjects = subjects.select{ |s| s.send(chart_type) != nil }
285
-
286
- rows = chart_variable_domain.collect do |display_name, value|
287
- row_subjects = filtered_subjects.select{ |s| s.send(chart_type) == value }
288
-
289
- row_cells = Spout::Helpers::ArrayStatistics::calculations.collect do |calculation_label, calculation_method, calculation_type, calculation_format|
290
- count = row_subjects.collect(&method.to_sym).send(calculation_method)
291
- (count == 0 && calculation_method == :count) ? { text: '-', class: 'text-muted' } : TableFormatting::format_number(count, calculation_type, calculation_format)
292
- end
293
-
294
- [display_name] + row_cells + [{ text: TableFormatting::format_number(row_subjects.count, :count), style: 'font-weight:bold'}]
295
- end
296
-
297
- total_values = Spout::Helpers::ArrayStatistics::calculations.collect do |calculation_label, calculation_method, calculation_type, calculation_format|
298
- total_count = filtered_subjects.collect(&method.to_sym).send(calculation_method)
299
- { text: TableFormatting::format_number(total_count, calculation_type, calculation_format), style: "font-weight:bold" }
300
- end
301
-
302
- footers = [
303
- [{ text: "Total", style: "font-weight:bold" }] + total_values + [{ text: TableFormatting::format_number(filtered_subjects.count, :count), style: 'font-weight:bold'}]
304
- ]
305
-
306
- { title: "#{chart_variable_json['display_name']} vs #{json['display_name']}", subtitle: subtitle, headers: headers, footers: footers, rows: rows }
307
-
308
- end
309
-
310
- def self.table_arbitrary_choices(chart_type, subjects, json, method, subtitle)
311
- return unless chart_variable_json = get_variable(chart_type)
312
- return unless chart_variable_domain = domain_array(chart_type)
313
- return unless domain_json = get_domain(json)
314
-
315
- headers = [
316
- [""] + chart_variable_domain.collect{|display_name, value| display_name} + ["Total"]
317
- ]
318
-
319
- filtered_subjects = subjects.select{ |s| s.send(chart_type) != nil }
320
-
321
- all_subject_values = filtered_subjects.collect(&method.to_sym).compact.sort
322
- domain_json = remove_unused_missing_codes_from_domain(domain_json, all_subject_values.uniq)
323
-
324
- rows = domain_json.collect do |option_hash|
325
- row_subjects = filtered_subjects.select{ |s| s.send(method) == option_hash['value'] }
326
- row_cells = chart_variable_domain.collect do |display_name, value|
327
- count = row_subjects.select{ |s| s.send(chart_type) == value }.count
328
- count > 0 ? TableFormatting::format_number(count, :count) : { text: '-', class: 'text-muted' }
329
- end
330
-
331
- total = row_subjects.count
332
-
333
- [option_hash['display_name']] + row_cells + [total == 0 ? { text: '-', class: 'text-muted' } : { text: TableFormatting::format_number(total, :count), style: 'font-weight:bold'}]
334
- end
335
-
336
- if filtered_subjects.select{|s| s.send(method) == nil }.count > 0
337
- unknown_values = chart_variable_domain.collect do |display_name, value|
338
- { text: TableFormatting::format_number(filtered_subjects.select{ |s| s.send(chart_type) == value and s.send(method) == nil }.count, :count), class: 'text-muted' }
339
- end
340
- rows << [{ text: 'Unknown', class: 'text-muted'}] + unknown_values + [ { text: TableFormatting::format_number(filtered_subjects.select{|s| s.send(method) == nil}.count, :count), style: 'font-weight:bold', class: 'text-muted' } ]
341
- end
342
-
343
-
344
-
345
- total_values = chart_variable_domain.collect do |display_name, value|
346
- total_count = filtered_subjects.select{|s| s.send(chart_type) == value }.count
347
- { text: (total_count == 0 ? "-" : TableFormatting::format_number(total_count, :count)), style: "font-weight:bold" }
348
- end
349
-
350
- footers = [
351
- [{ text: "Total", style: "font-weight:bold" }] + total_values + [{ text: TableFormatting::format_number(filtered_subjects.count, :count), style: 'font-weight:bold'}]
352
- ]
353
-
354
- { title: "#{json['display_name']} vs #{chart_variable_json['display_name']}", subtitle: subtitle, headers: headers, footers: footers, rows: rows }
355
- end
356
-
357
- def self.chart_histogram(chart_type, subjects, json, method)
358
- domain_json = get_domain(json)
359
- return if json['type'] == 'choices' and not domain_json
360
- return unless chart_variable_json = get_variable(chart_type)
361
- return unless chart_variable_domain = domain_array(chart_type)
362
-
363
- title = "#{json['display_name']}"
364
- subtitle = "By Visit"
365
- units = "Subjects"
366
- x_axis_units = json["units"]
367
- series = []
368
-
369
- all_subject_values = subjects.collect(&method.to_sym).compact.sort
370
- return nil if all_subject_values.count == 0
371
-
372
- domain_json = remove_unused_missing_codes_from_domain(domain_json, all_subject_values.uniq) if domain_json
373
-
374
- categories = pull_categories(json, method, all_subject_values, domain_json)
375
-
376
- buckets = continuous_buckets(all_subject_values)
377
-
378
- chart_variable_domain.each do |display_name, value|
379
- visit_subjects = subjects.select{ |s| s.send(chart_type) == value and s.send(method) != nil }.collect(&method.to_sym).sort
380
- next unless visit_subjects.size > 0
381
-
382
- data = pull_data(json, visit_subjects, buckets, categories, domain_json)
383
-
384
- series << { name: display_name, data: data }
385
- end
386
-
387
- { title: title, subtitle: subtitle, categories: categories, units: units, series: series, x_axis_title: x_axis_units }
388
- end
389
-
390
- def self.pull_categories(json, method, all_subject_values, domain_json)
391
- categories = []
392
- if json['type'] == 'choices'
393
- categories = domain_json.collect{|option_hash| option_hash['display_name']}
394
- else
395
- buckets = continuous_buckets(all_subject_values)
396
- categories = buckets.collect{|b| "#{b[0]} to #{b[1]}"}
397
- end
398
- categories
399
- end
400
-
401
- def self.pull_data(json, visit_subjects, buckets, categories, domain_json)
402
- data = []
403
- if json['type'] == 'choices'
404
- domain_json.each do |option_hash|
405
- data << visit_subjects.select{ |v| v == option_hash['value'] }.count
406
- end
407
- else
408
- visit_subjects.group_by{|v| get_bucket(buckets, v) }.each do |key, values|
409
- data[categories.index(key)] = values.count if categories.index(key)
410
- end
411
- end
412
- data
413
- end
414
-
415
- def self.remove_unused_missing_codes_from_domain(domain_json, unique_subject_values)
416
- domain_json.select{|option_hash| option_hash['missing'] != true or (option_hash['missing'] == true and unique_subject_values.include?(option_hash['value']))}
417
- end
418
-
419
36
  end
420
37
  end
421
38
  end
@@ -0,0 +1,22 @@
1
+ module Spout
2
+ module Models
3
+ class Bucket
4
+
5
+ attr_accessor :start, :stop
6
+
7
+ def initialize(start, stop)
8
+ @start = start
9
+ @stop = stop
10
+ end
11
+
12
+ def in_bucket?(value)
13
+ value >= @start and value <= @stop
14
+ end
15
+
16
+ def display_name
17
+ "#{@start} to #{@stop}"
18
+ end
19
+
20
+ end
21
+ end
22
+ end
@@ -1,11 +1,13 @@
1
1
  require 'json'
2
2
 
3
+ require 'spout/models/record'
3
4
  require 'spout/models/option'
4
5
 
5
6
  module Spout
6
7
  module Models
7
8
 
8
- class Domain
9
+ class Domain < Spout::Models::Record
10
+
9
11
  attr_accessor :id, :folder, :options
10
12
  attr_reader :errors
11
13