report_html 0.3.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,11 @@
1
+ CanvasXpress - JavaScript Canvas Library Copyright (c) 2009-2015 Isaac Neuhaus imnphd@gmail.com
2
+
3
+ Redistributions of the source code must retain this copyright notice and the following disclaimer.
4
+
5
+ CanvasXpress is licensed under the terms of the Open Source GPL version 3.0 license.
6
+
7
+ This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
8
+
9
+ Please see a copy of the GNU General Public License at http://www.gnu.org/licenses.
10
+
11
+ Commercial use may be granted to the extent that this source code does NOT become part of any other Open Source or Commercially licensed development library or toolkit without explicit permission.
@@ -0,0 +1,6 @@
1
+ require "report_html/version"
2
+ require "report_html/report_html"
3
+
4
+ module ReportHtml
5
+ # Your code goes here...
6
+ end
@@ -0,0 +1,561 @@
1
+ require 'erb'
2
+ require 'fileutils'
3
+ require 'json'
4
+ require 'base64'
5
+
6
+ JS_FOLDER = File.expand_path(File.join(__FILE__, '..', '..', '..', 'js'))
7
+
8
+ class Report_html
9
+ def initialize(hash_vars, title = "report", data_from_files = false)
10
+ @all_report = ""
11
+ @title = title
12
+ @hash_vars = hash_vars
13
+ @data_from_files = data_from_files
14
+ @plots_data = []
15
+ @count_objects = 0
16
+ end
17
+
18
+ def build(template)
19
+ renderered_template = ERB.new(template).result(binding)
20
+ @all_report = "<HTML>\n"
21
+ make_head
22
+ build_body do
23
+ renderered_template
24
+ end
25
+ @all_report << "\n</HTML>"
26
+ end
27
+
28
+ def build_body
29
+ @all_report << "<body onload=\"initPage();\">\n#{yield}\n</body>\n"
30
+ end
31
+
32
+ def make_head
33
+ @all_report << "\t<title>#{@title}</title>
34
+ <head>
35
+ <meta http-equiv=\"CACHE-CONTROL\" CONTENT=\"NO-CACHE\">
36
+ <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />
37
+ <meta http-equiv=\"Content-Language\" content=\"en-us\" />
38
+
39
+ <link rel=\"stylesheet\" href=\"js/canvasXpress.css\" type=\"text/css\"/>
40
+ <script type=\"text/javascript\" src=\"js/canvasXpress.min.js\"></script>
41
+ <script>
42
+ var initPage = function () {
43
+ <% @plots_data.each do |plot_data| %>
44
+ <%= plot_data %>
45
+ <% end %>
46
+ }
47
+ </script>
48
+ </head>\n"
49
+ end
50
+
51
+ def get_report #return all html string
52
+ renderer = ERB.new(@all_report)
53
+ return renderer.result(binding) #binding does accesible all the current ruby enviroment to erb
54
+ end
55
+
56
+ def write(file)
57
+ dir = File.dirname(file)
58
+ string_report = get_report
59
+ FileUtils.cp_r(JS_FOLDER, dir)
60
+ File.open(file, 'w'){|f| f.puts string_report}
61
+ end
62
+
63
+ # REPORT SYNTAX METHODS
64
+ ###################################################################################
65
+
66
+ # DATA MANIPULATION METHODS
67
+ #-------------------------------------------------------------------------------------
68
+ def get_data(options)
69
+ data = []
70
+ data = extract_data(options)
71
+ if !data.empty?
72
+ if @data_from_files # If data on container is loaded using html_report as lib, we don't care about data format
73
+ # if data comes from files and is loaded as strings. We need to format correctly the data.
74
+ rows = data.length
75
+ cols = data.first.length
76
+ if !options[:text]
77
+ rows.times do |r|
78
+ cols.times do |c|
79
+ next if r == 0 && options[:header]
80
+ next if c == 0 && options[:row_names]
81
+ data[r][c] = data[r][c].to_f
82
+ end
83
+ end
84
+ end
85
+ end
86
+ add_header_row_names(data, options)
87
+ data = data.transpose if options[:transpose]
88
+ end
89
+ return data
90
+ end
91
+
92
+ def add_header_row_names(data, options)
93
+ if options[:add_header_row_names] # This check if html object needs a default header/row_names or not
94
+ if !options[:header]
95
+ range = 0..(data.first.length - 1)
96
+ data.unshift(range.to_a)
97
+ end
98
+ if !options[:row_names]
99
+ data.each_with_index do |row, i|
100
+ row.unshift(i)
101
+ end
102
+ end
103
+ end
104
+ end
105
+
106
+ def extract_data(options)
107
+ data = []
108
+ ids = options[:id]
109
+ fields = options[:fields]
110
+ ids = ids.split(',') if ids.class == String && ids.include?(',') # String syntax
111
+ if ids.class == Array
112
+ fields = fields.split(';').map{|data_fields| data_fields.split(',').map{|fields| fields.to_i} } if fields.class == String # String syntax
113
+ ids.each_with_index do |id, n|
114
+ data_file = extract_fields(id, fields[n])
115
+ if data.empty?
116
+ data.concat(data_file)
117
+ else
118
+ data.each_with_index do |row, n|
119
+ data[n] = row + data_file[n]
120
+ end
121
+ end
122
+ end
123
+ else
124
+ fields = fields.first if fields.class == Array
125
+ data = extract_fields(ids, options[:fields])
126
+ end
127
+ return data
128
+ end
129
+
130
+ def extract_fields(id, fields)
131
+ data = []
132
+ @hash_vars[id].each do |row|
133
+ if fields.empty?
134
+ data << row.dup # Dup generates a array copy that avoids to modify original objects on data manipulation creating graphs
135
+ else
136
+ data << fields.map{|field| row[field]} #Map without bang do the same than dup
137
+ end
138
+ end
139
+ return data
140
+ end
141
+
142
+ # TABLE METHODS
143
+ #-------------------------------------------------------------------------------------
144
+ def table(user_options = {}, &block)
145
+ options = {
146
+ id: nil,
147
+ header: false,
148
+ row_names: false,
149
+ add_header_row_names: false,
150
+ transpose: false,
151
+ fields: [],
152
+ border: 1,
153
+ cell_align: [],
154
+ attrib: {}
155
+ }
156
+ options.merge!(user_options)
157
+ table_attr = prepare_table_attribs(options[:attrib])
158
+ array_data = get_data(options)
159
+ block.call(array_data) if !block.nil?
160
+ rowspan, colspan = get_col_n_row_span(array_data)
161
+ html = "
162
+ <table border=\"#{options[:border]}\" #{table_attr}>
163
+ <% array_data.each_with_index do |row, i| %>
164
+ <tr>
165
+ <% row.each_with_index do |cell, j|
166
+ if cell != 'colspan' && cell != 'rowspan'
167
+ if i == 0 && options[:header] %>
168
+ <th <%= get_span(colspan, rowspan, i, j) %>><%= cell %></th>
169
+ <% else %>
170
+ <td <%= get_cell_align(options[:cell_align], j) %> <%= get_span(colspan, rowspan, i, j) %>><%= cell %></td>
171
+ <% end
172
+ end %>
173
+ <% end %>
174
+ </tr>
175
+ <% end %>
176
+ </table>
177
+ "
178
+ return ERB.new(html).result(binding)
179
+ end
180
+
181
+ def get_span(colspan, rowspan, row, col)
182
+ span = []
183
+ colspan_value = colspan[row][col]
184
+ rowspan_value = rowspan[row][col]
185
+ if colspan_value > 1
186
+ span << "colspan=\"#{colspan_value}\""
187
+ end
188
+ if rowspan_value > 1
189
+ span << "rowspan=\"#{rowspan_value}\""
190
+ end
191
+ return span.join(' ')
192
+ end
193
+
194
+ def get_col_n_row_span(table)
195
+ colspan = []
196
+ rowspan = []
197
+ last_row = 0
198
+ table.each_with_index do |row, r|
199
+ rowspan << Array.new(row.length, 1)
200
+ colspan << Array.new(row.length, 1)
201
+ last_col = 0
202
+ row.each_with_index do |col, c|
203
+ if col == 'colspan'
204
+ colspan[r][last_col] += 1
205
+ else
206
+ last_col = c
207
+ end
208
+ if col == 'rowspan'
209
+ rowspan[last_row][c] += 1
210
+ else
211
+ last_row = r
212
+ end
213
+ end
214
+ end
215
+ return rowspan, colspan
216
+ end
217
+
218
+
219
+ def get_cell_align(align_vector, position)
220
+ cell_align = ''
221
+ if !align_vector.empty?
222
+ align = align_vector[position]
223
+ cell_align = "align=\"#{align}\""
224
+ end
225
+ return cell_align
226
+ end
227
+
228
+ def prepare_table_attribs(attribs)
229
+ attribs_string = ''
230
+ if !attribs.empty?
231
+ attribs.each do |attrib, value|
232
+ attribs_string << "#{attrib}= \"#{value}\" "
233
+ end
234
+ end
235
+ return attribs_string
236
+ end
237
+
238
+
239
+ # CANVASXPRESS METHODS
240
+ #-------------------------------------------------------------------------------------
241
+ def add_sample_attributes(data_structure, options)
242
+ parsed_sample_attributes = {}
243
+ options[:sample_attributes].each do |key, col|
244
+ data = get_data({id: options[:id], fields: [col], text: true})
245
+ data.shift if options[:header]
246
+ parsed_sample_attributes[key] = data.flatten
247
+ end
248
+ data_structure['x'] = parsed_sample_attributes
249
+ end
250
+
251
+ def tree_from_file(file)
252
+ string_tree = File.open(file).read.gsub("\n", '')
253
+ return string_tree
254
+ end
255
+
256
+ def set_tree(options, config)
257
+ tree = tree_from_file(options[:tree])
258
+ if options[:treeBy] == 's'
259
+ config['smpDendrogramNewick'] = tree
260
+ config['samplesClustered'] = true
261
+ elsif options[:treeBy] == 'v'
262
+ config['varDendrogramNewick'] = tree
263
+ config['variablesClustered'] = true
264
+ end
265
+ end
266
+
267
+ def canvasXpress_main(user_options, block = nil)
268
+ # Handle arguments
269
+ #------------------------------------------
270
+ options = {
271
+ id: nil,
272
+ fields: [],
273
+ data_format: 'one_axis',
274
+ responsive: true,
275
+ height: '600px',
276
+ width: '600px',
277
+ header: false,
278
+ row_names: false,
279
+ add_header_row_names: true,
280
+ transpose: true,
281
+ x_label: 'x_axis',
282
+ title: 'Title',
283
+ sample_attributes: {},
284
+ config: {},
285
+ after_render: [],
286
+ treeBy: 's'
287
+ }
288
+ options.merge!(user_options)
289
+ config = {
290
+ 'toolbarPermanent' => true,
291
+ 'xAxisTitle' => options[:x_label],
292
+ 'title' => options[:title]
293
+ }
294
+ if !options[:tree].nil?
295
+ set_tree(options, config)
296
+ end
297
+ config.merge!(options[:config])
298
+ # Data manipulation
299
+ #------------------------------------------
300
+ no_data_string = ERB.new("<div width=\"#{options[:width]}\" height=\"#{options[:height]}\" > <p>NO DATA<p></div>").result(binding)
301
+ data_array = get_data(options)
302
+ return no_data_string if data_array.empty?
303
+ block.call(data_array) if !block.nil?
304
+ object_id = "obj_#{@count_objects}_"
305
+ raise("ID #{options[:id]} has not data") if data_array.nil?
306
+ row_length = data_array.first.length
307
+ samples = data_array.shift[1..row_length]
308
+ return no_data_string if data_array.empty?
309
+ vars = []
310
+ data_array.each do |row|
311
+ vars << row.shift
312
+ end
313
+ values = data_array
314
+
315
+ yield(options, config, samples, vars, values, object_id)
316
+ # Build JSON objects and Javascript code
317
+ #-----------------------------------------------
318
+ @count_objects += 1
319
+ data_structure = {
320
+ 'y' => {
321
+ 'vars' => vars,
322
+ 'smps' => samples,
323
+ 'data' => values
324
+ }
325
+ }
326
+ events = false
327
+ info = false
328
+ afterRender = options[:after_render]
329
+ if options[:mod_data_structure] == 'boxplot'
330
+ data_structure['y']['smps'] = nil
331
+ data_structure.merge!({ 'x' => {'Factor' => samples}})
332
+ elsif options[:mod_data_structure] == 'circular'
333
+ data_structure.merge!({ 'z' => {'Ring' => options[:ring_assignation]}})
334
+ end
335
+ add_sample_attributes(data_structure, options) if !options[:sample_attributes].empty?
336
+ extracode = "#{options[:extracode]}\n"
337
+ extracode << "C#{object_id}.groupSamples(#{options[:group_samples]})\n" if !options[:group_samples].nil?
338
+ plot_data = "
339
+ var data = #{data_structure.to_json};
340
+ var conf = #{config.to_json};
341
+ var events = #{events.to_json};
342
+ var info = #{info.to_json};
343
+ var afterRender = #{afterRender.to_json};
344
+ var C#{object_id} = new CanvasXpress(\"#{object_id}\", data, conf, events, info, afterRender);\n#{extracode}"
345
+ @plots_data << plot_data
346
+
347
+ responsive = ''
348
+ responsive = "responsive='true'" if options[:responsive]
349
+ html = "<canvas id=\"#{object_id}\" width=\"#{options[:width]}\" height=\"#{options[:height]}\" aspectRatio='1:1' #{responsive}></canvas>"
350
+ return ERB.new(html).result(binding)
351
+ end
352
+
353
+ def line(user_options = {}, &block)
354
+ default_options = {
355
+ row_names: true
356
+ }.merge!(user_options)
357
+ html_string = canvasXpress_main(default_options, block) do |options, config, samples, vars, values, object_id|
358
+ config['graphType'] = 'Line'
359
+ end
360
+ return html_string
361
+ end
362
+
363
+ def stacked(user_options = {}, &block)
364
+ default_options = {
365
+ row_names: true,
366
+ }.merge!(user_options)
367
+ html_string = canvasXpress_main(default_options, block) do |options, config, samples, vars, values, object_id|
368
+ config['graphType'] = 'Stacked'
369
+ end
370
+ return html_string
371
+ end
372
+
373
+ def barplot(user_options = {}, &block)
374
+ default_options = {
375
+ row_names: true
376
+ }.merge!(user_options)
377
+ html_string = canvasXpress_main(default_options, block) do |options, config, samples, vars, values, object_id|
378
+ config['graphType'] = 'Bar'
379
+ end
380
+ return html_string
381
+ end
382
+
383
+ def heatmap(user_options = {}, &block)
384
+ default_options = {
385
+ row_names: true
386
+ }.merge!(user_options)
387
+ html_string = canvasXpress_main(default_options, block) do |options, config, samples, vars, values, object_id|
388
+ config['graphType'] = 'Heatmap'
389
+ end
390
+ return html_string
391
+ end
392
+
393
+ def boxplot(user_options = {}, &block)
394
+ default_options = {
395
+ row_names: true,
396
+ header: true
397
+ }.merge!(user_options)
398
+ html_string = canvasXpress_main(default_options, block) do |options, config, samples, vars, values, object_id|
399
+ config['graphType'] = 'Boxplot'
400
+ options[:mod_data_structure] = 'boxplot'
401
+ if options[:extracode].nil?
402
+ options[:extracode] = "C#{object_id}.groupSamples([\"Factor\"]);"
403
+ end
404
+ end
405
+ return html_string
406
+ end
407
+
408
+ def pie(user_options = {}, &block)
409
+ default_options = {
410
+ transpose: false
411
+ }.merge!(user_options)
412
+ html_string = canvasXpress_main(default_options, block) do |options, config, samples, vars, values, object_id|
413
+ config['graphType'] = 'Pie'
414
+ if samples.length > 1
415
+ config['showPieGrid'] = true
416
+ config['xAxis'] = samples
417
+ config['layout'] = "#{(samples.length.to_f/2).ceil}X2" if config['layout'].nil?
418
+ config['showPieSampleLabel'] = true if config['showPieSampleLabel'].nil?
419
+ end
420
+ end
421
+ return html_string
422
+ end
423
+
424
+ def sccater2D(user_options = {}, &block)
425
+ default_options = {
426
+ row_names: false,
427
+ transpose: false
428
+ }.merge!(user_options)
429
+ html_string = canvasXpress_main(default_options, block) do |options, config, samples, vars, values, object_id|
430
+ config['graphType'] = 'Scatter2D'
431
+ config['xAxis'] = [samples.first] if config['xAxis'].nil?
432
+ config['yAxis'] = samples[1..samples.length-1] if config['yAxis'].nil?
433
+ if default_options[:y_label].nil?
434
+ config['yAxisTitle'] = 'y_axis'
435
+ else
436
+ config['yAxisTitle'] = default_options[:y_label]
437
+ end
438
+ if options[:regressionLine]
439
+ options[:extracode] = "C#{object_id}.addRegressionLine();"
440
+ end
441
+ end
442
+ return html_string
443
+ end
444
+
445
+ def scatterbubble2D(user_options = {}, &block)
446
+ default_options = {
447
+ row_names: true,
448
+ transpose: false
449
+ }.merge!(user_options)
450
+ html_string = canvasXpress_main(default_options, block) do |options, config, samples, vars, values, object_id|
451
+ config['graphType'] = 'ScatterBubble2D'
452
+ if options[:xAxis].nil?
453
+ config['xAxis'] = [samples[0]]
454
+ else
455
+ config['xAxis'] = options[:xAxis]
456
+ end
457
+ if options[:yAxis].nil?
458
+ config['yAxis'] = [samples[1]]
459
+ else
460
+ config['yAxis'] = options[:yAxis]
461
+ end
462
+ if options[:zAxis].nil?
463
+ config['zAxis'] = [samples[2]]
464
+ else
465
+ config['zAxis'] = options[:zAxis]
466
+ end
467
+ if default_options[:y_label].nil?
468
+ config['yAxisTitle'] = 'y_axis'
469
+ else
470
+ config['yAxisTitle'] = default_options[:y_label]
471
+ end
472
+ if default_options[:z_label].nil?
473
+ config['zAxisTitle'] = 'z_axis'
474
+ else
475
+ config['zAxisTitle'] = default_options[:z_label]
476
+ end
477
+ if !options[:upper_limit].nil? && !options[:lower_limit].nil? && !options[:ranges].nil?
478
+ diff = (options[:upper_limit] - options[:lower_limit]).to_f/options[:ranges]
479
+ sizes = Array.new(options[:ranges]) {|index| options[:lower_limit] + index * diff}
480
+ config['sizes'] = sizes
481
+ end
482
+ end
483
+ return html_string
484
+ end
485
+
486
+ def circular(user_options = {}, &block)
487
+ default_options = {
488
+ ring_assignation: [],
489
+ ringsType: [],
490
+ ringsWeight: []
491
+ }.merge!(user_options)
492
+ html_string = canvasXpress_main(default_options, block) do |options, config, samples, vars, values, object_id|
493
+ options[:mod_data_structure] = 'circular'
494
+ config['graphType'] = 'Circular'
495
+ config['segregateVariablesBy'] = ['Ring']
496
+ if default_options[:ringsType].empty?
497
+ config['ringsType'] = Array.new(vars.length, 'heatmap')
498
+ else
499
+ config['ringsType'] = default_options[:ringsType]
500
+ end
501
+ if default_options[:ringsWeight].empty?
502
+ size = 100/vars.length
503
+ config['ringsWeight'] = Array.new(vars.length, size)
504
+ else
505
+ config['ringsWeight'] = default_options[:ringsWeight]
506
+ end
507
+ if default_options[:ring_assignation].empty?
508
+ options[:ring_assignation] = Array.new(vars.length) {|index| (index + 1).to_s}
509
+ else
510
+ options[:ring_assignation] = default_options[:ring_assignation].map{|item| item.to_s}
511
+ end
512
+ if !default_options[:links].nil?
513
+ if !@hash_vars[default_options[:links]].nil? && !@hash_vars[default_options[:links]].empty?
514
+ link_data = get_data({id: default_options[:links], fields: [], add_header_row_names: false, text: true, transpose: false})
515
+ config['connections'] = assign_rgb(link_data)
516
+ end
517
+ end
518
+ end
519
+ return html_string
520
+ end
521
+
522
+ def assign_rgb(link_data)
523
+ colors = {
524
+ 'red' => [255, 0, 0],
525
+ 'green' => [0, 255, 0],
526
+ 'black' => [0, 0, 0],
527
+ 'yellow' => [255, 255, 0],
528
+ 'blue' => [0, 0, 255],
529
+ 'gray' => [128, 128, 128],
530
+ 'orange' => [255, 165, 0],
531
+ 'cyan' => [0, 255, 255],
532
+ 'magenta' => [255, 0, 255]
533
+ }
534
+ link_data.each do |link|
535
+ code = colors[link[0]]
536
+ if !code.nil?
537
+ link[0] = "rgb(#{code.join(',')})"
538
+ else
539
+ raise "Color link #{link} is not allowed. The allowed color names are: #{colors.keys.join(' ')}"
540
+ end
541
+ end
542
+ end
543
+
544
+ # EMBED FILES
545
+ ###################################################################################
546
+
547
+ def embed_img(img_file, img_attribs = nil)
548
+ img_content = File.open(img_file).read
549
+ img_base64 = Base64.encode64(img_content)
550
+ format = File.basename(img_file).split('.').last
551
+ img_string = "<img #{img_attribs} src=\"data:image/#{format};base64,#{img_base64}\">"
552
+ return img_string
553
+ end
554
+
555
+ def embed_pdf(pdf_file, pdf_attribs = nil)
556
+ pdf_content = File.open(pdf_file).read
557
+ pdf_base64 = Base64.encode64(pdf_content)
558
+ pdf_string = "<embed #{pdf_attribs} src=\"data:application/pdf;base64,#{pdf_base64}\" type=\"application/pdf\"></embed>"
559
+ return pdf_string
560
+ end
561
+ end