$:.unshift File.dirname(__FILE__) require 'gchart/version' require "open-uri" require "uri" class Gchart include GchartInfo @@url = "http://chart.apis.google.com/chart?" @@types = ['line', 'line_xy', 'scatter', 'bar', 'venn', 'pie', 'pie_3d', 'jstize'] @@simple_chars = ('A'..'Z').to_a + ('a'..'z').to_a + ('0'..'9').to_a @@chars = @@simple_chars + ['-', '.'] @@ext_pairs = @@chars.map { |char_1| @@chars.map { |char_2| char_1 + char_2 } }.flatten @@file_name = 'chart.png' attr_accessor :title, :type, :width, :height, :horizontal, :grouped, :legend, :data, :encoding, :max_value, :bar_colors, :title_color, :title_size, :custom, :axis_with_labels, :axis_labels class << self # Support for Gchart.line(:title => 'my title', :size => '400x600') def method_missing(m, options={}) # Extract the format and optional filename, then clean the hash format = options[:format] || 'url' @@file_name = options[:filename] unless options[:filename].nil? options.delete(:format) options.delete(:filename) # create the chart and return it in the format asked for if @@types.include?(m.to_s) chart = new(options.merge!({:type => m})) chart.send(format) elsif m.to_s == 'version' Gchart::VERSION::STRING else "#{m} is not a supported chart format, please use one of the following: #{supported_types}." end end end def initialize(options={}) @type = :line @data = [] @width = 300 @height = 200 @horizontal = false @grouped = false @encoding = 'simple' @max_value = 'auto' # set the options value if definable options.each do |attribute, value| send("#{attribute.to_s}=", value) if self.respond_to?("#{attribute}=") end end def self.supported_types @@types.join(' ') end # Defines the Graph size using the following format: # width X height def size=(size='300x200') @width, @height = size.split("x").map { |dimension| dimension.to_i } end def size "#{@width}x#{@height}" end # Sets the orientation of a bar graph def orientation=(orientation='h') if orientation == 'h' || orientation == 'horizontal' self.horizontal = true elsif orientation == 'v' || orientation == 'vertical' self.horizontal = false end end # Sets the bar graph presentation (stacked or grouped) def stacked=(option=true) @grouped = option ? false : true end 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 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 def self.jstize(string) string.gsub(' ', '+').gsub(/\[|\{|\}|\||\\|\^|\[|\]|\`|\]/) {|c| "%#{c[0].to_s(16).upcase}"} end # load all the custom aliases require 'gchart/aliases' protected # Returns the chart's generated PNG as a blob. (borrowed from John's gchart.rubyforge.org) def fetch open(query_builder) { |io| io.read } end # Writes the chart's generated PNG to a file. (borrowed from John's gchart.rubyforge.org) def write(io_or_file=@@file_name) return io_or_file.write(fetch) if io_or_file.respond_to?(:write) open(io_or_file, "w+") { |io| io.write(fetch) } end # Format def img_tag "" end def url query_builder end def file write end # def jstize(string) Gchart.jstize(string) end private # 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 def set_title title_params = "chtt=#{title}" unless (title_color.nil? && title_size.nil? ) title_params << "&chts=" + (color, size = (@title_color || '454545'), @title_size).compact.join(',') end title_params end def set_size "chs=#{size}" end def set_data data = send("#{@encoding}_encoding", @data) "chd=#{data}" end 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 # set bar, line colors def set_bar_colors @bar_colors = @bar_colors.join(',') if @bar_colors.is_a?(Array) "chco=#{@bar_colors}" end 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 # A chart can have one or many legends. # Gchart.line(:legend => 'label') # or # Gchart.line(:legend => ['first label', 'last label']) def set_legend return set_labels if @type == :pie || @type == :pie_3d if @legend.is_a?(Array) "chdl=#{@legend.map{|label| "#{label}"}.join('|')}" else "chdl=#{@legend}" end end def set_labels if @legend.is_a?(Array) "chl=#{@legend.map{|label| "#{label}"}.join('|')}" else "chl=#{@legend}" end end def set_axis_with_labels @axis_with_labels = @axis_with_labels.join(',') if @axis_with_labels.is_a?(Array) "chxt=#{@axis_with_labels}" end def set_axis_labels labels_arr = [] axis_labels.each_with_index do |labels,index| if labels.is_a?(Array) labels_arr << "#{index}:|#{labels.join('|')}" else labels_arr << "#{index}:|#{labels}" end end "chxl=#{labels_arr.join('|')}" end def set_type case @type when :line "cht=lc" when :line_xy "cht=lxy" when :bar "cht=b" + (horizontal? ? "h" : "v") + (grouped? ? "g" : "s") when :pie_3d "cht=p3" when :pie "cht=p" when :venn "cht=v" when :scatter "cht=s" end end def fill_type(type) case type when 'solid' 's' when 'gradient' 'lg' when 'stripes' 'ls' end end # Wraps a single dataset inside another array to support more datasets def prepare_dataset(ds) ds = [ds] unless ds.first.is_a?(Array) ds end def convert_to_simple_value(number) if number.nil? "_" else value = @@simple_chars[number.to_i] value.nil? ? "_" : value end end # http://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. def simple_encoding(dataset=[]) dataset = prepare_dataset(dataset) @max_value = dataset.map{|ds| ds.max}.max if @max_value == 'auto' if @max_value == false || @max_value == 'false' || @max_value == :false "s:" + dataset.map { |ds| ds.map { |number| convert_to_simple_value(number) }.join }.join(',') else "s:" + dataset.map { |ds| ds.map { |number| convert_to_simple_value( (@@simple_chars.size - 1) * number / @max_value) }.join }.join(',') end end # http://code.google.com/apis/chart/#text # Text encoding has a resolution of 1,000 different values, # using floating point numbers between 0.0 and 100.0. Allowing five pixels per data point, # integers (1.0, 2.0, and so on) are sufficient for line and bar charts up to about 500 pixels. # Include a single decimal place (35.7 for example) if you require higher resolution. # Text encoding is suitable for all other types of chart regardless of size. def text_encoding(dataset=[]) dataset = prepare_dataset(dataset) "t:" + dataset.map{ |ds| ds.join(',') }.join('|') end def convert_to_extended_value(number) if number.nil? '__' else value = @@ext_pairs[number.to_i] value.nil? ? "__" : value end end # http://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. def extended_encoding(dataset=[]) dataset = prepare_dataset(dataset) @max_value = dataset.map{|ds| ds.max}.max if @max_value == 'auto' if @max_value == false || @max_value == 'false' || @max_value == :false "e:" + dataset.map { |ds| ds.map { |number| convert_to_extended_value(number)}.join }.join(',') else "e:" + dataset.map { |ds| ds.map { |number| convert_to_extended_value( (@@ext_pairs.size - 1) * number / @max_value) }.join }.join(',') end end def query_builder query_params = instance_variables.map do |var| case var # 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 '@bg_color' set_colors when '@chart_color' set_colors if @bg_color.nil? when '@data' set_data unless @data == [] when '@bar_colors' set_bar_colors when '@axis_with_labels' set_axis_with_labels when '@axis_labels' set_axis_labels when '@custom' @custom end end.compact jstize(@@url + query_params.join('&')) end end