wayneeseguin-dynamic_reports 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.
- data/HISTORY +7 -0
- data/README +189 -0
- data/README.rdoc +189 -0
- data/dynamic_reports.gemspec +62 -0
- data/lib/dynamic_reports/charts.rb +217 -0
- data/lib/dynamic_reports/reports.rb +268 -0
- data/lib/dynamic_reports/templates.rb +178 -0
- data/lib/dynamic_reports/vendor/google_chart/bar_chart.rb +90 -0
- data/lib/dynamic_reports/vendor/google_chart/base.rb +539 -0
- data/lib/dynamic_reports/vendor/google_chart/financial_line_chart.rb +31 -0
- data/lib/dynamic_reports/vendor/google_chart/line_chart.rb +79 -0
- data/lib/dynamic_reports/vendor/google_chart/pie_chart.rb +33 -0
- data/lib/dynamic_reports/vendor/google_chart/scatter_chart.rb +38 -0
- data/lib/dynamic_reports/vendor/google_chart/venn_diagram.rb +36 -0
- data/lib/dynamic_reports/vendor/google_chart.rb +11 -0
- data/lib/dynamic_reports/views/default_layout.html.erb +1 -0
- data/lib/dynamic_reports/views/default_report.html.erb +73 -0
- data/lib/dynamic_reports/views/default_report.html.haml +62 -0
- data/lib/dynamic_reports/views.rb +30 -0
- data/lib/dynamic_reports.rb +47 -0
- data/test/dynamic_reports/charts_test.rb +61 -0
- data/test/dynamic_reports/reports_test.rb +77 -0
- data/test/dynamic_reports/templates_test.rb +3 -0
- data/test/dynamic_reports/views_test.rb +5 -0
- data/test/dynamic_reports.rb +18 -0
- data/test/factories/records.rb +0 -0
- data/test/test_helper.rb +64 -0
- metadata +81 -0
@@ -0,0 +1,268 @@
|
|
1
|
+
# DynamicReports
|
2
|
+
#
|
3
|
+
# Dynamic Reporting Engine for Ruby / Rails
|
4
|
+
module DynamicReports
|
5
|
+
|
6
|
+
# DynamicReports::Report
|
7
|
+
#
|
8
|
+
class Report
|
9
|
+
@@default_engine = "erb"
|
10
|
+
|
11
|
+
attr_accessor :name, :title, :sub_title, :columns, :charts, :records, :template, :class_name, :styles
|
12
|
+
|
13
|
+
# views accessor, array of view paths.
|
14
|
+
def views
|
15
|
+
@views
|
16
|
+
end
|
17
|
+
|
18
|
+
# options accessor, all report options and configuration is stored in this.
|
19
|
+
def options
|
20
|
+
@options
|
21
|
+
end
|
22
|
+
|
23
|
+
class << self
|
24
|
+
# Views setter and accessor.
|
25
|
+
def views(*array)
|
26
|
+
@views ||= ["#{File::dirname(File::expand_path(__FILE__))}/views/"]
|
27
|
+
unless array.empty?
|
28
|
+
@views.unshift(array)
|
29
|
+
@views.flatten!
|
30
|
+
@views.uniq!
|
31
|
+
else
|
32
|
+
@views
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# class level options accessor
|
37
|
+
def options
|
38
|
+
@options ||= {}
|
39
|
+
end
|
40
|
+
|
41
|
+
# class level name accessor & setter
|
42
|
+
#
|
43
|
+
# Set the name of the report, for example:
|
44
|
+
#
|
45
|
+
# name "Orders Report"
|
46
|
+
#
|
47
|
+
def name(value = nil)
|
48
|
+
if value
|
49
|
+
options[:name] = value
|
50
|
+
else
|
51
|
+
# TODO: snake_case_the_name
|
52
|
+
options[:name] || self.class.name
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Accessor used to set the title:
|
57
|
+
#
|
58
|
+
# title "All orders for the account"
|
59
|
+
#
|
60
|
+
# Or to access the already set title:
|
61
|
+
#
|
62
|
+
# OrdersReport.title
|
63
|
+
#
|
64
|
+
# #=> "All orders for the account"
|
65
|
+
#
|
66
|
+
def title(value = nil)
|
67
|
+
value ? options[:title] = value : options[:title]
|
68
|
+
end
|
69
|
+
|
70
|
+
# Accessor used to set the sub title:
|
71
|
+
#
|
72
|
+
# sub_title "All orders for the account"
|
73
|
+
#
|
74
|
+
# Or to access the already set sub title:
|
75
|
+
#
|
76
|
+
# OrdersReport.sub_title
|
77
|
+
#
|
78
|
+
# #=> "All orders for the account"
|
79
|
+
#
|
80
|
+
def sub_title(value = nil)
|
81
|
+
value ? options[:sub_title] = value : options[:sub_title]
|
82
|
+
end
|
83
|
+
|
84
|
+
def styles
|
85
|
+
options[:styles] ||= false
|
86
|
+
end
|
87
|
+
|
88
|
+
def class_name(value = nil)
|
89
|
+
if value
|
90
|
+
options[:class_name] = value
|
91
|
+
elsif options[:class_name].empty?
|
92
|
+
options[:class_name] = self.to_s
|
93
|
+
else
|
94
|
+
options[:class_name]
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# Accessor for the template to use for the report.
|
99
|
+
#
|
100
|
+
# Example:
|
101
|
+
#
|
102
|
+
# template :orders # => renders orders.html.erb (erb is the default engine)
|
103
|
+
#
|
104
|
+
# Used without argument returns the template set for the report.
|
105
|
+
#
|
106
|
+
# OrdersReport.template # => :orders
|
107
|
+
#
|
108
|
+
def template(value = nil)
|
109
|
+
if value
|
110
|
+
@template = value
|
111
|
+
options[:template] = @template
|
112
|
+
end
|
113
|
+
@template ||= nil
|
114
|
+
end
|
115
|
+
|
116
|
+
# Accessor for columns
|
117
|
+
#
|
118
|
+
# Pass an array of symbols to define columns on the report
|
119
|
+
#
|
120
|
+
# Example:
|
121
|
+
#
|
122
|
+
# columns :total, :created_at
|
123
|
+
#
|
124
|
+
# Calling the class method with no arguments will return an array with the defined columns.
|
125
|
+
#
|
126
|
+
# Example:
|
127
|
+
#
|
128
|
+
# OrdersReport.columns
|
129
|
+
#
|
130
|
+
# # => [:total, :created_at]
|
131
|
+
#
|
132
|
+
def columns(*array)
|
133
|
+
unless array.empty?
|
134
|
+
if (array.class == Array)
|
135
|
+
options[:columns] = array
|
136
|
+
else
|
137
|
+
raise "Report columns must be specified."
|
138
|
+
end
|
139
|
+
else
|
140
|
+
options[:columns]
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
# Return the chart with the specified name, if it exists. nil otherwise.
|
145
|
+
def chart_with_name(name)
|
146
|
+
options[:charts].to_a.detect{ |c| c.name == name.to_sym }
|
147
|
+
end
|
148
|
+
|
149
|
+
# Return an array of charts defined for the report.
|
150
|
+
def charts(object=nil)
|
151
|
+
options[:charts] ||= []
|
152
|
+
options[:charts] << object if object
|
153
|
+
options[:charts]
|
154
|
+
end
|
155
|
+
|
156
|
+
# Define a chart for the report
|
157
|
+
#
|
158
|
+
# Example:
|
159
|
+
# chart :PV_Visits, {:grxl => 'xxxxx'} do
|
160
|
+
# title "Pageviews and Visits"
|
161
|
+
# columns [:pageviews, :visits]
|
162
|
+
# no_labels false
|
163
|
+
# label_column "recorded_at"
|
164
|
+
# width "400"
|
165
|
+
# height "350"
|
166
|
+
# type "line"
|
167
|
+
# end
|
168
|
+
#
|
169
|
+
def chart(name, *chart_options, &block)
|
170
|
+
chart_options = chart_options.shift || {}
|
171
|
+
charts(Chart.configure(name, chart_options, &block))
|
172
|
+
end
|
173
|
+
|
174
|
+
# Method for instanciating a report instance on a set of given records.
|
175
|
+
#
|
176
|
+
# Example:
|
177
|
+
#
|
178
|
+
# OrdersReport.on(@records)
|
179
|
+
#
|
180
|
+
# Where @records is an array of
|
181
|
+
#
|
182
|
+
# * Objects that respond to methods for all columns defined on the report
|
183
|
+
# * Hashes that have keys corresponding to all columns defined on the report
|
184
|
+
#
|
185
|
+
# This will return an instance of the OrdersReport bound to @records
|
186
|
+
#
|
187
|
+
def on(records)
|
188
|
+
new(records, @options)
|
189
|
+
end
|
190
|
+
|
191
|
+
#--
|
192
|
+
# Methods for definining a sub report
|
193
|
+
#def link_column
|
194
|
+
#end
|
195
|
+
#def link_rows
|
196
|
+
#end
|
197
|
+
|
198
|
+
end
|
199
|
+
|
200
|
+
# Instantiate the report on a set of records.
|
201
|
+
#
|
202
|
+
# Example:
|
203
|
+
#
|
204
|
+
# OrdersReport.new(@records)
|
205
|
+
#
|
206
|
+
# options is a set of optional overrides for
|
207
|
+
#
|
208
|
+
# * views - Used to override the class defined views.
|
209
|
+
# * template - Used to override the class defined template.
|
210
|
+
#
|
211
|
+
def initialize(records, *new_options)
|
212
|
+
new_options = new_options.shift || {}
|
213
|
+
@records = records
|
214
|
+
|
215
|
+
@views = self.class.views
|
216
|
+
@views.unshift(new_options.delete(:views)) if new_options[:views]
|
217
|
+
@views.flatten!
|
218
|
+
@views.uniq!
|
219
|
+
|
220
|
+
@template = self.class.template
|
221
|
+
@template = new_options.delete(:template) if new_options[:template]
|
222
|
+
|
223
|
+
@options = self.class.options.merge!(new_options)
|
224
|
+
@options.each_pair do |key,value|
|
225
|
+
if key == "chart"
|
226
|
+
# TODO: erh?
|
227
|
+
self.chart(value[:name],{},value)
|
228
|
+
else
|
229
|
+
instance_variable_set("@#{key}".to_sym, value)
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
# Convert an instance of a report bound to a records set to an html view
|
235
|
+
#
|
236
|
+
# Example
|
237
|
+
#
|
238
|
+
# OrdersReport.on(@records).to_html
|
239
|
+
#
|
240
|
+
# [options]
|
241
|
+
# :engine - one of :erb, :haml, :csv, :pdf
|
242
|
+
#
|
243
|
+
# Note: CSV & PDF forthcoming.
|
244
|
+
#
|
245
|
+
def to_html(*options)
|
246
|
+
view = View.new(self)
|
247
|
+
# todo: this is not clean...
|
248
|
+
options = (options.shift || {}).merge!(@options || {})
|
249
|
+
# todo: if rails is loaded set the default engine: dynamicreports::report.default_engine
|
250
|
+
engine = options.delete(:engine) || @@default_engine
|
251
|
+
options[:template] = self.class.template
|
252
|
+
view.__send__("#{engine}", options)
|
253
|
+
end
|
254
|
+
|
255
|
+
# Not Implemented Yet
|
256
|
+
def to_csv
|
257
|
+
# TODO: Write csv hook
|
258
|
+
end
|
259
|
+
|
260
|
+
# Not Implemented Yet
|
261
|
+
def to_pdf
|
262
|
+
# TODO: Write pdf hook
|
263
|
+
end
|
264
|
+
|
265
|
+
end
|
266
|
+
|
267
|
+
end
|
268
|
+
|
@@ -0,0 +1,178 @@
|
|
1
|
+
module DynamicReports
|
2
|
+
# Options are:
|
3
|
+
# :layout If set to false, no layout is rendered, otherwise
|
4
|
+
# the specified layout is used
|
5
|
+
# :locals A hash with local variables that should be available
|
6
|
+
# in the template
|
7
|
+
module Templates
|
8
|
+
def csv(options={}, locals={})
|
9
|
+
template = options.delete(:template)
|
10
|
+
render :csv, template, options.merge!(:content_type => "csv"), locals
|
11
|
+
end
|
12
|
+
|
13
|
+
def erb(options={}, locals={})
|
14
|
+
require "erb"
|
15
|
+
template = options.delete(:template)
|
16
|
+
render :erb, template, options, locals
|
17
|
+
end
|
18
|
+
|
19
|
+
def haml(options={}, locals={})
|
20
|
+
require_warn("Haml") unless defined?(::Haml::Engine)
|
21
|
+
template = options.delete(:template)
|
22
|
+
render :haml, template, options, locals
|
23
|
+
end
|
24
|
+
|
25
|
+
def builder(options={}, locals={}, &block)
|
26
|
+
require_warn("Builder") unless defined?(::Builder)
|
27
|
+
template = options.delete(:template)
|
28
|
+
options, template = template, nil if template.is_a?(Hash)
|
29
|
+
template = lambda { block } if template.nil?
|
30
|
+
render :builder, template, options, locals
|
31
|
+
end
|
32
|
+
|
33
|
+
# TODO: Add Report Helpers for injection
|
34
|
+
def titleize(object)
|
35
|
+
object.to_s.split('_').each{ |word| word.capitalize! }.join(' ')
|
36
|
+
end
|
37
|
+
|
38
|
+
def commify(object)
|
39
|
+
if object.is_a?(Numeric)
|
40
|
+
object.to_s.gsub(/(\d)(?=(\d{3})+$)/,'\1,')
|
41
|
+
else
|
42
|
+
object
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def chart_url(chart,report)
|
47
|
+
columns = chart.columns ? chart.columns : report.columns
|
48
|
+
chart_type = chart.type.nil? ? :line : chart.type.to_sym
|
49
|
+
case chart_type
|
50
|
+
when :line
|
51
|
+
Charts.line_chart(chart,columns,report)
|
52
|
+
when :pie
|
53
|
+
Charts.pie_chart(chart,columns,report)
|
54
|
+
when :bar
|
55
|
+
Charts.bar_column_chart(chart,columns,report,:vertical)
|
56
|
+
when :column
|
57
|
+
Charts.bar_column_chart(chart,columns,report,:horizontal)
|
58
|
+
else
|
59
|
+
raise StandardError => "Unknown chart type '#{chart.type}'."
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def render(engine, template, options={}, locals={})
|
67
|
+
# merge app-level options
|
68
|
+
options = self.class.send(engine).merge(options) if self.class.respond_to?(engine)
|
69
|
+
|
70
|
+
# extract generic options
|
71
|
+
layout = options.delete(:layout)
|
72
|
+
layout = :default_layout if (layout.nil? || layout == true)
|
73
|
+
views = options.delete(:views) || self.views
|
74
|
+
content_type = options.delete(:content_type)
|
75
|
+
locals = options.delete(:locals) || locals || {}
|
76
|
+
locals.merge!(:report => @report, :options => options || {})
|
77
|
+
|
78
|
+
# render template
|
79
|
+
data, options[:filename], options[:line] = lookup_template(engine, template, views, content_type)
|
80
|
+
output = __send__("render_#{engine}", template, data, options, locals)
|
81
|
+
# render layout
|
82
|
+
# TODO: Fix Layout Rendering & Specify Layout
|
83
|
+
if layout
|
84
|
+
data, options[:filename], options[:line] = lookup_layout(engine, layout, views, content_type)
|
85
|
+
if data
|
86
|
+
output = __send__("render_#{engine}", layout, data, options, {}) { output }
|
87
|
+
end
|
88
|
+
end
|
89
|
+
output
|
90
|
+
end
|
91
|
+
|
92
|
+
def lookup_template(engine, template, views, content_type = nil, filename = nil, line = nil)
|
93
|
+
content_type = "html" if content_type.nil? or content_type.blank?
|
94
|
+
if (template.nil? || template == '')
|
95
|
+
template = :default_template
|
96
|
+
#views = DefaultReport.default_view_paths
|
97
|
+
end
|
98
|
+
case template
|
99
|
+
when Symbol
|
100
|
+
if cached = self.class.cached_templates[template]
|
101
|
+
lookup_template(engine, cached[:template], views, content_type, cached[:filename], cached[:line])
|
102
|
+
else
|
103
|
+
filename = "#{template}.#{content_type}.#{engine}"
|
104
|
+
dir = views.to_a.detect do |view|
|
105
|
+
::File.exists?(::File.join(view, filename))
|
106
|
+
end
|
107
|
+
if dir
|
108
|
+
path = ::File.join(dir, filename)
|
109
|
+
else
|
110
|
+
path = ::File.join(::File::dirname(::File::expand_path(__FILE__)), "views","default_report.#{content_type}.#{engine}")
|
111
|
+
end
|
112
|
+
[ ::File.read(path), path, 1 ]
|
113
|
+
end
|
114
|
+
when Proc
|
115
|
+
filename, line = self.class.caller_locations.first if filename.nil?
|
116
|
+
[ template.call, filename, line.to_i ]
|
117
|
+
when String
|
118
|
+
filename, line = self.class.caller_locations.first if filename.nil?
|
119
|
+
[ template, filename, line.to_i ]
|
120
|
+
else
|
121
|
+
raise ArgumentError, "Template was not specified properly: '#{template}'."
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def lookup_layout(engine, template, views, content_type)
|
126
|
+
lookup_template(engine, template, views, content_type)
|
127
|
+
rescue Errno::ENOENT
|
128
|
+
nil
|
129
|
+
end
|
130
|
+
|
131
|
+
def render_csv(template, data, options, locals, &block)
|
132
|
+
# TODO: Implement this.
|
133
|
+
end
|
134
|
+
|
135
|
+
def render_erb(template, data, options, locals, &block)
|
136
|
+
original_out_buf = defined?(@_out_buf) && @_out_buf
|
137
|
+
data = data.call if data.kind_of? Proc
|
138
|
+
|
139
|
+
instance = ::ERB.new(data, nil, nil, "@_out_buf")
|
140
|
+
locals_assigns = locals.to_a.collect { |k,v| "#{k} = locals[:#{k}]" }
|
141
|
+
|
142
|
+
filename = options.delete(:filename) || "(__ERB__)"
|
143
|
+
line = options.delete(:line) || 1
|
144
|
+
line -= 1 if instance.src =~ /^#coding:/
|
145
|
+
|
146
|
+
render_binding = binding
|
147
|
+
eval locals_assigns.join("\n"), render_binding
|
148
|
+
eval instance.src, render_binding, filename, line
|
149
|
+
@_out_buf, result = original_out_buf, @_out_buf
|
150
|
+
result
|
151
|
+
end
|
152
|
+
|
153
|
+
def render_haml(template, data, options, locals, &block)
|
154
|
+
::Haml::Engine.new(data, options).render(self, locals, &block)
|
155
|
+
end
|
156
|
+
|
157
|
+
def render_builder(template, data, options, locals, &block)
|
158
|
+
options = { :indent => 2 }.merge(options)
|
159
|
+
filename = options.delete(:filename) || "<BUILDER>"
|
160
|
+
line = options.delete(:line) || 1
|
161
|
+
xml = ::Builder::XmlMarkup.new(options)
|
162
|
+
if data.respond_to?(:to_str)
|
163
|
+
eval data.to_str, binding, filename, line
|
164
|
+
elsif data.kind_of?(Proc)
|
165
|
+
data.call(xml)
|
166
|
+
end
|
167
|
+
xml.target!
|
168
|
+
end
|
169
|
+
|
170
|
+
def require_warn(engine)
|
171
|
+
warn "Auto-require of #{engine} is deprecated; add require \"#{engine}\" to your app."
|
172
|
+
require engine.downcase
|
173
|
+
end
|
174
|
+
|
175
|
+
|
176
|
+
|
177
|
+
end
|
178
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/base'
|
2
|
+
module GoogleChart
|
3
|
+
# Generates a Bar Chart. You can specify the alignment(horizontal or vertical) and whether you want the bars to be grouped or stacked
|
4
|
+
# ==== Examples
|
5
|
+
# bc = GoogleChart::BarChart.new('800x200', "Bar Chart", :vertical, false)
|
6
|
+
# bc.data "Trend 1", [5,4,3,1,3,5], '0000ff'
|
7
|
+
class BarChart < Base
|
8
|
+
|
9
|
+
attr_accessor :alignment, :stacked
|
10
|
+
|
11
|
+
# Specify the
|
12
|
+
# * +chart_size+ in WIDTHxHEIGHT format
|
13
|
+
# * +chart_title+ as a string
|
14
|
+
# * +alignment+ as either <tt>:vertical</tt> or <tt>:horizontal</tt>
|
15
|
+
# * +stacked+ should be +true+ if you want the bars to be stacked, false otherwise
|
16
|
+
def initialize(chart_size='300x200', chart_title=nil, alignment=:vertical, stacked=false) # :yield: self
|
17
|
+
super(chart_size, chart_title)
|
18
|
+
@alignment = alignment
|
19
|
+
@stacked = stacked
|
20
|
+
set_chart_type
|
21
|
+
self.show_legend = true
|
22
|
+
yield self if block_given?
|
23
|
+
end
|
24
|
+
|
25
|
+
# Set the alignment to either <tt>:vertical</tt> or <tt>:horizontal</tt>
|
26
|
+
def alignment=(value)
|
27
|
+
@alignment = value
|
28
|
+
set_chart_type
|
29
|
+
end
|
30
|
+
|
31
|
+
# If you want the bar chart to be stacked, set the value to <tt>true</tt>, otherwise set the value to <tt>false</tt> to group it.
|
32
|
+
def stacked=(value)
|
33
|
+
@stacked = value
|
34
|
+
set_chart_type
|
35
|
+
end
|
36
|
+
|
37
|
+
# Defines options for bar width, spacing between bars and between groups of bars. Applicable for bar charts.
|
38
|
+
# [+options+] : Options for the style, specifying things like line thickness and lengths of the line segment and blank portions
|
39
|
+
#
|
40
|
+
# ==== Options
|
41
|
+
# * <tt>:bar_width</tt>, Bar width in pixels
|
42
|
+
# * <tt>:bar_spacing</tt> (optional), space between bars in a group
|
43
|
+
# * <tt>:group_spacing</tt> (optional), space between groups
|
44
|
+
def width_spacing_options(options={})
|
45
|
+
options_str = "#{options[:bar_width]}"
|
46
|
+
options_str += ",#{options[:bar_spacing]}" if options[:bar_spacing]
|
47
|
+
options_str += ",#{options[:group_spacing]}" if options[:bar_spacing] and options[:group_spacing]
|
48
|
+
@bar_width_spacing_options = options_str
|
49
|
+
end
|
50
|
+
|
51
|
+
def process_data
|
52
|
+
if @stacked # Special handling of max value for stacked
|
53
|
+
unless @max_data # Unless max_data is explicitly set
|
54
|
+
@max_data = @data.inject([]) do |sum_arr, series|
|
55
|
+
series.each_with_index do |v,i|
|
56
|
+
if sum_arr[i] == nil
|
57
|
+
sum_arr[i] = v
|
58
|
+
else
|
59
|
+
sum_arr[i] += v
|
60
|
+
end
|
61
|
+
end
|
62
|
+
sum_arr
|
63
|
+
end.max
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
if @data.size > 1
|
68
|
+
join_encoded_data(@data.collect { |series|
|
69
|
+
encode_data(series, max_data_value)
|
70
|
+
})
|
71
|
+
else
|
72
|
+
encode_data(@data.flatten,max_data_value)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
def set_chart_type
|
78
|
+
# Set chart type
|
79
|
+
if alignment == :vertical and stacked == false
|
80
|
+
self.chart_type = :bvg
|
81
|
+
elsif alignment == :vertical and stacked == true
|
82
|
+
self.chart_type = :bvs
|
83
|
+
elsif alignment == :horizontal and stacked == false
|
84
|
+
self.chart_type = :bhg
|
85
|
+
elsif alignment == :horizontal and stacked == true
|
86
|
+
self.chart_type = :bhs
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|