dynamic_reports 0.0.0

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.
@@ -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