class Gchart
Attributes
Public Class Methods
# File lib/gchart.rb, line 29 def self.chars @chars ||= simple_chars + ['-', '.'] end
# File lib/gchart.rb, line 37 def self.default_filename 'chart.png' end
# File lib/gchart.rb, line 33 def self.ext_pairs @ext_pairs ||= chars.map { |char_1| chars.map { |char_2| char_1 + char_2 } }.flatten end
# File lib/gchart.rb, line 265 def self.jstize(string) # See http://github.com/mattetti/googlecharts/issues#issue/27 #URI.escape( string ).gsub("%7C", "|") # See discussion: http://github.com/mattetti/googlecharts/commit/9b5cfb93aa51aae06611057668e631cd515ec4f3#comment_51347 string.gsub(' ', '+').gsub(/\[|\{|\}|\\|\^|\[|\]|\`|\]/) {|c| "%#{c[0].to_s.upcase}"} #string.gsub(' ', '+').gsub(/\[|\{|\}|\||\\|\^|\[|\]|\`|\]/) {|c| "%#{c[0].to_s.upcase}"} end
# File lib/gchart.rb, line 71 def self.method_missing(m, options={}) raise NoMethodError, "#{m} is not a supported chart format. Please use one of the following: #{supported_types}." end
# File lib/gchart.rb, line 75 def initialize(options={}) # Allow Gchart to take a theme too @theme = options[:theme] options = @theme ? Chart::Theme.load(@theme).to_options.merge(options) : options options.delete(:theme) @type = options[:type] || 'line' @data = [] @width = 300 @height = 200 @curved = false @horizontal = false @grouped = false @overlapped = false @use_ssl = false @encoding = 'simple' # @max_value = 'auto' # @min_value defaults to nil meaning zero @filename = options[:filename] # Sets the alt tag when chart is exported as image tag @alt = 'Google Chart' # Sets the CSS id selector when chart is exported as image tag @id = false # Sets the CSS class selector when chart is exported as image tag @klass = options[:class] || false # set the options value if definable options.each do |attribute, value| send("#{attribute}=", value) if self.respond_to?("#{attribute}=") end end
# File lib/gchart.rb, line 25 def self.simple_chars @simple_chars ||= ('A'..'Z').to_a + ('a'..'z').to_a + ('0'..'9').to_a end
# File lib/gchart.rb, line 108 def self.supported_types self.types.join(' ') end
# File lib/gchart.rb, line 21 def self.types @types ||= ['line', 'line_xy', 'scatter', 'bar', 'venn', 'pie', 'pie_3d', 'pie_c', 'jstize', 'sparkline', 'meter', 'map', 'radar'] end
# File lib/gchart.rb, line 13 def self.url(use_ssl = false) if use_ssl 'https://chart.googleapis.com/chart?' else 'http://chart.apis.google.com/chart?' end end
# File lib/gchart.rb, line 67 def self.version VERSION::STRING end
Public Instance Methods
# File lib/gchart.rb, line 136 def bar_presentation if @overlapped 'o' elsif @grouped 'g' else 's' end end
# File lib/gchart.rb, line 146 def bg=(options) if options.is_a?(String) @bg_color = options elsif options.is_a?(Hash) @bg_color = options[:color] @bg_type = options[:type] @bg_angle = options[:angle] end end
# File lib/gchart.rb, line 242 def dataset if @dataset @dataset else @dataset = convert_dataset(data || []) full_data_range(@dataset) # unless axis_range @dataset end end
Sets of data to handle multiple sets
# File lib/gchart.rb, line 253 def datasets datasets = [] dataset.each do |d| if d[:data].first.is_a?(Array) datasets += d[:data] else datasets << d[:data] end end datasets end
# File lib/gchart.rb, line 122 def dimensions # TODO: maybe others? [:line_xy, :scatter].include?(type) ? 2 : 1 end
Returns the chart’s generated PNG as a blob. (borrowed from John’s gchart.rubyforge.org)
# File lib/gchart.rb, line 276 def fetch url = URI.parse(self.class.url(use_ssl)) req = Net::HTTP::Post.new(url.path) req.body = query_builder req.content_type = 'application/x-www-form-urlencoded' http = Net::HTTP.new(url.host, url.port) http.verify_mode = OpenSSL::SSL::VERIFY_PEER if use_ssl http.use_ssl = use_ssl http.start {|resp| resp.request(req) }.body end
# File lib/gchart.rb, line 315 def file write end
returns the full data range as an array it also sets the data range if not defined
# File lib/gchart.rb, line 184 def full_data_range(ds) return if max_value == false ds.each_with_index do |mds, mds_index| mds[:min_value] ||= min_value mds[:max_value] ||= max_value if mds_index == 0 && type.to_s == 'bar' # TODO: unless you specify a zero line (using chp or chds), # the min_value of a bar chart is always 0. #mds[:min_value] ||= mds[:data].first.to_a.compact.min mds[:min_value] ||= 0 end if (mds_index == 0 && type.to_s == 'bar' && !grouped && mds[:data].first.is_a?(Array)) totals = [] mds[:data].each do |l| l.each_with_index do |v, index| next if v.nil? totals[index] ||= 0 totals[index] += v end end mds[:max_value] ||= totals.compact.max else all = mds[:data].flatten.compact # default min value should be 0 unless set to auto if mds[:min_value] == 'auto' mds[:min_value] = all.min else min = all.min mds[:min_value] ||= (min && min < 0 ? min : 0) end mds[:max_value] ||= all.max end end unless axis_range @calculated_axis_range = true @axis_range = ds.map{|mds| [mds[:min_value], mds[:max_value]]} if dimensions == 1 && (type.to_s != 'bar' || horizontal) tmp = axis_range.fetch(0, []) @axis_range[0] = axis_range.fetch(1, []) @axis_range[1] = tmp end end # return [min, max] unless (min.nil? || max.nil?) # @max = (max_value.nil? || max_value == 'auto') ? ds.compact.map{|mds| mds.compact.max}.max : max_value # # if min_value.nil? # min_ds_value = ds.compact.map{|mds| mds.compact.min}.min || 0 # @min = (min_ds_value < 0) ? min_ds_value : 0 # else # @min = min_value == 'auto' ? ds.compact.map{|mds| mds.compact.min}.min || 0 : min_value # end # @axis_range = [[min,max]] end
# File lib/gchart.rb, line 156 def graph_bg=(options) if options.is_a?(String) @chart_color = options elsif options.is_a?(Hash) @chart_color = options[:color] @chart_type = options[:type] @chart_angle = options[:angle] end end
Format
# File lib/gchart.rb, line 296 def image_tag image = "<img" image += " id=\"#{id}\"" if id image += " class=\"#{klass}\"" if klass image += " src=\"#{url_builder(:html)}\"" image += " width=\"#{width}\"" image += " height=\"#{height}\"" image += " alt=\"#{alt}\"" image += " title=\"#{title}\"" if title image += " usemap=\"#{usemap}\"" if usemap image += " />" end
# File lib/gchart.rb, line 320 def jstize(string) self.class.jstize(string) end
# File lib/gchart.rb, line 166 def max_value=(max_v) if max_v =~ /false/ @max_value = false else @max_value = max_v end end
# File lib/gchart.rb, line 174 def min_value=(min_v) if min_v =~ /false/ @min_value = false else @min_value = min_v end end
Sets the orientation of a bar graph
# File lib/gchart.rb, line 128 def orientation=(orientation='h') if orientation == 'h' || orientation == 'horizontal' self.horizontal = true elsif orientation == 'v' || orientation == 'vertical' self.horizontal = false end end
# File lib/gchart.rb, line 118 def size "#{width}x#{height}" end
Defines the Graph size using the following format: width X height
# File lib/gchart.rb, line 114 def size=(size='300x200') @width, @height = size.split("x").map { |dimension| dimension.to_i } end
# File lib/gchart.rb, line 311 def url url_builder end
Writes the chart’s generated PNG to a file. (borrowed from John’s gchart.rubyforge.org)
# File lib/gchart.rb, line 288 def write io_or_file = filename || self.class.default_filename return io_or_file.write(fetch) if io_or_file.respond_to?(:write) open(io_or_file, "wb+") { |io| io.write(fetch) } end
Private Instance Methods
just an alias
# File lib/gchart.rb, line 587 def axis_set dataset end
Turns input into an array of axis hashes, dependent on the chart type
# File lib/gchart.rb, line 567 def convert_dataset(ds) if dimensions == 2 # valid inputs include: # an array of >=2 arrays, or an array of >=2 hashes ds = ds.map do |d| d.is_a?(Hash) ? d : {:data => d} end elsif dimensions == 1 # valid inputs include: # a hash, an array of data, an array of >=1 array, or an array of >=1 hash if ds.is_a?(Hash) ds = [ds] elsif not ds.first.is_a?(Hash) ds = [{:data => ds}] end end ds end
# File lib/gchart.rb, line 600 def convert_to_extended_value(number) if number.nil? '__' else value = self.class.ext_pairs[number.to_i] value.nil? ? "__" : value end end
# File lib/gchart.rb, line 591 def convert_to_simple_value(number) if number.nil? "_" else value = self.class.simple_chars[number.to_i] value.nil? ? "_" : value end end
# File lib/gchart.rb, line 609 def encode_scaled_dataset(chars, nil_char) dsets = [] dataset.each do |ds| if max_value != false range = ds[:max_value] - ds[:min_value] range = 1 if range == 0 end unless ds[:data].first.is_a?(Array) ldatasets = [ds[:data]] else ldatasets = ds[:data] end ldatasets.each do |l| dsets << l.map do |number| if number.nil? nil_char else unless range.nil? || range.zero? number = chars.size * (number - ds[:min_value]) / range.to_f number = [number, chars.size - 1].min end chars[number.to_i] end end.join end end dsets.join(',') end
code.google.com/apis/chart/#extended Extended encoding has a resolution of 4,096 different values and is best used for large charts where a large data range is required.
# File lib/gchart.rb, line 666 def extended_encoding "e" + number_visible + ":" + encode_scaled_dataset(self.class.ext_pairs, '__') end
# File lib/gchart.rb, line 400 def fill_for(type=nil, color='', angle=nil) unless type.nil? case type when 'lg' angle ||= 0 color = "#{color},0,ffffff,1" if color.split(',').size == 1 "#{type},#{angle},#{color}" when 'ls' angle ||= 90 color = "#{color},0.2,ffffff,0.2" if color.split(',').size == 1 "#{type},#{angle},#{color}" else "#{type},#{color}" end end end
# File lib/gchart.rb, line 545 def fill_type(type) case type when 'solid' then 's' when 'gradient' then 'lg' when 'stripes' then 'ls' end end
# File lib/gchart.rb, line 553 def number_visible n = 0 dataset.each do |mds| return n.to_s if mds[:invisible] == true if mds[:data].first.is_a?(Array) n += mds[:data].length else n += 1 end end "" end
# File lib/gchart.rb, line 674 def query_builder(options="") query_params = instance_variables.sort.map do |var| case var.to_s when '@data' set_data unless data == [] # Set the graph size when '@width' set_size unless width.nil? || height.nil? when '@type' set_type when '@title' set_title unless title.nil? when '@legend' set_legend unless legend.nil? when '@labels' set_labels unless labels.nil? when '@legend_position' set_legend_position unless legend_position.nil? when '@thickness' set_line_thickness when '@new_markers' set_line_markers when '@bg_color' set_colors when '@chart_color' set_colors if bg_color.nil? when '@bar_colors' set_bar_colors when '@bar_width_and_spacing' set_bar_width_and_spacing when '@axis_with_labels' set_axis_with_labels when '@axis_labels' set_axis_labels when '@range_markers' set_range_markers when '@grid_lines' set_grid_lines when '@custom_axis_with_labels' set_custom_axis_with_labels when '@geographical_area' set_geographical_area when '@country_codes' set_country_codes when '@custom' custom end end.compact query_params << set_axis_range # Use ampersand as default delimiter unless options == :html delimiter = '&' # Escape ampersand for html image tags else delimiter = '&' end jstize(query_params.join(delimiter)) end
# File lib/gchart.rb, line 481 def set_axis_labels if axis_labels.is_a?(Array) if RUBY_VERSION.to_f < 1.9 labels_arr = axis_labels.enum_with_index.map{|labels,index| [index,labels]} else labels_arr = axis_labels.map.with_index.map{|labels,index| [index,labels]} end elsif axis_labels.is_a?(Hash) labels_arr = axis_labels.to_a end labels_arr.map! do |index,labels| if labels.is_a?(Array) "#{index}:|#{labels.map{|label| "#{CGI::escape(label.to_s)}"}.join('|')}" else "#{index}:|#{labels}" end end "chxl=#{labels_arr.join('|')}" end
code.google.com/apis/chart/labels.html#axis_range Specify a range for axis labels
# File lib/gchart.rb, line 503 def set_axis_range # a passed axis_range should look like: # [[10,100]] or [[10,100,4]] or [[10,100], [20,300]] # in the second example, 4 is the interval set = @calculated_axis_range ? datasets : axis_range || datasets return unless set && set.respond_to?(:each) && set.find {|o| o}.respond_to?(:each) 'chxr=' + set.enum_for(:each_with_index).map do |axis_range, index| next nil if axis_range.nil? # ignore this axis min, max, step = axis_range if axis_range.size > 3 || step && max && step > max # this is a full series max = axis_range.compact.max step = nil end [index, (min_value || min || 0), (max_value || max), step].compact.join(',') end.compact.join("|") end
# File lib/gchart.rb, line 471 def set_axis_with_labels @axis_with_labels = axis_with_labels.join(',') if @axis_with_labels.is_a?(Array) "chxt=#{axis_with_labels}" end
set bar, line colors
# File lib/gchart.rb, line 353 def set_bar_colors @bar_colors = bar_colors.join(',') if bar_colors.is_a?(Array) "chco=#{bar_colors}" end
set bar spacing chbh= <bar width in pixels>, <optional space between bars in a group>, <optional space between groups>
# File lib/gchart.rb, line 368 def set_bar_width_and_spacing width_and_spacing_values = case bar_width_and_spacing when String bar_width_and_spacing when Array bar_width_and_spacing.join(',') when Hash width = bar_width_and_spacing[:width] || 23 spacing = bar_width_and_spacing[:spacing] || 4 group_spacing = bar_width_and_spacing[:group_spacing] || 8 [width,spacing,group_spacing].join(',') else bar_width_and_spacing.to_s end "chbh=#{width_and_spacing_values}" end
# File lib/gchart.rb, line 345 def set_colors @bg_type = fill_type(bg_type) || 's' if bg_color @chart_type = fill_type(chart_type) || 's' if chart_color "chf=" + {'bg' => fill_for(bg_type, bg_color, bg_angle), 'c' => fill_for(chart_type, chart_color, chart_angle)}.map{|k,v| "#{k},#{v}" unless v.nil?}.compact.join('|') end
# File lib/gchart.rb, line 358 def set_country_codes @country_codes = country_codes.join() if country_codes.is_a?(Array) "chld=#{country_codes}" end
# File lib/gchart.rb, line 476 def set_custom_axis_with_labels @custom_axis_with_labels = custom_axis_with_labels.join(',') if @custom_axis_with_labels.is_a?(Array) "chxp=#{custom_axis_with_labels}" end
# File lib/gchart.rb, line 340 def set_data data = send("#{@encoding}_encoding") "chd=#{data}" end
# File lib/gchart.rb, line 522 def set_geographical_area "chtm=#{geographical_area}" end
# File lib/gchart.rb, line 459 def set_grid_lines "chg=#{grid_lines}" end
# File lib/gchart.rb, line 463 def set_labels if labels.is_a?(Array) "chl=#{@labels.map{|label| "#{CGI::escape(label.to_s)}"}.join('|')}" else "chl=#{@labels}" end end
A chart can have one or many legends. Gchart.line(:legend => ‘label’) or Gchart.line(:legend => [‘first label’, ‘last label’])
# File lib/gchart.rb, line 421 def set_legend if type.to_s =~ /meter/ @labels = legend return set_labels end if legend.is_a?(Array) "chdl=#{@legend.map{|label| "#{CGI::escape(label.to_s)}"}.join('|')}" else "chdl=#{legend}" end end
# File lib/gchart.rb, line 434 def set_legend_position case @legend_position.to_s when /(bottom|b)$/ "chdlp=b" when /(bottom_vertical|bv)$/ "chdlp=bv" when /(top|t)$/ "chdlp=t" when /(top_vertical|tv)$/ "chdlp=tv" when /(right|r)$/ "chdlp=r" when /(left|l)$/ "chdlp=l" end end
# File lib/gchart.rb, line 455 def set_line_markers "chm=#{new_markers}" end
# File lib/gchart.rb, line 451 def set_line_thickness "chls=#{thickness}" end
# File lib/gchart.rb, line 395 def set_range_marker(options) orientation = ['vertical', 'Vertical', 'V', 'v', 'R'].include?(options[:orientation]) ? 'R' : 'r' "#{orientation},#{options[:color]},0,#{options[:start_position]},#{options[:stop_position]}#{',1' if options[:overlaid?]}" end
# File lib/gchart.rb, line 385 def set_range_markers markers = case range_markers when Hash set_range_marker(range_markers) when Array range_markers.collect{|marker| set_range_marker(marker)}.join('|') end "chm=#{markers}" end
# File lib/gchart.rb, line 336 def set_size "chs=#{size}" end
The title size cannot be set without specifying a color. A dark key will be used for the title color if no color is specified
# File lib/gchart.rb, line 328 def set_title title_params = "chtt=#{title}".gsub(/\|/,"\n") unless (title_color.nil? && title_size.nil? && title_alignment.nil?) title_params << "&chts=" + (color, size, alignment = (title_color || '454545'), title_size, (title_alignment.to_s[0,1] || 'c')).compact.join(',') end title_params end
# File lib/gchart.rb, line 526 def set_type 'cht=' + case type.to_s when 'line' then "lc" when 'line_xy' then "lxy" when 'pie_3d' then "p3" when 'pie_c' then "pc" when 'pie' then "p" when 'venn' then "v" when 'scatter' then "s" when 'sparkline' then "ls" when 'meter' then "gom" when 'map' then "t" when 'radar' "r" + (curved? ? 's' : '') when 'bar' "b" + (horizontal? ? "h" : "v") + bar_presentation end end
code.google.com/apis/chart/#simple Simple encoding has a resolution of 62 different values. Allowing five pixels per data point, this is sufficient for line and bar charts up to about 300 pixels. Simple encoding is suitable for all other types of chart regardless of size.
# File lib/gchart.rb, line 642 def simple_encoding "s" + number_visible + ":" + encode_scaled_dataset(self.class.simple_chars, '_') end
code.google.com/apis/chart/#text Text encoding with data scaling lets you specify arbitrary positive or negative floating point numbers, in combination with a scaling parameter that lets you specify a custom range for your chart. This chart is useful when you don’t want to worry about limiting your data to a specific range, or do the calculations to scale your data down or up to fit nicely inside a chart.
Valid values range from (+/-)9.999e(+/-)100, and only four non-zero digits are supported (that is, 123400, 1234, 12.34, and 0.1234 are valid, but 12345, 123.45 and 123400.5 are not).
This encoding is not available for maps.
# File lib/gchart.rb, line 658 def text_encoding chds = dataset.map{|ds| "#{ds[:min_value]},#{ds[:max_value]}" }.join(",") "t" + number_visible + ":" + datasets.map{ |ds| ds.map{|e|e||'_'}.join(',') }.join('|') + "&chds=" + chds end
# File lib/gchart.rb, line 670 def url_builder(options="") self.class.url(use_ssl) + query_builder(options) end