gregoryfoster-gchartrb 0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CREDITS +6 -0
- data/History.txt +69 -0
- data/Manifest.txt +47 -0
- data/README.txt +64 -0
- data/Rakefile +61 -0
- data/TODO +10 -0
- data/gchartrb.gemspec +18 -0
- data/lib/core_ext.rb +32 -0
- data/lib/example.rb +75 -0
- data/lib/gchartrb.rb +1 -0
- data/lib/google_chart.rb +39 -0
- data/lib/google_chart/bar_chart.rb +83 -0
- data/lib/google_chart/base.rb +148 -0
- data/lib/google_chart/line_chart.rb +19 -0
- data/lib/google_chart/linexy_chart.rb +20 -0
- data/lib/google_chart/map_chart.rb +74 -0
- data/lib/google_chart/modules/axis.rb +62 -0
- data/lib/google_chart/modules/color.rb +15 -0
- data/lib/google_chart/modules/data_array.rb +57 -0
- data/lib/google_chart/modules/fills.rb +102 -0
- data/lib/google_chart/modules/grid.rb +27 -0
- data/lib/google_chart/modules/label.rb +27 -0
- data/lib/google_chart/modules/legend.rb +24 -0
- data/lib/google_chart/modules/markers.rb +88 -0
- data/lib/google_chart/pie_chart.rb +44 -0
- data/lib/google_chart/radar_chart.rb +33 -0
- data/lib/google_chart/scatter_plot.rb +34 -0
- data/lib/google_chart/sparkline_chart.rb +19 -0
- data/lib/google_chart/venn_diagram.rb +41 -0
- data/lib/test.rb +252 -0
- data/scripts/iso3166_en_code_lists.txt +250 -0
- data/scripts/process_country_codes.rb +27 -0
- data/spec/gchartrb/axis_spec.rb +125 -0
- data/spec/gchartrb/bar_chart_spec.rb +128 -0
- data/spec/gchartrb/fills_spec.rb +124 -0
- data/spec/gchartrb/grid_spec.rb +59 -0
- data/spec/gchartrb/line_chart_spec.rb +78 -0
- data/spec/gchartrb/linexy_chart_spec.rb +31 -0
- data/spec/gchartrb/markers_spec.rb +160 -0
- data/spec/gchartrb/pie_chart_spec.rb +36 -0
- data/spec/gchartrb/radar_chart_spec.rb +40 -0
- data/spec/gchartrb/scatter_plot_spec.rb +37 -0
- data/spec/gchartrb/sparkline_chart_spec.rb +16 -0
- data/spec/gchartrb/venn_diagram_spec.rb +57 -0
- data/spec/gchartrb_spec.rb +117 -0
- data/spec/helper.rb +15 -0
- data/spec/spec.opts +7 -0
- metadata +102 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
require 'open-uri'
|
|
2
|
+
require 'uri'
|
|
3
|
+
|
|
4
|
+
module GoogleChart
|
|
5
|
+
class Base
|
|
6
|
+
# Make new method private to avoid direct initialisation
|
|
7
|
+
class <<self ; private :new ; end
|
|
8
|
+
|
|
9
|
+
# Wicked metaprogramming hack to :
|
|
10
|
+
# - make the new method public to enable initialisation
|
|
11
|
+
# - add a chart_type attribute to base class
|
|
12
|
+
def self.inherited(subclass) #:nodoc:
|
|
13
|
+
subclass.class_eval do
|
|
14
|
+
attr_reader :chart_type
|
|
15
|
+
class <<self ; public :new ; end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Chart width, in pixels
|
|
20
|
+
attr_accessor :width
|
|
21
|
+
|
|
22
|
+
# Chart height, in pixels
|
|
23
|
+
attr_accessor :height
|
|
24
|
+
|
|
25
|
+
# Chart title
|
|
26
|
+
attr_accessor :title
|
|
27
|
+
|
|
28
|
+
# Chart title color (hex value)
|
|
29
|
+
attr_accessor :title_color
|
|
30
|
+
|
|
31
|
+
# Chart title font size
|
|
32
|
+
attr_accessor :title_font_size
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# Encoding
|
|
36
|
+
attr_accessor :encoding
|
|
37
|
+
|
|
38
|
+
# TODO write doc here
|
|
39
|
+
def initialize(options = {}, &block)
|
|
40
|
+
@width = 320
|
|
41
|
+
@height = 200
|
|
42
|
+
@encoding = :simple
|
|
43
|
+
@data = []
|
|
44
|
+
@params = {}
|
|
45
|
+
options.each { |k, v| send("#{k}=", v) }
|
|
46
|
+
yield self if block_given?
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def title=(title) #:nodoc:
|
|
50
|
+
@title = title.gsub("\n", "|")
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Sets the chart's width, in pixels. Raises +ArgumentError+
|
|
54
|
+
# if +width+ is less than 1 or greater than 1,000 (440 for maps).
|
|
55
|
+
def width=(width)
|
|
56
|
+
width_max = @chart_type == 't' ? 440 : 1_000
|
|
57
|
+
if width.nil? || width < 1 || width > width_max
|
|
58
|
+
raise ArgumentError, "Invalid width: #{width.inspect}"
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
@width = width
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Sets the chart's height, in pixels. Raises +ArgumentError+
|
|
65
|
+
# if +height+ is less than 1 or greater than 1,000 (220 for maps).
|
|
66
|
+
def height=(height)
|
|
67
|
+
height_max = @chart_type == 't' ? 220 : 1_000
|
|
68
|
+
if height.nil? || height < 1 || height > height_max
|
|
69
|
+
raise ArgumentError, "Invalid height: #{height.inspect}"
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
@height = height
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Returns the chart's size as "WIDTHxHEIGHT".
|
|
76
|
+
def size
|
|
77
|
+
"#{width}x#{height}"
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Sets the chart's size as "WIDTHxHEIGHT". Raises +ArgumentError+
|
|
81
|
+
# if +width+ * +height+ is greater than 300,000 pixels.
|
|
82
|
+
def size=(size)
|
|
83
|
+
self.width, self.height = size.split("x").collect { |n| Integer(n) }
|
|
84
|
+
|
|
85
|
+
if (width * height) > 300_000
|
|
86
|
+
raise ArgumentError, "Invalid size: #{size.inspect} yields a graph with more than 300,000 pixels"
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
# Set the data encoding to one of :simple, :text or :extended
|
|
92
|
+
def encoding=(enc)
|
|
93
|
+
raise ArgumentError.new("unsupported encoding: #{encoding.inspect}") unless GoogleChart::ENCODINGS.include?(enc)
|
|
94
|
+
@encoding = enc
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Return a hash of the query params that will be converted to the URL
|
|
98
|
+
def query_params
|
|
99
|
+
@params.clear
|
|
100
|
+
add_defaults
|
|
101
|
+
add_data
|
|
102
|
+
add_legends if @legends and show_legend?
|
|
103
|
+
add_labels if @labels and show_labels?
|
|
104
|
+
add_colors if @colors
|
|
105
|
+
add_fills if @fills
|
|
106
|
+
add_axes if @axes
|
|
107
|
+
add_grid if @grid
|
|
108
|
+
add_markers if @markers
|
|
109
|
+
add_bar_width_and_spacing if respond_to?(:add_bar_width_and_spacing)
|
|
110
|
+
add_map_parameters if respond_to?(:add_map_parameters)
|
|
111
|
+
return @params
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Returns a URL to access the chart.
|
|
115
|
+
# Use +extras+ to add more parameters that may be unsupported or impossible to construct using gchartrb
|
|
116
|
+
def to_url(extras={})
|
|
117
|
+
query = query_params.merge(extras).collect { |k, v| "#{k}=#{URI.escape(v)}" }.join("&")
|
|
118
|
+
"#{GoogleChart::URL}?#{query}"
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
private
|
|
122
|
+
|
|
123
|
+
def add_defaults
|
|
124
|
+
@params[:cht] = chart_type
|
|
125
|
+
@params[:chs] = size
|
|
126
|
+
@params[:chtt] = title if title
|
|
127
|
+
@params[:chts] = [title_color, title_font_size].compact.join(",")
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def add_data
|
|
131
|
+
sets = encode_data # calling encode data method of subclass
|
|
132
|
+
prefix = case encoding
|
|
133
|
+
when :simple then "s:"
|
|
134
|
+
when :text then "t:"
|
|
135
|
+
when :extended then "e:"
|
|
136
|
+
end
|
|
137
|
+
@params[:chd] = prefix + sets.join(get_series_separator)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def get_series_separator
|
|
141
|
+
encoding == :text ? "|" : ","
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def get_data_separator
|
|
145
|
+
encoding == :text ? "," : ""
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module GoogleChart
|
|
2
|
+
class LineChart < Base
|
|
3
|
+
include Legend
|
|
4
|
+
include Color
|
|
5
|
+
include DataArray
|
|
6
|
+
include Fills
|
|
7
|
+
include Axis
|
|
8
|
+
include Grid
|
|
9
|
+
include Markers
|
|
10
|
+
|
|
11
|
+
data_type :numeric_array
|
|
12
|
+
|
|
13
|
+
def initialize(options={})
|
|
14
|
+
@chart_type = "lc"
|
|
15
|
+
@show_legend = false
|
|
16
|
+
super(options)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module GoogleChart
|
|
2
|
+
class LineXYChart < Base
|
|
3
|
+
include Legend
|
|
4
|
+
include Color
|
|
5
|
+
include DataArray
|
|
6
|
+
include Fills
|
|
7
|
+
include Axis
|
|
8
|
+
include Grid
|
|
9
|
+
include Markers
|
|
10
|
+
|
|
11
|
+
data_type :numeric_2d_array
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def initialize(options={})
|
|
15
|
+
@chart_type = "lxy"
|
|
16
|
+
@show_legend = false
|
|
17
|
+
super(options)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
module GoogleChart
|
|
2
|
+
class MapChart < Base
|
|
3
|
+
include Color
|
|
4
|
+
include DataArray
|
|
5
|
+
include Fills
|
|
6
|
+
|
|
7
|
+
GEOGRAPHICAL_AREAS = [ :africa, :asia, :europe, :middle_east, :south_america, :usa, :world ]
|
|
8
|
+
ISO_3166_1_ALPHA_2 = [ :AF, :AX, :AL, :DZ, :AS, :AD, :AO, :AI, :AQ, :AG, :AR, :AM, :AW, :AU, :AT, :AZ, :BS, :BH, :BD, :BB, :BY, :BE, :BZ, :BJ, :BM, :BT, :BO, :BA, :BW, :BV, :BR, :IO, :BN, :BG, :BF, :BI, :KH, :CM, :CA, :CV, :KY, :CF, :TD, :CL, :CN, :CX, :CC, :CO, :KM, :CG, :CD, :CK, :CR, :CI, :HR, :CU, :CY, :CZ, :DK, :DJ, :DM, :DO, :EC, :EG, :SV, :GQ, :ER, :EE, :ET, :FK, :FO, :FJ, :FI, :FR, :GF, :PF, :TF, :GA, :GM, :GE, :DE, :GH, :GI, :GR, :GL, :GD, :GP, :GU, :GT, :GG, :GN, :GW, :GY, :HT, :HM, :VA, :HN, :HK, :HU, :IS, :IN, :ID, :IR, :IQ, :IE, :IM, :IL, :IT, :JM, :JP, :JE, :JO, :KZ, :KE, :KI, :KP, :KR, :KW, :KG, :LA, :LV, :LB, :LS, :LR, :LY, :LI, :LT, :LU, :MO, :MK, :MG, :MW, :MY, :MV, :ML, :MT, :MH, :MQ, :MR, :MU, :YT, :MX, :FM, :MD, :MC, :MN, :ME, :MS, :MA, :MZ, :MM, :NA, :NR, :NP, :NL, :AN, :NC, :NZ, :NI, :NE, :NG, :NU, :NF, :MP, :NO, :OM, :PK, :PW, :PS, :PA, :PG, :PY, :PE, :PH, :PN, :PL, :PT, :PR, :QA, :RE, :RO, :RU, :RW, :BL, :SH, :KN, :LC, :MF, :PM, :VC, :WS, :SM, :ST, :SA, :SN, :RS, :SC, :SL, :SG, :SK, :SI, :SB, :SO, :ZA, :GS, :ES, :LK, :SD, :SR, :SJ, :SZ, :SE, :CH, :SY, :TW, :TJ, :TZ, :TH, :TL, :TG, :TK, :TO, :TT, :TN, :TR, :TM, :TC, :TV, :UG, :UA, :AE, :GB, :US, :UM, :UY, :UZ, :VU, :VE, :VN, :VG, :VI, :WF, :EH, :YE, :ZM, :ZW ]
|
|
9
|
+
STATE_ABBREVIATIONS = [ :AL, :AK, :AZ, :AR, :CA, :CO, :CT, :DE, :DC, :FL, :GA, :HI, :ID, :IL, :IN, :IA, :KS, :KY, :LA, :ME, :MD, :MA, :MI, :MN, :MS, :MO, :MT, :NE, :NV, :NH, :NJ, :NM, :NY, :NC, :ND, :OH, :OK, :OR, :PA, :RI, :SC, :SD, :TN, :TX, :UT, :VT, :VA, :WA, :WV, :WI, :WY ]
|
|
10
|
+
|
|
11
|
+
attr_accessor :geographical_area, :default_color, :gradient
|
|
12
|
+
|
|
13
|
+
data_type :numeric
|
|
14
|
+
|
|
15
|
+
def initialize(options={})
|
|
16
|
+
@chart_type = 't'
|
|
17
|
+
@show_legend = false
|
|
18
|
+
@default_color = 'FFFFFF'
|
|
19
|
+
super(options)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# Called to add map-specific parameters to the query_params hash.
|
|
24
|
+
def add_map_parameters
|
|
25
|
+
@params[:chtm] = geographical_area.to_s
|
|
26
|
+
@params[:chld] = @region_codes.join('') unless @region_codes.empty?
|
|
27
|
+
unless @default_color.empty?
|
|
28
|
+
@params[:chco] = @params[:chco].empty? ? @default_color : "#{@default_color},#{@params[:chco]}"
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
#
|
|
34
|
+
# ACCESSORS.
|
|
35
|
+
#
|
|
36
|
+
def data(code, data)
|
|
37
|
+
raise ArgumentError.new("data should be an integer value") unless data.is_a?(Numeric)
|
|
38
|
+
@data << data
|
|
39
|
+
region_code(code)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def geographical_area=(area)
|
|
43
|
+
raise ArgumentError.new("area must be one of :#{GEOGRAPHICAL_AREAS.join(', :')}") unless GEOGRAPHICAL_AREAS.include?(area)
|
|
44
|
+
@geographical_area = area
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def gradient=(gradient_colors)
|
|
48
|
+
if gradient_colors.is_a?(Array)
|
|
49
|
+
gradient_colors.each { |gradient_color| color(gradient_color) }
|
|
50
|
+
elsif gradient_colors.is_a?(String)
|
|
51
|
+
color(gradient_colors)
|
|
52
|
+
else
|
|
53
|
+
raise ArgumentError.new("gradient must be a single RGB value as a String or an Array of String RGB values")
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
def region_code(code)
|
|
59
|
+
@region_codes ||= []
|
|
60
|
+
|
|
61
|
+
# Ensure a proper region code is being added.
|
|
62
|
+
raise ArgumentError.new("geographical area must be defined before adding data") unless @geographical_area
|
|
63
|
+
|
|
64
|
+
if @geographical_area == :usa
|
|
65
|
+
raise ArgumentError.new("geographical area must be a US state abbreviation") unless STATE_ABBREVIATIONS.include?(code)
|
|
66
|
+
else
|
|
67
|
+
raise ArgumentError.new("geographical area must be an ISO-3166-1 alpha-2 code") unless ISO_3166_1_ALPHA_2.include?(code)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
@region_codes << code
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
end
|
|
74
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
require 'ostruct'
|
|
2
|
+
module GoogleChart
|
|
3
|
+
# This module is used as a mixin for providing axis
|
|
4
|
+
module Axis
|
|
5
|
+
AXIS_TYPE_MAPPING = { :top => "t", :bottom => "x", :left => "y", :right => "r" }
|
|
6
|
+
ALIGNMENT_MAPPING = { :center => 0, :left => -1, :right => -1}
|
|
7
|
+
|
|
8
|
+
Axis = Struct.new("Axis", :type, :labels, :positions, :range, :color, :font_size, :alignment)
|
|
9
|
+
|
|
10
|
+
def axis(type, options={})
|
|
11
|
+
@axes ||= []
|
|
12
|
+
raise ArgumentError.new("Axis type must be :top, :bottom, :left or :right") unless AXIS_TYPE_MAPPING.keys.include?(type)
|
|
13
|
+
axis = Axis.new
|
|
14
|
+
axis.type = type
|
|
15
|
+
|
|
16
|
+
options.each { |k,v| axis.send("#{k}=",v) }
|
|
17
|
+
yield axis if block_given?
|
|
18
|
+
|
|
19
|
+
validate_axis(axis)
|
|
20
|
+
@axes << axis
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
def add_axes
|
|
25
|
+
chxt = @axes.collect { |axis| "#{AXIS_TYPE_MAPPING[axis.type]}" }
|
|
26
|
+
labels = @axes.collect { |axis| "#{@axes.index(axis)}:|#{axis.labels.join('|')}" if axis.labels }.compact
|
|
27
|
+
positions = @axes.collect { |axis| "#{@axes.index(axis)},#{axis.positions.join(',')}" if axis.positions}.compact
|
|
28
|
+
ranges = @axes.collect { |axis| "#{@axes.index(axis)},#{axis.range.min},#{axis.range.max}" if axis.range }.compact
|
|
29
|
+
styles = @axes.collect do |axis|
|
|
30
|
+
style = [axis.color,axis.font_size, ALIGNMENT_MAPPING[axis.alignment]].compact
|
|
31
|
+
unless style.empty?
|
|
32
|
+
"#{@axes.index(axis)},#{style.join(',')}"
|
|
33
|
+
end
|
|
34
|
+
end.compact
|
|
35
|
+
|
|
36
|
+
@params[:chxt] = chxt.join(",")
|
|
37
|
+
@params[:chxl] = labels.join("|") unless labels.empty?
|
|
38
|
+
@params[:chxp] = positions.join("|") unless positions.empty?
|
|
39
|
+
@params[:chxr] = ranges.join("|") unless ranges.empty?
|
|
40
|
+
@params[:chxs] = styles.join("|") unless styles.empty?
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def validate_axis(axis)
|
|
44
|
+
raise ArgumentError.new("color must be a string containing a hex value") unless axis.color == nil or axis.color.is_a?(String)
|
|
45
|
+
raise ArgumentError.new("labels must be an array") unless axis.labels == nil or axis.labels.is_a?(Array)
|
|
46
|
+
raise ArgumentError.new("positions must be an array of numeric values") unless axis.positions == nil or (axis.positions.is_a?(Array) and axis.positions.is_numeric_array?)
|
|
47
|
+
raise ArgumentError.new("font_size must be an integer") unless axis.font_size == nil or axis.font_size.is_a?(Integer)
|
|
48
|
+
raise ArgumentError.new("range must be an range of integer values") unless axis.range == nil or axis.range.is_a?(Range) or axis.range.min.is_a?(Integer)
|
|
49
|
+
raise ArgumentError.new("font_size cannot be specified without a color") if (axis.font_size && axis.color == nil)
|
|
50
|
+
raise ArgumentError.new("alignment cannot be specified without a color") if (axis.alignment && (axis.color == nil or axis.font_size == nil))
|
|
51
|
+
raise ArgumentError.new("alignment must be one of :left, :center, :right") unless (axis.alignment ==nil or ALIGNMENT_MAPPING.keys.include?(axis.alignment))
|
|
52
|
+
|
|
53
|
+
unless (axis.labels.to_a.size == axis.positions.to_a.size)
|
|
54
|
+
raise ArgumentError.new("sizes of labels and positions must be the same") unless axis.labels == nil or axis.positions == nil
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
if axis.positions and axis.range
|
|
58
|
+
raise ArgumentError.new("positions must be in the specified range") unless axis.positions.all? { |item| axis.range.include?(item)}
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module GoogleChart
|
|
2
|
+
# This module is used as a mixin for providing color functionality
|
|
3
|
+
module Color
|
|
4
|
+
private
|
|
5
|
+
def color(color)
|
|
6
|
+
@colors ||= []
|
|
7
|
+
@colors << color
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def add_colors
|
|
11
|
+
@params[:chco] = @colors.compact.join(",") unless @colors.compact.empty?
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
module GoogleChart
|
|
2
|
+
# This module is used as a mixin for providing data functionality
|
|
3
|
+
module DataArray
|
|
4
|
+
def self.included(mod)#:nodoc:
|
|
5
|
+
# Extend class methods
|
|
6
|
+
mod.send(:extend,ClassMethods)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def data(legend, series, color = nil)
|
|
10
|
+
raise ArgumentError.new("Invalid value for series data") unless series.send(self.class.get_data_type)
|
|
11
|
+
@data << series
|
|
12
|
+
legend(legend)
|
|
13
|
+
color(color)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def encode_data
|
|
17
|
+
if @data.first.is_a?(Numeric)
|
|
18
|
+
max_val = (self.max or @data.max)
|
|
19
|
+
sets = [@data.collect { |d| GoogleChart::encode(encoding,d,max_val)}.join(get_data_separator)]
|
|
20
|
+
elsif @data.first.is_numeric_2d_array? # Check if each data is an array of 2d values
|
|
21
|
+
x_values = @data.collect { |item| item.x_values }
|
|
22
|
+
y_values = @data.collect { |item| item.y_values }
|
|
23
|
+
|
|
24
|
+
x_max_val = (max_x or x_values.flatten.max)
|
|
25
|
+
y_max_val = (max_y or y_values.flatten.max)
|
|
26
|
+
|
|
27
|
+
x_sets = x_values.collect { |set| set.collect{ |d| GoogleChart::encode(encoding,d,x_max_val)}.join(get_data_separator) }
|
|
28
|
+
y_sets = y_values.collect { |set| set.collect{ |d| GoogleChart::encode(encoding,d,y_max_val)}.join(get_data_separator) }
|
|
29
|
+
|
|
30
|
+
sets = x_sets.zip(y_sets).collect { |item| item.join(get_series_separator) }
|
|
31
|
+
elsif @data.first.is_numeric_array?
|
|
32
|
+
max_val = (self.max or @data.flatten.max)
|
|
33
|
+
sets = @data.collect { |set| set.collect{ |d| GoogleChart::encode(encoding,d,max_val)}.join(get_data_separator) }
|
|
34
|
+
end
|
|
35
|
+
return sets
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
module ClassMethods
|
|
39
|
+
def data_type(type) #:nodoc:
|
|
40
|
+
raise ArgumentError.new("Invalid data type, must be either :numeric, :numeric_array or :numeric_2d_array") unless [:numeric, :numeric_array, :numeric_2d_array].include?(type)
|
|
41
|
+
# This is the *instance variable* of the *singleton class* ;)
|
|
42
|
+
@data_type = type
|
|
43
|
+
|
|
44
|
+
if @data_type == :numeric_2d_array
|
|
45
|
+
attr_accessor :max_x, :max_y
|
|
46
|
+
else
|
|
47
|
+
attr_accessor :max
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def get_data_type #:nodoc:
|
|
52
|
+
raise ArgumentError.new("Data type not specified") unless @data_type
|
|
53
|
+
"is_#{@data_type}?".to_sym
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
require 'ostruct'
|
|
2
|
+
module GoogleChart
|
|
3
|
+
# This module is used as a mixin for providing solid, linear gradient and stripe fills
|
|
4
|
+
module Fills
|
|
5
|
+
FILL_KINDS = [:solid, :gradient, :stripes]
|
|
6
|
+
|
|
7
|
+
FILL_TYPES_SOLID = [:background, :chart, :transparent]
|
|
8
|
+
FILL_TYPES = [:background, :chart]
|
|
9
|
+
FILL_TYPE_CODES = ["bg", "c", "a"]
|
|
10
|
+
|
|
11
|
+
SolidFill = Struct.new("SolidFill", :type, :color)
|
|
12
|
+
GradientFill = Struct.new("GradientFill", :type, :angle, :colors, :offsets)
|
|
13
|
+
StripesFill = Struct.new("StripesFill", :type, :angle, :colors, :widths)
|
|
14
|
+
|
|
15
|
+
# TODO add proper docs
|
|
16
|
+
def fill(type, options = {})
|
|
17
|
+
@fills ||= []
|
|
18
|
+
raise ArgumentError.new("Invalid fill type") unless FILL_KINDS.include?(type)
|
|
19
|
+
f= create_fill(type)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
f.type = :background #default
|
|
23
|
+
options.each { |k,v| f.send("#{k}=",v)}
|
|
24
|
+
|
|
25
|
+
yield f if block_given?
|
|
26
|
+
|
|
27
|
+
validate_fill(f)
|
|
28
|
+
|
|
29
|
+
@fills << parse_fill(f)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
def add_fills
|
|
34
|
+
@params[:chf] = @fills.join("|") unless @fills.empty?
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def create_fill(type)
|
|
38
|
+
case type
|
|
39
|
+
when :solid
|
|
40
|
+
SolidFill.new
|
|
41
|
+
when :gradient
|
|
42
|
+
GradientFill.new
|
|
43
|
+
when :stripes
|
|
44
|
+
StripesFill.new
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def validate_fill(f)
|
|
49
|
+
case f
|
|
50
|
+
when SolidFill
|
|
51
|
+
validate_solid(f)
|
|
52
|
+
when GradientFill
|
|
53
|
+
validate_gradient(f)
|
|
54
|
+
when StripesFill
|
|
55
|
+
validate_stripes(f)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def validate_solid(f)
|
|
60
|
+
raise ArgumentError.new("Invalid fill type value. Must be one of :background, :chart or :transparent") unless FILL_TYPES_SOLID.include?(f.type)
|
|
61
|
+
raise ArgumentError.new(":color must be a string value") unless f.color.is_a?(String)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def validate_gradient(f)
|
|
65
|
+
raise ArgumentError.new("Invalid fill type value. Must be one of :background, :chart or :transparent") unless FILL_TYPES.include?(f.type)
|
|
66
|
+
raise ArgumentError.new("angle must be a numeric value") unless f.angle.is_a?(Numeric)
|
|
67
|
+
#TODO validate color values using regex
|
|
68
|
+
raise ArgumentError.new(":colors must be an array of strings") unless f.colors.is_a?(Array) and f.colors.all? { |i| i.is_a?(String)}
|
|
69
|
+
raise ArgumentError.new(":offsets must be an array of numeric values between 0 and 1") unless f.offsets.is_a?(Array) and
|
|
70
|
+
f.offsets.is_numeric_array? and
|
|
71
|
+
f.offsets.all? { |i| i>=0 and i<=1 }
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def validate_stripes(f)
|
|
75
|
+
raise ArgumentError.new("Invalid fill type value. Must be one of :background, :chart or :transparent") unless FILL_TYPES.include?(f.type)
|
|
76
|
+
raise ArgumentError.new("angle must be a numeric value") unless f.angle.is_a?(Numeric)
|
|
77
|
+
#TODO validate color values using regex
|
|
78
|
+
raise ArgumentError.new(":colors must be an array of strings") unless f.colors.is_a?(Array) and f.colors.all? { |i| i.is_a?(String)}
|
|
79
|
+
raise ArgumentError.new(":widths must be an array of numeric values between 0 and 1") unless f.widths.is_a?(Array) and
|
|
80
|
+
f.widths.is_numeric_array? and
|
|
81
|
+
f.widths.all? { |i| i>=0 and i<=1 }
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def parse_fill(f)
|
|
85
|
+
case f
|
|
86
|
+
when SolidFill
|
|
87
|
+
"#{map_type(f.type)},s,#{f.color}"
|
|
88
|
+
when GradientFill
|
|
89
|
+
colors_offsets = f.colors.zip(f.offsets).collect { |item| item.join(",") }.join(",")
|
|
90
|
+
"#{map_type(f.type)},lg,#{f.angle},#{colors_offsets}"
|
|
91
|
+
when StripesFill
|
|
92
|
+
colors_widths = f.colors.zip(f.widths).collect { |item| item.join(",") }.join(",")
|
|
93
|
+
"#{map_type(f.type)},ls,#{f.angle},#{colors_widths}"
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def map_type(t)
|
|
98
|
+
i = FILL_TYPES_SOLID.index(t)
|
|
99
|
+
FILL_TYPE_CODES[i]
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|