dynamic_reports 0.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/HISTORY +2 -0
- data/README +146 -0
- data/gemspec.rb +24 -0
- data/lib/dynamic_reports.rb +47 -0
- data/lib/dynamic_reports/charts.rb +217 -0
- data/lib/dynamic_reports/reports.rb +252 -0
- data/lib/dynamic_reports/templates.rb +178 -0
- data/lib/dynamic_reports/vendor/google_chart.rb +11 -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/views.rb +30 -0
- data/lib/dynamic_reports/views/default_chart.html.erb +0 -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
- metadata +74 -0
@@ -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
|
@@ -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
|