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.
Files changed (48) hide show
  1. data/CREDITS +6 -0
  2. data/History.txt +69 -0
  3. data/Manifest.txt +47 -0
  4. data/README.txt +64 -0
  5. data/Rakefile +61 -0
  6. data/TODO +10 -0
  7. data/gchartrb.gemspec +18 -0
  8. data/lib/core_ext.rb +32 -0
  9. data/lib/example.rb +75 -0
  10. data/lib/gchartrb.rb +1 -0
  11. data/lib/google_chart.rb +39 -0
  12. data/lib/google_chart/bar_chart.rb +83 -0
  13. data/lib/google_chart/base.rb +148 -0
  14. data/lib/google_chart/line_chart.rb +19 -0
  15. data/lib/google_chart/linexy_chart.rb +20 -0
  16. data/lib/google_chart/map_chart.rb +74 -0
  17. data/lib/google_chart/modules/axis.rb +62 -0
  18. data/lib/google_chart/modules/color.rb +15 -0
  19. data/lib/google_chart/modules/data_array.rb +57 -0
  20. data/lib/google_chart/modules/fills.rb +102 -0
  21. data/lib/google_chart/modules/grid.rb +27 -0
  22. data/lib/google_chart/modules/label.rb +27 -0
  23. data/lib/google_chart/modules/legend.rb +24 -0
  24. data/lib/google_chart/modules/markers.rb +88 -0
  25. data/lib/google_chart/pie_chart.rb +44 -0
  26. data/lib/google_chart/radar_chart.rb +33 -0
  27. data/lib/google_chart/scatter_plot.rb +34 -0
  28. data/lib/google_chart/sparkline_chart.rb +19 -0
  29. data/lib/google_chart/venn_diagram.rb +41 -0
  30. data/lib/test.rb +252 -0
  31. data/scripts/iso3166_en_code_lists.txt +250 -0
  32. data/scripts/process_country_codes.rb +27 -0
  33. data/spec/gchartrb/axis_spec.rb +125 -0
  34. data/spec/gchartrb/bar_chart_spec.rb +128 -0
  35. data/spec/gchartrb/fills_spec.rb +124 -0
  36. data/spec/gchartrb/grid_spec.rb +59 -0
  37. data/spec/gchartrb/line_chart_spec.rb +78 -0
  38. data/spec/gchartrb/linexy_chart_spec.rb +31 -0
  39. data/spec/gchartrb/markers_spec.rb +160 -0
  40. data/spec/gchartrb/pie_chart_spec.rb +36 -0
  41. data/spec/gchartrb/radar_chart_spec.rb +40 -0
  42. data/spec/gchartrb/scatter_plot_spec.rb +37 -0
  43. data/spec/gchartrb/sparkline_chart_spec.rb +16 -0
  44. data/spec/gchartrb/venn_diagram_spec.rb +57 -0
  45. data/spec/gchartrb_spec.rb +117 -0
  46. data/spec/helper.rb +15 -0
  47. data/spec/spec.opts +7 -0
  48. 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