dynamic_reports 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,11 @@
1
+ %w(
2
+ base
3
+ pie_chart
4
+ line_chart
5
+ bar_chart
6
+ venn_diagram
7
+ scatter_chart
8
+ financial_line_chart
9
+ ).each do |filename|
10
+ require File.dirname(__FILE__) + "/google_chart/#{filename}"
11
+ 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
@@ -0,0 +1,539 @@
1
+ require 'uri'
2
+
3
+ module GoogleChart
4
+ class Base
5
+ BASE_URL = "http://chart.apis.google.com/chart?"
6
+
7
+ SIMPLE_ENCODING = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'.split('');
8
+ COMPLEX_ENCODING_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-.'.split('');
9
+ @@complex_encoding = []
10
+ COMPLEX_ENCODING_ALPHABET.each_with_index do |outer,index_outer|
11
+ COMPLEX_ENCODING_ALPHABET.each_with_index do |inner, index_inner|
12
+ @@complex_encoding[index_outer * 64 + index_inner] = outer + inner
13
+ end
14
+ end
15
+
16
+ SHAPE_MARKERS = {:arrow => "a",
17
+ :cross => "c",
18
+ :diamond => "d",
19
+ :circle => "o",
20
+ :square => "s",
21
+ :vline_segment => "v",
22
+ :vline_full => "V",
23
+ :hline_full => "h",
24
+ :x => "x"
25
+ }
26
+
27
+ DEFAULT_LINE_STYLE = '1'
28
+
29
+
30
+ # Size of the chart in WIDTHxHEIGHT format
31
+ attr_accessor :chart_size
32
+
33
+ # Type of the chart. Usually, you do not need to set this yourself
34
+ attr_accessor :chart_type
35
+
36
+ # Chart title
37
+ attr_accessor :chart_title
38
+
39
+ # RRGGBB hex value for the color of the title
40
+ attr_accessor :title_color
41
+
42
+ # Font size of the title
43
+ attr_accessor :title_font_size
44
+
45
+ # Data encoding to use. Can be one of <tt>:simple</tt>, <tt>:text</tt> or <tt>:extended</tt> (see http://code.google.com/apis/chart/#chart_data)
46
+ attr_accessor :data_encoding
47
+
48
+ # A hash of the params used to construct the URL
49
+ attr_accessor :params
50
+
51
+ # Set to <tt>true</tt> or <tt>false</tt> to show or hide the chart legend. Not applicable for Scatter Chart.
52
+ attr_accessor :show_legend
53
+
54
+ def initialize(chart_size, chart_title)
55
+ # Declare instance variables to be a good citizen.
56
+ @background_fill = nil, @chart_fill = nil, @grid_str = nil, @max_data = nil, @bar_width_spacing_options = nil, @background_fill = nil, @chart_fill = nil, @grid_str = nil, @max_data = nil, @bar_width_spacing_options = nil, @background_fill = nil, @chart_fill = nil, @grid_str = nil, @max_data = nil, @bar_width_spacing_options = nil, @background_fill = nil, @chart_fill = nil, @grid_str = nil, @max_data = nil, @bar_width_spacing_options = nil
57
+ self.params = Hash.new
58
+ @labels = []
59
+ @data = []
60
+ @colors = []
61
+ @axis = []
62
+ @markers = []
63
+ @line_styles = []
64
+ self.chart_size = chart_size
65
+ self.chart_title = chart_title
66
+ self.data_encoding = :simple
67
+ self.show_legend = true
68
+ end
69
+
70
+ # Generates the URL string that can be used to retrieve the graph image in PNG format.
71
+ # Use this after assigning all the properties to the graph
72
+ # You can pass in additional params as a hash for features that may not have been implemented
73
+ # For e.g
74
+ # lc = GoogleChart::LineChart.new('320x200', "Line Chart", false)
75
+ # lc.data "Trend 1", [5,4,3,1,3,5,6], '0000ff'
76
+ # lc.data "Trend 2", [1,2,3,4,5,6], '00ff00'
77
+ # lc.data "Trend 3", [6,5,4,3,2,1], 'ff0000'
78
+ # puts lc.to_url({:chm => "000000,0,0.1,0.11"}) # Single black line as a horizontal marker
79
+ def to_url(extras={})
80
+ prepare_params
81
+ params.merge!(extras)
82
+ query_string = params.map { |k,v| "#{k}=#{URI.escape(v.to_s).gsub(/%20/,'+').gsub(/%7C/,'|')}" }.join('&')
83
+ BASE_URL + query_string
84
+ end
85
+
86
+ # Generates a fully encoded URL string that can be used to retrieve the graph image in PNG format.
87
+ # For less verbose URLs, use the <tt>to_url</tt> method. Use this only if you are doing further
88
+ # processing with the URLs, like passing the URL to a method for downloading the images
89
+ #
90
+ # Use this after assigning all the properties to the graph
91
+ # You can pass in additional params as a hash for features that may not have been implemented
92
+ # For e.g
93
+ # lc = GoogleChart::LineChart.new('320x200', "Line Chart", false)
94
+ # lc.data "Trend 1", [5,4,3,1,3,5,6], '0000ff'
95
+ # lc.data "Trend 2", [1,2,3,4,5,6], '00ff00'
96
+ # lc.data "Trend 3", [6,5,4,3,2,1], 'ff0000'
97
+ # puts lc.to_escaped_url({:chm => "000000,0,0.1,0.11"}) # Single black line as a horizontal marker
98
+ def to_escaped_url(extras={})
99
+ prepare_params
100
+ params.merge!(extras)
101
+ query_string = params.map { |k,v| "#{k}=#{URI.escape(v.to_s)}" }.join('&')
102
+ BASE_URL + query_string
103
+ end
104
+
105
+ # Adds the data to the chart, according to the type of the graph being generated.
106
+ #
107
+ # [+name+] is a string containing a label for the data.
108
+ # [+value+] is either a number or an array of numbers containing the data. Pie Charts and Venn Diagrams take a single number, but other graphs require an array of numbers
109
+ # [+color+ (optional)] is a hexadecimal RGB value for the color to represent the data
110
+ #
111
+ # ==== Examples
112
+ #
113
+ # for GoogleChart::LineChart (normal)
114
+ # lc.data "Trend 1", [1,2,3,4,5], 'ff00ff'
115
+ #
116
+ # for GoogleChart::LineChart (XY chart)
117
+ # lc.data "Trend 2", [[4,5], [2,2], [1,1], [3,4]], 'ff00ff'
118
+ #
119
+ # for GoogleChart::PieChart
120
+ # lc.data "Apples", 5, 'ff00ff'
121
+ # lc.data "Oranges", 7, '00ffff'
122
+ def data(name, value, color=nil)
123
+ @data << value
124
+ @labels << name
125
+ @colors << color if color
126
+ end
127
+
128
+ # Allows (optional) setting of a max value for the chart, which will be used for data encoding and axis plotting.
129
+ # The value to pass depends on the type of chart
130
+ # * For Line Chart and Bar Charts it should be a single integer or float value
131
+ # * For Scatter Charts and Line XY Charts, you MUST pass an array containing the maximum values for X and Y
132
+ #
133
+ # ==== Examples
134
+ # For bar charts
135
+ # bc.max_value 5 # 5 will be used to calculate the relative encoding values
136
+ # For scatter chart
137
+ # sc.max_value [5,6] # 5 is the max x value and 6 is the max y value
138
+ #
139
+ # Note : MAKE SURE you are passing the right values otherwise an exception will be raised
140
+ def max_value(value)
141
+ if [:lxy, :s].member?(self.chart_type) and value.is_a?(Array)
142
+ @max_x = value.first
143
+ @max_y = value.last
144
+ elsif [:lc,:bhg,:bhs,:bvg,:bvs] and (value.is_a?(Integer) or value.is_a?(Float))
145
+ @max_data = value
146
+ else
147
+ raise "Invalid max value for this chart type"
148
+ end
149
+ end
150
+
151
+ # Adds a background or chart fill. Call this option twice if you want both a background and a chart fill
152
+ # [+bg_or_c+] Can be one of <tt>:background</tt> or <tt>:chart</tt> depending on the kind of fill requested
153
+ # [+type+] Can be one of <tt>:solid</tt>, <tt>:gradient</tt> or <tt>:stripes</tt>
154
+ # [+options+] : Options depend on the type of fill selected above
155
+ #
156
+ # ==== Options
157
+ # For <tt>:solid</tt> type
158
+ # * A <tt>:color</tt> option which specifies the RGB hex value of the color to be used as a fill. For e.g <tt>lc.fill(:chart, :solid, {:color => 'ffcccc'})</tt>
159
+ #
160
+ # For <tt>:gradient</tt> type
161
+ # * An <tt>:angle</tt>, which is the angle of the gradient between 0(horizontal) and 90(vertical)
162
+ # * A <tt>:color</tt> option which is a 2D array containing the colors and an offset each, which specifies at what point the color is pure where: 0 specifies the right-most chart position and 1 the left-most. e,g <tt>lc.fill :background, :gradient, :angle => 0, :color => [['76A4FB',1],['ffffff',0]]</tt>
163
+ #
164
+ # For <tt>:stripes</tt> type
165
+ # * An <tt>:angle</tt>, which is the angle of the stripe between 0(horizontal) and 90(vertical)
166
+ # * A <tt>:color</tt> option which is a 2D array containing the colors and width value each, which must be between 0 and 1 where 1 is the full width of the chart. for e.g <tt>lc.fill :chart, :stripes, :angle => 90, :color => [ ['76A4FB',0.2], ['ffffff',0.2] ]</tt>
167
+ def fill(bg_or_c, type, options = {})
168
+ case bg_or_c
169
+ when :background
170
+ @background_fill = "bg," + process_fill_options(type, options)
171
+ when :chart
172
+ @chart_fill = "c," + process_fill_options(type, options)
173
+ end
174
+ end
175
+
176
+ # Adds an axis to the graph. Not applicable for Pie Chart (GoogleChart::PieChart) or Venn Diagram (GoogleChart::VennDiagram)
177
+ #
178
+ # [+type+] is a symbol which can be one of <tt>:x</tt>, <tt>:y</tt>, <tt>:right</tt>, <tt>:top</tt>
179
+ # [+options+] is a hash containing the options (see below)
180
+ #
181
+ # ==== Options
182
+ # Not all the options are mandatory.
183
+ # [<tt>:labels</tt>] An array containing the labels for the axis
184
+ # [<tt>:positions</tt>] An Array containing the positions for the labels
185
+ # [<tt>:range</tt>] An array containing 2 elements, the start value and end value
186
+ #
187
+ # axis styling options have to be specified as follows
188
+ # [<tt>:color</tt>] Hexadecimal RGB value for the color to represent the data for the axis labels
189
+ # [<tt>:font_size</tt>] Font size of the labels in pixels
190
+ # [<tt>:alignment</tt>] can be one of <tt>:left</tt>, <tt>:center</tt> or <tt>:right</tt>
191
+ #
192
+ # ==== Examples
193
+ # lc.axis :y, :range => [0,6], :color => 'ff00ff', :font_size => 16, :alignment => :center
194
+ #
195
+ def axis(type, options = {})
196
+ raise "Illegal axis type" unless [:x, :y, :right, :top].member?(type)
197
+ @axis << [type, options]
198
+ end
199
+
200
+ # Adds a grid to the graph. Applicable only for Line Chart (GoogleChart::LineChart) and Scatter Chart (GoogleChart::ScatterChart)
201
+ #
202
+ # [+options+] is a hash containing the options (see below)
203
+ #
204
+ # === Options
205
+ # [<tt>:xstep</tt>] X axis step size
206
+ # [<tt>:ystep</tt>] Y axis step size
207
+ # [<tt>:length_segment</tt> (optional)] Length of the line segement. Useful with the :length_blank value to have dashed lines
208
+ # [<tt>:length_blank</tt> (optional)] Length of the blank segment. use 0 if you want a solid grid
209
+ #
210
+ # === Examples
211
+ # lc.grid :x_step => 5, :y_step => 5, :length_segment => 1, :length_blank => 0
212
+ #
213
+ def grid(options={})
214
+ @grid_str = "#{options[:x_step].to_f},#{options[:y_step].to_f}"
215
+ if options[:length_segment] or options[:length_blank]
216
+ @grid_str += ",#{options[:length_segment].to_f},#{options[:length_blank].to_f}"
217
+ end
218
+ end
219
+
220
+ # Defines a horizontal or vertical range marker. Applicable for line charts and vertical charts
221
+ #
222
+ # [+alignment+] can be <tt>:horizontal</tt> or <tt>:vertical</tt>
223
+ # [+options+] specifies the color, start point and end point
224
+ #
225
+ # ==== Options
226
+ # [<tt>:color</tt>] RRGGBB hex value for the color of the range marker
227
+ # [<tt>:start_point</tt>] position on the x-axis/y-axis at which the range starts where 0.00 is the left/bottom and 1.00 is the right/top
228
+ # [<tt>:end_point</tt>] position on the x-axis/y-axis at which the range ends where 0.00 is the left/bottom and 1.00 is the right/top
229
+ #
230
+ # ==== Examples
231
+ # lc.range_marker :horizontal, :color => 'E5ECF9', :start_point => 0.1, :end_point => 0.5
232
+ # lc.range_marker :vertical, :color => 'a0bae9', :start_point => 0.1, :end_point => 0.5
233
+ def range_marker(alignment, options={})
234
+ raise "Invalid alignment specified" unless [:horizontal, :vertical].member?(alignment)
235
+ str = (alignment == :horizontal ) ? "r" : "R"
236
+ str += ",#{options[:color]},0,#{options[:start_point]},#{options[:end_point]}"
237
+ @markers << str
238
+ end
239
+
240
+ # Defines a shape marker. Applicable for line charts and scatter plots
241
+ #
242
+ # [+type+] can be <tt>:arrow</tt>, <tt>:cross</tt>, <tt>:diamond</tt>, <tt>:circle</tt>, <tt>:square</tt>, <tt>:vline_segment</tt>, <tt>:vline_full</tt>, <tt>:hline_full</tt>, <tt>:x</tt>
243
+ # [+options+] specifies the color, data set index, data point index and size in pixels
244
+ #
245
+ # ==== Options
246
+ # [<tt>:color</tt>] RRGGBB hex value for the color of the range marker
247
+ # [<tt>:data_set_index</tt>] the index of the line on which to draw the marker. This is 0 for the first data set, 1 for the second and so on.
248
+ # [<tt>:data_point_index</tt>] is a floating point value that specifies on which data point of the data set the marker will be drawn. This is 0 for the first data point, 1 for the second and so on. Specify a fraction to interpolate a marker between two points.
249
+ # [<tt>:size</tt>] is the size of the marker in pixels.
250
+ #
251
+ # ==== Examples
252
+ # lcxy.shape_marker :circle, :color => "000000", :data_set_index => 1, :data_point_index => 2, :pixel_size => 10
253
+ # lcxy.shape_marker :cross, :color => "E5ECF9", :data_set_index => 0, :data_point_index => 0.5, :pixel_size => 10
254
+ def shape_marker(type, options={})
255
+ raise "Invalid shape marker type specified" unless SHAPE_MARKERS.has_key?(type)
256
+ shape_marker_str = "#{SHAPE_MARKERS[type]},#{options[:color]},#{options[:data_set_index]},#{options[:data_point_index]},#{options[:pixel_size]}"
257
+ @markers << shape_marker_str
258
+ end
259
+
260
+ # Defines a Fill area. Applicable for line charts only
261
+ #
262
+ # [+color+] is the color of the fill area
263
+ # [+start_index+] is the index of the line at which the fill starts. This is 0 for the first data set, 1 for the second and so on.
264
+ # [+end_index+] is the index of the line at which the fill ends.
265
+ #
266
+ # ==== Examples
267
+ # # Fill Area (Multiple Datasets)
268
+ # lc = GoogleChart::LineChart.new('320x200', "Line Chart", false) do |lc|
269
+ # lc.show_legend = false
270
+ # lc.data "Trend 1", [5,5,6,5,5], 'ff0000'
271
+ # lc.data "Trend 2", [3,3,4,3,3], '00ff00'
272
+ # lc.data "Trend 3", [1,1,2,1,1], '0000ff'
273
+ # lc.data "Trend 4", [0,0,0,0,0], 'ffffff'
274
+ # lc.fill_area '0000ff',2,3
275
+ # lc.fill_area '00ff00',1,2
276
+ # lc.fill_area 'ff0000',0,1
277
+ # end
278
+ # puts "\nFill Area (Multiple Datasets)"
279
+ # puts lc.to_url
280
+ #
281
+ # # Fill Area (Single Dataset)
282
+ # lc = GoogleChart::LineChart.new('320x200', "Line Chart", false) do |lc|
283
+ # lc.show_legend = false
284
+ # lc.data "Trend 1", [5,5,6,5,5], 'ff0000'
285
+ # lc.fill_area 'cc6633', 0, 0
286
+ # end
287
+ # puts "\nFill Area (Single Dataset)"
288
+ # puts lc.to_url
289
+ #
290
+ def fill_area(color, start_index, end_index)
291
+ if (start_index == 0 and end_index == 0)
292
+ @markers << "B,#{color},0,0,0"
293
+ else
294
+ @markers << "b,#{color},#{start_index},#{end_index},0"
295
+ end
296
+ end
297
+
298
+ protected
299
+
300
+ def prepare_params
301
+ params.clear
302
+ set_size
303
+ set_type
304
+ set_colors
305
+ set_fill_options
306
+ add_axis unless @axis.empty?
307
+ add_grid
308
+ add_data
309
+ add_line_styles unless @line_styles.empty?
310
+ set_bar_width_spacing_options if @bar_width_spacing_options
311
+ add_markers unless @markers.empty?
312
+ add_labels(@labels) if [:p, :p3].member?(self.chart_type)
313
+ add_legend(@labels) if show_legend
314
+ add_title if chart_title.to_s.length > 0
315
+ end
316
+
317
+ def process_fill_options(type, options)
318
+ case type
319
+ when :solid
320
+ "s,#{options[:color]}"
321
+ when :gradient
322
+ "lg,#{options[:angle]}," + options[:color].collect { |o| "#{o.first},#{o.last}" }.join(",")
323
+ when :stripes
324
+ "ls,#{options[:angle]}," + options[:color].collect { |o| "#{o.first},#{o.last}" }.join(",")
325
+ end
326
+
327
+ end
328
+
329
+ def set_type
330
+ params.merge!({:cht => chart_type})
331
+ end
332
+
333
+ def set_size
334
+ params.merge!({:chs => chart_size})
335
+ end
336
+
337
+ def set_colors
338
+ params.merge!({:chco => @colors.collect{|c| c.downcase}.join(",") }) if @colors.size > 0
339
+ end
340
+
341
+ def set_fill_options
342
+ fill_opt = [@background_fill, @chart_fill].compact.join("|")
343
+ params.merge!({:chf => fill_opt}) if fill_opt.length > 0
344
+ end
345
+
346
+ def add_labels(labels)
347
+ params.merge!({:chl => labels.collect{|l| l.to_s}.join("|") }) if self.show_labels
348
+ end
349
+
350
+ def add_legend(labels)
351
+ params.merge!({:chdl => labels.collect{ |l| l.to_s}.join("|")})
352
+ end
353
+
354
+ def add_title
355
+ params.merge!({:chtt => chart_title})
356
+ params.merge!({:chts => title_color}) if title_color
357
+ params.merge!({:chts => "#{title_color},#{title_font_size}"}) if title_color and title_font_size
358
+ end
359
+
360
+ def add_axis
361
+ chxt = []
362
+ chxl = []
363
+ chxp = []
364
+ chxr = []
365
+ chxs = []
366
+ # Process params
367
+ @axis.each_with_index do |axis, idx|
368
+ # Find axis type
369
+ case axis.first
370
+ when :x
371
+ chxt << "x"
372
+ when :y
373
+ chxt << "y"
374
+ when :top
375
+ chxt << "t"
376
+ when :right
377
+ chxt << "r"
378
+ end
379
+
380
+ # Axis labels
381
+ axis_opts = axis.last
382
+
383
+ if axis_opts[:labels]
384
+ chxl[idx] = "#{idx}:|" + axis_opts[:labels].join("|")
385
+ end
386
+
387
+ # Axis positions
388
+ if axis_opts[:positions]
389
+ chxp[idx] = "#{idx}," + axis_opts[:positions].join(",")
390
+ end
391
+
392
+ # Axis range
393
+ if axis_opts[:range]
394
+ chxr[idx] = "#{idx},#{axis_opts[:range].first},#{axis_opts[:range].last}"
395
+ end
396
+
397
+ # Axis Styles
398
+ if axis_opts[:color] or axis_opts[:font_size] or axis_opts[:alignment]
399
+ if axis_opts[:alignment]
400
+ alignment = case axis_opts[:alignment]
401
+ when :center
402
+ 0
403
+ when :left
404
+ -1
405
+ when :right
406
+ 1
407
+ else
408
+ nil
409
+ end
410
+ end
411
+ chxs[idx] = "#{idx}," + [axis_opts[:color], axis_opts[:font_size], alignment].compact.join(",")
412
+ end
413
+ end
414
+
415
+ # Add to params hash
416
+ params.merge!({ :chxt => chxt.join(",") }) unless chxt.empty?
417
+ params.merge!({ :chxl => chxl.compact.join("|") }) unless chxl.compact.empty?
418
+ params.merge!({ :chxp => chxp.compact.join("|") }) unless chxp.compact.empty?
419
+ params.merge!({ :chxr => chxr.compact.join("|") }) unless chxr.compact.empty?
420
+ params.merge!({ :chxs => chxs.compact.join("|") }) unless chxs.compact.empty?
421
+ end
422
+
423
+ def add_grid
424
+ params.merge!({ :chg => @grid_str }) if @grid_str
425
+ end
426
+
427
+ def add_line_styles
428
+ 0.upto(@line_styles.length - 1) { |i|
429
+ @line_styles[i] = DEFAULT_LINE_STYLE unless @line_styles[i]
430
+ }
431
+ params.merge!({:chls => @line_styles.join("|")})
432
+ end
433
+
434
+ def set_bar_width_spacing_options
435
+ params.merge!({:chbh => @bar_width_spacing_options})
436
+ end
437
+
438
+ def add_markers
439
+ params.merge!({:chm => @markers.join("|")})
440
+ end
441
+
442
+ def add_data
443
+ converted_data = process_data
444
+ case data_encoding
445
+ when :simple
446
+ converted_data = "s:" + converted_data
447
+ when :text
448
+ converted_data = "t:" + converted_data
449
+ when :extended
450
+ converted_data = "e:" + converted_data
451
+ else
452
+ raise "Illegal Encoding Specified"
453
+ end
454
+ params.merge!({:chd => converted_data})
455
+ end
456
+
457
+ def encode_data(values, max_value=nil)
458
+ case data_encoding
459
+ when :simple
460
+ simple_encode(values, max_value)
461
+ when :text
462
+ text_encode(values, max_value)
463
+ when :extended
464
+ extended_encode(values, max_value)
465
+ else
466
+ raise "Illegal Encoding Specified"
467
+ end
468
+ end
469
+
470
+ def simple_encode(values, max_value=nil)
471
+ alphabet_length = 61
472
+ max_value = values.max unless max_value
473
+
474
+ chart_data = values.collect do |val|
475
+ if val.to_i >=0
476
+ if max_value == 0
477
+ SIMPLE_ENCODING[0]
478
+ else
479
+ SIMPLE_ENCODING[(alphabet_length * val / max_value).to_i]
480
+ end
481
+ else
482
+ "_"
483
+ end
484
+ end
485
+
486
+ return chart_data.join('')
487
+ end
488
+
489
+ def text_encode(values, max_value=nil)
490
+ max_value = values.max unless max_value
491
+ values.inject("") { |sum, v|
492
+ if max_value == 0
493
+ sum += "0,"
494
+ else
495
+ sum += ( "%.1f" % (v*100/max_value) ) + ","
496
+ end
497
+ }.chomp(",")
498
+ end
499
+
500
+ def extended_encode(values, max_value)
501
+ max_value = values.max unless max_value
502
+ values.collect { |v|
503
+ if max_value == 0
504
+ @@complex_encoding[0]
505
+ else
506
+ @@complex_encoding[(v * 4095/max_value).to_i]
507
+ end
508
+ }.join('')
509
+ end
510
+
511
+ def join_encoded_data(encoded_data)
512
+ encoded_data.join((self.data_encoding == :simple or self.data_encoding == :extended) ? "," : "|")
513
+ end
514
+
515
+ def max_data_value
516
+ @max_data or @data.flatten.max
517
+ end
518
+
519
+ def max_x_value
520
+ @max_x or x_data.flatten.max
521
+ end
522
+
523
+ def max_y_value
524
+ @max_y or y_data.flatten.max
525
+ end
526
+
527
+ def x_data
528
+ @data.collect do |series|
529
+ series.collect { |val| val.first }
530
+ end
531
+ end
532
+
533
+ def y_data
534
+ @data.collect do |series|
535
+ series.collect { |val| val.last }
536
+ end
537
+ end
538
+ end
539
+ end