From 79bbafd93c67f2dc513e1621ea3cf98a8bf86d9b Mon Sep 17 00:00:00 2001 From: Matt Aimonetti Date: Sat, 26 Apr 2008 20:46:36 -0700 Subject: [PATCH] initial import --- .gitignore | 2 + History.txt | 21 + License.txt | 20 + Manifest.txt | 27 + README.txt | 179 ++ Rakefile | 4 + config/hoe.rb | 71 + config/requirements.rb | 16 + lib/gchart.rb | 364 ++++ lib/gchart/aliases.rb | 14 + lib/gchart/version.rb | 9 + script/destroy | 14 + script/generate | 14 + script/txt2html | 74 + setup.rb | 1585 +++++++++++++++++ spec/gchart_spec.rb | 277 +++ spec/spec.opts | 1 + spec/spec_helper.rb | 7 + tasks/deployment.rake | 34 + tasks/environment.rake | 7 + tasks/rspec.rake | 21 + tasks/website.rake | 17 + website/index.html | 601 +++++++ website/index.txt | 417 +++++ .../javascripts/rounded_corners_lite.inc.js | 285 +++ website/stylesheets/screen.css | 138 ++ website/template.rhtml | 53 + 27 files changed, 4272 insertions(+) create mode 100644 .gitignore create mode 100644 History.txt create mode 100644 License.txt create mode 100644 Manifest.txt create mode 100644 README.txt create mode 100644 Rakefile create mode 100644 config/hoe.rb create mode 100644 config/requirements.rb create mode 100644 lib/gchart.rb create mode 100644 lib/gchart/aliases.rb create mode 100644 lib/gchart/version.rb create mode 100755 script/destroy create mode 100755 script/generate create mode 100755 script/txt2html create mode 100644 setup.rb create mode 100644 spec/gchart_spec.rb create mode 100644 spec/spec.opts create mode 100644 spec/spec_helper.rb create mode 100644 tasks/deployment.rake create mode 100644 tasks/environment.rake create mode 100644 tasks/rspec.rake create mode 100644 tasks/website.rake create mode 100644 website/index.html create mode 100644 website/index.txt create mode 100644 website/javascripts/rounded_corners_lite.inc.js create mode 100644 website/stylesheets/screen.css create mode 100644 website/template.rhtml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d22f796 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.DS_Store +log/* \ No newline at end of file diff --git a/History.txt b/History.txt new file mode 100644 index 0000000..906267f --- /dev/null +++ b/History.txt @@ -0,0 +1,21 @@ +== 1.1.0 +* fixed another bug fix related to the uri escaping required to download the file properly. +== 1.0.0 +* fixed the (URI::InvalidURIError) issue +== 0.2.0 +* added export options (file and image tag) +* added support for all arguments to be passed as a string or an array + +== 0.1.0 2007-12-11 +* fixed the axis labels + +== 0.0.3 2007-12-11 +* added :chart_background alias and fixed a bug related to the background colors. + +== 0.0.2 2007-12-11 +* added support for more features and aliases + +== 0.0.1 2007-12-08 + +* 1 major enhancement: + * Initial release diff --git a/License.txt b/License.txt new file mode 100644 index 0000000..8f67a70 --- /dev/null +++ b/License.txt @@ -0,0 +1,20 @@ +Copyright (c) 2007 Matt Aimonetti + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/Manifest.txt b/Manifest.txt new file mode 100644 index 0000000..500a692 --- /dev/null +++ b/Manifest.txt @@ -0,0 +1,27 @@ +History.txt +License.txt +Manifest.txt +README.txt +Rakefile +config/hoe.rb +config/requirements.rb +lib/gchart.rb +lib/gchart/aliases.rb +lib/gchart/version.rb +log/debug.log +script/destroy +script/generate +script/txt2html +setup.rb +spec/gchart_spec.rb +spec/spec.opts +spec/spec_helper.rb +tasks/deployment.rake +tasks/environment.rake +tasks/rspec.rake +tasks/website.rake +website/index.html +website/index.txt +website/javascripts/rounded_corners_lite.inc.js +website/stylesheets/screen.css +website/template.rhtml diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..ca494a3 --- /dev/null +++ b/README.txt @@ -0,0 +1,179 @@ +The goal of this Gem is to make the creation of Google Charts a simple and easy task. + +Gchart.line( :size => '200x300', + :title => "example title", + :bg => 'efefef', + :legend => ['first data set label', 'second data set label'], + :data => [10, 30, 120, 45, 72]) + + + +==Chart Type + +This gem supports the following types of charts: + + * line, + * line_xy + * scatter + * bar + * venn + * pie + * pie_3d + +To create a chart, simply require Gchart and call any of the existing type: + + require 'gchart' + Gchart.pie + + +==Chart Title + + To add a title to a chart pass the title to your chart: + + Gchart.line(:title => 'Sexy Charts!') + +You can also specify the color and/or size + + Gchart.line(:title => 'Sexy Charts!', :title_color => 'FF0000', :title_size => '20') + +==Colors + +Specify a color with at least a 6-letter string of hexadecimal values in the format RRGGBB. For example: + + * FF0000 = red + * 00FF00 = green + * 0000FF = blue + * 000000 = black + * FFFFFF = white + +You can optionally specify transparency by appending a value between 00 and FF where 00 is completely transparent and FF completely opaque. For example: + + * 0000FFFF = solid blue + * 0000FF00 = transparent blue + +If you need to use multiple colors, check the doc. Usually you just need to pass :attribute => 'FF0000,00FF00' + +Some charts have more options than other, make sure to refer to the documentation. + +===Background options: + +If you don't set the background option, your graph will be transparent. + +* You have 3 types of background http://code.google.com/apis/chart/#chart_or_background_fill + +- solid +- gradient +- stripes + +By default, if you set a background color, the fill will be solid: + + Gchart.bar(:bg => 'efefef') + +However you can specify another fill type such as: + + Gchart.line(:bg => {:color => 'efefef', :type => 'gradient'}) + +In the above code, we decided to have a gradient background, however since we only passed one color, the chart will start by the specified color and transition to white. By the default, the gradient angle is 0. Change it as follows: + + Gchart.line(:title =>'bg example', :bg => {:color => 'efefef', :type => 'gradient', :angle => 90}) + +For a more advance use of colors, refer to http://code.google.com/apis/chart/#linear_gradient + + Gchart.line(:bg => {:color => '76A4FB,1,ffffff,0', :type => 'gradient'}) + + +The same way you set the background color, you can also set the graph background: + + Gchart.line(:graph_bg => 'cccccc') + +or both + + Gchart.line(:bg => {:color => '76A4FB,1,ffffff,0', :type => 'gradient'}, :graph_bg => 'cccccc', :title => 'Sexy Chart') + + +Another type of fill is stripes http://code.google.com/apis/chart/#linear_stripes + + Gchart.line(:bg => {:color => 'efefef', :type => 'stripes'}) + +You can customize the amount of stripes, colors and width by changing the color value. + + +== Legend & Labels + +You probably will want to use a legend or labels for your graph. + + Gchart.line(:legend => 'legend label') +or + Gchart.line(:legend => ['legend label 1', 'legend label 2']) + +Will do the trick. You can also use the labels alias (makes more sense when using the pie charts) + + chart = Gchart.pie(:labels => ['label 1', 'label 2']) + +== Multiple axis labels + +Multiple axis labels are available for line charts, bar charts and scatter plots. + +* x = bottom x-axis +* t = top x-axis +* y = left y-axis +* r = right y-axis + + Gchart.line(:label_axis => 'x,y,r') + +To add labels on these axis: + + Gchart.line(:axis_labels => ['Jan|July|Jan|July|Jan', '0|100', 'A|B|C', '2005|2006|2007']) + + +== Data options + +Data are passed using an array or a nested array. + + Gchart.bar(:data => [1,2,4,67,100,41,234]) + + Gchart.bar(:data => [[1,2,4,67,100,41,234],[45,23,67,12,67,300, 250]]) + +By default, the graph is drawn with your max value representing 100% of the height or width of the graph. You can change that my passing the max value. + + Gchart.bar(:data => [1,2,4,67,100,41,234], :max_value => 300) + Gchart.bar(:data => [1,2,4,67,100,41,234], :max_value => 'auto') + +or if you want to use the real values from your dataset: + + Gchart.bar(:data => [1,2,4,67,100,41,234], :max_value => false) + + +You can also define a different encoding to add more granularity: + + Gchart.bar(:data => [1,2,4,67,100,41,234], :encoding => 'simple') + Gchart.bar(:data => [1,2,4,67,100,41,234], :encoding => 'extended') + Gchart.bar(:data => [1,2,4,67,100,41,234], :encoding => 'text') + + +==Pies: + +you have 2 type of pies: + - Gchart.pie() the standard 2D pie + _ Gchart.pie_3d() the fancy 3D pie + +To set labels, you can use one of these two options: + + @legend = ['Matt_fu', 'Rob_fu'] + Gchart.pie_3d(:title => @title, :labels => @legend, :data => @data, :size => '400x200') + Gchart.pie_3d(:title => @title, :legend => @legend, :data => @data, :size => '400x200') + + + +=== try yourself + +Gchart.bar( :data => [[1,2,4,67,100,41,234],[45,23,67,12,67,300, 250]], + :title => 'SDRuby Fu level', + :legend => ['matt','patrick'], + :bg => {:color => '76A4FB', :type => 'gradient'}, + :bar_colors => 'ff0000,00ff00') + + "http://chart.apis.google.com/chart?chs=300x200&chdl=matt|patrick&chd=s:AAANUIv,JENCN9y&chtt=SDRuby+Fu+level&chf=bg,lg,0,76A4FB,0,ffffff,1&cht=bvs&chco=ff0000,00ff00" + +Gchart.pie(:data => [20,10,15,5,50], :title => 'SDRuby Fu level', :size => '400x200', :labels => ['matt', 'rob', 'patrick', 'ryan', 'jordan']) +http://chart.apis.google.com/chart?cht=p&chs=400x200&chd=s:YMSG9&chtt=SDRuby+Fu+level&chl=matt|rob|patrick|ryan|jordan diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..e469154 --- /dev/null +++ b/Rakefile @@ -0,0 +1,4 @@ +require 'config/requirements' +require 'config/hoe' # setup Hoe + all gem configuration + +Dir['tasks/**/*.rake'].each { |rake| load rake } \ No newline at end of file diff --git a/config/hoe.rb b/config/hoe.rb new file mode 100644 index 0000000..ae0b1da --- /dev/null +++ b/config/hoe.rb @@ -0,0 +1,71 @@ +require 'gchart/version' + +AUTHOR = 'Matt Aimonetti' # can also be an array of Authors +EMAIL = "mattaimonetti@gmail.com" +DESCRIPTION = "description of gem" +GEM_NAME = 'googlecharts' # what ppl will type to install your gem +RUBYFORGE_PROJECT = 'googlecharts' # The unix name for your project +HOMEPATH = "http://#{RUBYFORGE_PROJECT}.rubyforge.org" +DOWNLOAD_PATH = "http://rubyforge.org/projects/#{RUBYFORGE_PROJECT}" + +@config_file = "~/.rubyforge/user-config.yml" +@config = nil +RUBYFORGE_USERNAME = "matt_a" +def rubyforge_username + unless @config + begin + @config = YAML.load(File.read(File.expand_path(@config_file))) + rescue + puts <<-EOS +ERROR: No rubyforge config file found: #{@config_file} +Run 'rubyforge setup' to prepare your env for access to Rubyforge + - See http://newgem.rubyforge.org/rubyforge.html for more details + EOS + exit + end + end + RUBYFORGE_USERNAME.replace @config["username"] +end + + +REV = nil +# UNCOMMENT IF REQUIRED: +# REV = `svn info`.each {|line| if line =~ /^Revision:/ then k,v = line.split(': '); break v.chomp; else next; end} rescue nil +VERS = GchartInfo::VERSION::STRING + (REV ? ".#{REV}" : "") +RDOC_OPTS = ['--quiet', '--title', 'gchart documentation', + "--opname", "index.html", + "--line-numbers", + "--main", "README", + "--inline-source"] + +class Hoe + def extra_deps + @extra_deps.reject! { |x| Array(x).first == 'hoe' } + @extra_deps + end +end + +# Generate all the Rake tasks +# Run 'rake -T' to see list of generated tasks (from gem root directory) +hoe = Hoe.new(GEM_NAME, VERS) do |p| + p.author = AUTHOR + p.description = DESCRIPTION + p.email = EMAIL + p.summary = DESCRIPTION + p.url = HOMEPATH + p.rubyforge_name = RUBYFORGE_PROJECT if RUBYFORGE_PROJECT + p.test_globs = ["test/**/test_*.rb"] + p.clean_globs |= ['**/.*.sw?', '*.gem', '.config', '**/.DS_Store'] #An array of file patterns to delete on clean. + + # == Optional + p.changes = p.paragraphs_of("History.txt", 0..1).join("\n\n") + #p.extra_deps = [] # An array of rubygem dependencies [name, version], e.g. [ ['active_support', '>= 1.3.1'] ] + + #p.spec_extras = {} # A hash of extra values to set in the gemspec. + +end + +CHANGES = hoe.paragraphs_of('History.txt', 0..1).join("\\n\\n") +PATH = (RUBYFORGE_PROJECT == GEM_NAME) ? RUBYFORGE_PROJECT : "#{RUBYFORGE_PROJECT}/#{GEM_NAME}" +hoe.remote_rdoc_dir = File.join(PATH.gsub(/^#{RUBYFORGE_PROJECT}\/?/,''), 'rdoc') +hoe.rsync_args = '-av --delete --ignore-errors' \ No newline at end of file diff --git a/config/requirements.rb b/config/requirements.rb new file mode 100644 index 0000000..bf1b541 --- /dev/null +++ b/config/requirements.rb @@ -0,0 +1,16 @@ +require 'fileutils' +include FileUtils + +require 'rubygems' +%w[rake hoe newgem rubigen].each do |req_gem| + begin + require req_gem + rescue LoadError + puts "This Rakefile could use '#{req_gem}' RubyGem." + puts "Installation: gem install #{req_gem} -y" + end +end + +$:.unshift(File.join(File.dirname(__FILE__), %w[.. lib])) + +require 'gchart' \ No newline at end of file diff --git a/lib/gchart.rb b/lib/gchart.rb new file mode 100644 index 0000000..f26b8e8 --- /dev/null +++ b/lib/gchart.rb @@ -0,0 +1,364 @@ +$:.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 \ No newline at end of file diff --git a/lib/gchart/aliases.rb b/lib/gchart/aliases.rb new file mode 100644 index 0000000..f7071fd --- /dev/null +++ b/lib/gchart/aliases.rb @@ -0,0 +1,14 @@ +class Gchart + + alias_method :background=, :bg= + alias_method :chart_bg=, :graph_bg= + alias_method :chart_color=, :graph_bg= + alias_method :chart_background=, :graph_bg= + alias_method :bar_color=, :bar_colors= + alias_method :line_colors=, :bar_colors= + alias_method :line_color=, :bar_colors= + alias_method :labels=, :legend= + alias_method :horizontal?, :horizontal + alias_method :grouped?, :grouped + +end \ No newline at end of file diff --git a/lib/gchart/version.rb b/lib/gchart/version.rb new file mode 100644 index 0000000..06be613 --- /dev/null +++ b/lib/gchart/version.rb @@ -0,0 +1,9 @@ +module GchartInfo #:nodoc: + module VERSION #:nodoc: + MAJOR = 1 + MINOR = 1 + TINY = 0 + + STRING = [MAJOR, MINOR, TINY].join('.') + end +end diff --git a/script/destroy b/script/destroy new file mode 100755 index 0000000..5fa7e10 --- /dev/null +++ b/script/destroy @@ -0,0 +1,14 @@ +#!/usr/bin/env ruby +APP_ROOT = File.join(File.dirname(__FILE__), '..') + +begin + require 'rubigen' +rescue LoadError + require 'rubygems' + require 'rubigen' +end +require 'rubigen/scripts/destroy' + +ARGV.shift if ['--help', '-h'].include?(ARGV[0]) +RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit] +RubiGen::Scripts::Destroy.new.run(ARGV) diff --git a/script/generate b/script/generate new file mode 100755 index 0000000..230a186 --- /dev/null +++ b/script/generate @@ -0,0 +1,14 @@ +#!/usr/bin/env ruby +APP_ROOT = File.join(File.dirname(__FILE__), '..') + +begin + require 'rubigen' +rescue LoadError + require 'rubygems' + require 'rubigen' +end +require 'rubigen/scripts/generate' + +ARGV.shift if ['--help', '-h'].include?(ARGV[0]) +RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit] +RubiGen::Scripts::Generate.new.run(ARGV) diff --git a/script/txt2html b/script/txt2html new file mode 100755 index 0000000..87198bc --- /dev/null +++ b/script/txt2html @@ -0,0 +1,74 @@ +#!/usr/bin/env ruby + +require 'rubygems' +begin + require 'newgem' +rescue LoadError + puts "\n\nGenerating the website requires the newgem RubyGem" + puts "Install: gem install newgem\n\n" + exit(1) +end +require 'redcloth' +require 'syntax/convertors/html' +require 'erb' +require File.dirname(__FILE__) + '/../lib/gchart' + +version = Gchart.version +download = 'http://rubyforge.org/projects/googlecharts' + +class Fixnum + def ordinal + # teens + return 'th' if (10..19).include?(self % 100) + # others + case self % 10 + when 1: return 'st' + when 2: return 'nd' + when 3: return 'rd' + else return 'th' + end + end +end + +class Time + def pretty + return "#{mday}#{mday.ordinal} #{strftime('%B')} #{year}" + end +end + +def convert_syntax(syntax, source) + return Syntax::Convertors::HTML.for_syntax(syntax).convert(source).gsub(%r!^
|
$!,'') +end + +if ARGV.length >= 1 + src, template = ARGV + template ||= File.join(File.dirname(__FILE__), '/../website/template.rhtml') + +else + puts("Usage: #{File.split($0).last} source.txt [template.rhtml] > output.html") + exit! +end + +template = ERB.new(File.open(template).read) + +title = nil +body = nil +File.open(src) do |fsrc| + title_text = fsrc.readline + body_text = fsrc.read + syntax_items = [] + body_text.gsub!(%r!<(pre|code)[^>]*?syntax=['"]([^'"]+)[^>]*>(.*?)!m){ + ident = syntax_items.length + element, syntax, source = $1, $2, $3 + syntax_items << "<#{element} class='syntax'>#{convert_syntax(syntax, source)}" + "syntax-temp-#{ident}" + } + title = RedCloth.new(title_text).to_html.gsub(%r!<.*?>!,'').strip + body = RedCloth.new(body_text).to_html + body.gsub!(%r!(?:
)?syntax-temp-(\d+)(?:
)?!){ syntax_items[$1.to_i] } +end +stat = File.stat(src) +created = stat.ctime +modified = stat.mtime + +$stdout << template.result(binding) diff --git a/setup.rb b/setup.rb new file mode 100644 index 0000000..424a5f3 --- /dev/null +++ b/setup.rb @@ -0,0 +1,1585 @@ +# +# setup.rb +# +# Copyright (c) 2000-2005 Minero Aoki +# +# This program is free software. +# You can distribute/modify this program under the terms of +# the GNU LGPL, Lesser General Public License version 2.1. +# + +unless Enumerable.method_defined?(:map) # Ruby 1.4.6 + module Enumerable + alias map collect + end +end + +unless File.respond_to?(:read) # Ruby 1.6 + def File.read(fname) + open(fname) {|f| + return f.read + } + end +end + +unless Errno.const_defined?(:ENOTEMPTY) # Windows? + module Errno + class ENOTEMPTY + # We do not raise this exception, implementation is not needed. + end + end +end + +def File.binread(fname) + open(fname, 'rb') {|f| + return f.read + } +end + +# for corrupted Windows' stat(2) +def File.dir?(path) + File.directory?((path[-1,1] == '/') ? path : path + '/') +end + + +class ConfigTable + + include Enumerable + + def initialize(rbconfig) + @rbconfig = rbconfig + @items = [] + @table = {} + # options + @install_prefix = nil + @config_opt = nil + @verbose = true + @no_harm = false + end + + attr_accessor :install_prefix + attr_accessor :config_opt + + attr_writer :verbose + + def verbose? + @verbose + end + + attr_writer :no_harm + + def no_harm? + @no_harm + end + + def [](key) + lookup(key).resolve(self) + end + + def []=(key, val) + lookup(key).set val + end + + def names + @items.map {|i| i.name } + end + + def each(&block) + @items.each(&block) + end + + def key?(name) + @table.key?(name) + end + + def lookup(name) + @table[name] or setup_rb_error "no such config item: #{name}" + end + + def add(item) + @items.push item + @table[item.name] = item + end + + def remove(name) + item = lookup(name) + @items.delete_if {|i| i.name == name } + @table.delete_if {|name, i| i.name == name } + item + end + + def load_script(path, inst = nil) + if File.file?(path) + MetaConfigEnvironment.new(self, inst).instance_eval File.read(path), path + end + end + + def savefile + '.config' + end + + def load_savefile + begin + File.foreach(savefile()) do |line| + k, v = *line.split(/=/, 2) + self[k] = v.strip + end + rescue Errno::ENOENT + setup_rb_error $!.message + "\n#{File.basename($0)} config first" + end + end + + def save + @items.each {|i| i.value } + File.open(savefile(), 'w') {|f| + @items.each do |i| + f.printf "%s=%s\n", i.name, i.value if i.value? and i.value + end + } + end + + def load_standard_entries + standard_entries(@rbconfig).each do |ent| + add ent + end + end + + def standard_entries(rbconfig) + c = rbconfig + + rubypath = File.join(c['bindir'], c['ruby_install_name'] + c['EXEEXT']) + + major = c['MAJOR'].to_i + minor = c['MINOR'].to_i + teeny = c['TEENY'].to_i + version = "#{major}.#{minor}" + + # ruby ver. >= 1.4.4? + newpath_p = ((major >= 2) or + ((major == 1) and + ((minor >= 5) or + ((minor == 4) and (teeny >= 4))))) + + if c['rubylibdir'] + # V > 1.6.3 + libruby = "#{c['prefix']}/lib/ruby" + librubyver = c['rubylibdir'] + librubyverarch = c['archdir'] + siteruby = c['sitedir'] + siterubyver = c['sitelibdir'] + siterubyverarch = c['sitearchdir'] + elsif newpath_p + # 1.4.4 <= V <= 1.6.3 + libruby = "#{c['prefix']}/lib/ruby" + librubyver = "#{c['prefix']}/lib/ruby/#{version}" + librubyverarch = "#{c['prefix']}/lib/ruby/#{version}/#{c['arch']}" + siteruby = c['sitedir'] + siterubyver = "$siteruby/#{version}" + siterubyverarch = "$siterubyver/#{c['arch']}" + else + # V < 1.4.4 + libruby = "#{c['prefix']}/lib/ruby" + librubyver = "#{c['prefix']}/lib/ruby/#{version}" + librubyverarch = "#{c['prefix']}/lib/ruby/#{version}/#{c['arch']}" + siteruby = "#{c['prefix']}/lib/ruby/#{version}/site_ruby" + siterubyver = siteruby + siterubyverarch = "$siterubyver/#{c['arch']}" + end + parameterize = lambda {|path| + path.sub(/\A#{Regexp.quote(c['prefix'])}/, '$prefix') + } + + if arg = c['configure_args'].split.detect {|arg| /--with-make-prog=/ =~ arg } + makeprog = arg.sub(/'/, '').split(/=/, 2)[1] + else + makeprog = 'make' + end + + [ + ExecItem.new('installdirs', 'std/site/home', + 'std: install under libruby; site: install under site_ruby; home: install under $HOME')\ + {|val, table| + case val + when 'std' + table['rbdir'] = '$librubyver' + table['sodir'] = '$librubyverarch' + when 'site' + table['rbdir'] = '$siterubyver' + table['sodir'] = '$siterubyverarch' + when 'home' + setup_rb_error '$HOME was not set' unless ENV['HOME'] + table['prefix'] = ENV['HOME'] + table['rbdir'] = '$libdir/ruby' + table['sodir'] = '$libdir/ruby' + end + }, + PathItem.new('prefix', 'path', c['prefix'], + 'path prefix of target environment'), + PathItem.new('bindir', 'path', parameterize.call(c['bindir']), + 'the directory for commands'), + PathItem.new('libdir', 'path', parameterize.call(c['libdir']), + 'the directory for libraries'), + PathItem.new('datadir', 'path', parameterize.call(c['datadir']), + 'the directory for shared data'), + PathItem.new('mandir', 'path', parameterize.call(c['mandir']), + 'the directory for man pages'), + PathItem.new('sysconfdir', 'path', parameterize.call(c['sysconfdir']), + 'the directory for system configuration files'), + PathItem.new('localstatedir', 'path', parameterize.call(c['localstatedir']), + 'the directory for local state data'), + PathItem.new('libruby', 'path', libruby, + 'the directory for ruby libraries'), + PathItem.new('librubyver', 'path', librubyver, + 'the directory for standard ruby libraries'), + PathItem.new('librubyverarch', 'path', librubyverarch, + 'the directory for standard ruby extensions'), + PathItem.new('siteruby', 'path', siteruby, + 'the directory for version-independent aux ruby libraries'), + PathItem.new('siterubyver', 'path', siterubyver, + 'the directory for aux ruby libraries'), + PathItem.new('siterubyverarch', 'path', siterubyverarch, + 'the directory for aux ruby binaries'), + PathItem.new('rbdir', 'path', '$siterubyver', + 'the directory for ruby scripts'), + PathItem.new('sodir', 'path', '$siterubyverarch', + 'the directory for ruby extentions'), + PathItem.new('rubypath', 'path', rubypath, + 'the path to set to #! line'), + ProgramItem.new('rubyprog', 'name', rubypath, + 'the ruby program using for installation'), + ProgramItem.new('makeprog', 'name', makeprog, + 'the make program to compile ruby extentions'), + SelectItem.new('shebang', 'all/ruby/never', 'ruby', + 'shebang line (#!) editing mode'), + BoolItem.new('without-ext', 'yes/no', 'no', + 'does not compile/install ruby extentions') + ] + end + private :standard_entries + + def load_multipackage_entries + multipackage_entries().each do |ent| + add ent + end + end + + def multipackage_entries + [ + PackageSelectionItem.new('with', 'name,name...', '', 'ALL', + 'package names that you want to install'), + PackageSelectionItem.new('without', 'name,name...', '', 'NONE', + 'package names that you do not want to install') + ] + end + private :multipackage_entries + + ALIASES = { + 'std-ruby' => 'librubyver', + 'stdruby' => 'librubyver', + 'rubylibdir' => 'librubyver', + 'archdir' => 'librubyverarch', + 'site-ruby-common' => 'siteruby', # For backward compatibility + 'site-ruby' => 'siterubyver', # For backward compatibility + 'bin-dir' => 'bindir', + 'bin-dir' => 'bindir', + 'rb-dir' => 'rbdir', + 'so-dir' => 'sodir', + 'data-dir' => 'datadir', + 'ruby-path' => 'rubypath', + 'ruby-prog' => 'rubyprog', + 'ruby' => 'rubyprog', + 'make-prog' => 'makeprog', + 'make' => 'makeprog' + } + + def fixup + ALIASES.each do |ali, name| + @table[ali] = @table[name] + end + @items.freeze + @table.freeze + @options_re = /\A--(#{@table.keys.join('|')})(?:=(.*))?\z/ + end + + def parse_opt(opt) + m = @options_re.match(opt) or setup_rb_error "config: unknown option #{opt}" + m.to_a[1,2] + end + + def dllext + @rbconfig['DLEXT'] + end + + def value_config?(name) + lookup(name).value? + end + + class Item + def initialize(name, template, default, desc) + @name = name.freeze + @template = template + @value = default + @default = default + @description = desc + end + + attr_reader :name + attr_reader :description + + attr_accessor :default + alias help_default default + + def help_opt + "--#{@name}=#{@template}" + end + + def value? + true + end + + def value + @value + end + + def resolve(table) + @value.gsub(%r<\$([^/]+)>) { table[$1] } + end + + def set(val) + @value = check(val) + end + + private + + def check(val) + setup_rb_error "config: --#{name} requires argument" unless val + val + end + end + + class BoolItem < Item + def config_type + 'bool' + end + + def help_opt + "--#{@name}" + end + + private + + def check(val) + return 'yes' unless val + case val + when /\Ay(es)?\z/i, /\At(rue)?\z/i then 'yes' + when /\An(o)?\z/i, /\Af(alse)\z/i then 'no' + else + setup_rb_error "config: --#{@name} accepts only yes/no for argument" + end + end + end + + class PathItem < Item + def config_type + 'path' + end + + private + + def check(path) + setup_rb_error "config: --#{@name} requires argument" unless path + path[0,1] == '$' ? path : File.expand_path(path) + end + end + + class ProgramItem < Item + def config_type + 'program' + end + end + + class SelectItem < Item + def initialize(name, selection, default, desc) + super + @ok = selection.split('/') + end + + def config_type + 'select' + end + + private + + def check(val) + unless @ok.include?(val.strip) + setup_rb_error "config: use --#{@name}=#{@template} (#{val})" + end + val.strip + end + end + + class ExecItem < Item + def initialize(name, selection, desc, &block) + super name, selection, nil, desc + @ok = selection.split('/') + @action = block + end + + def config_type + 'exec' + end + + def value? + false + end + + def resolve(table) + setup_rb_error "$#{name()} wrongly used as option value" + end + + undef set + + def evaluate(val, table) + v = val.strip.downcase + unless @ok.include?(v) + setup_rb_error "invalid option --#{@name}=#{val} (use #{@template})" + end + @action.call v, table + end + end + + class PackageSelectionItem < Item + def initialize(name, template, default, help_default, desc) + super name, template, default, desc + @help_default = help_default + end + + attr_reader :help_default + + def config_type + 'package' + end + + private + + def check(val) + unless File.dir?("packages/#{val}") + setup_rb_error "config: no such package: #{val}" + end + val + end + end + + class MetaConfigEnvironment + def initialize(config, installer) + @config = config + @installer = installer + end + + def config_names + @config.names + end + + def config?(name) + @config.key?(name) + end + + def bool_config?(name) + @config.lookup(name).config_type == 'bool' + end + + def path_config?(name) + @config.lookup(name).config_type == 'path' + end + + def value_config?(name) + @config.lookup(name).config_type != 'exec' + end + + def add_config(item) + @config.add item + end + + def add_bool_config(name, default, desc) + @config.add BoolItem.new(name, 'yes/no', default ? 'yes' : 'no', desc) + end + + def add_path_config(name, default, desc) + @config.add PathItem.new(name, 'path', default, desc) + end + + def set_config_default(name, default) + @config.lookup(name).default = default + end + + def remove_config(name) + @config.remove(name) + end + + # For only multipackage + def packages + raise '[setup.rb fatal] multi-package metaconfig API packages() called for single-package; contact application package vendor' unless @installer + @installer.packages + end + + # For only multipackage + def declare_packages(list) + raise '[setup.rb fatal] multi-package metaconfig API declare_packages() called for single-package; contact application package vendor' unless @installer + @installer.packages = list + end + end + +end # class ConfigTable + + +# This module requires: #verbose?, #no_harm? +module FileOperations + + def mkdir_p(dirname, prefix = nil) + dirname = prefix + File.expand_path(dirname) if prefix + $stderr.puts "mkdir -p #{dirname}" if verbose? + return if no_harm? + + # Does not check '/', it's too abnormal. + dirs = File.expand_path(dirname).split(%r<(?=/)>) + if /\A[a-z]:\z/i =~ dirs[0] + disk = dirs.shift + dirs[0] = disk + dirs[0] + end + dirs.each_index do |idx| + path = dirs[0..idx].join('') + Dir.mkdir path unless File.dir?(path) + end + end + + def rm_f(path) + $stderr.puts "rm -f #{path}" if verbose? + return if no_harm? + force_remove_file path + end + + def rm_rf(path) + $stderr.puts "rm -rf #{path}" if verbose? + return if no_harm? + remove_tree path + end + + def remove_tree(path) + if File.symlink?(path) + remove_file path + elsif File.dir?(path) + remove_tree0 path + else + force_remove_file path + end + end + + def remove_tree0(path) + Dir.foreach(path) do |ent| + next if ent == '.' + next if ent == '..' + entpath = "#{path}/#{ent}" + if File.symlink?(entpath) + remove_file entpath + elsif File.dir?(entpath) + remove_tree0 entpath + else + force_remove_file entpath + end + end + begin + Dir.rmdir path + rescue Errno::ENOTEMPTY + # directory may not be empty + end + end + + def move_file(src, dest) + force_remove_file dest + begin + File.rename src, dest + rescue + File.open(dest, 'wb') {|f| + f.write File.binread(src) + } + File.chmod File.stat(src).mode, dest + File.unlink src + end + end + + def force_remove_file(path) + begin + remove_file path + rescue + end + end + + def remove_file(path) + File.chmod 0777, path + File.unlink path + end + + def install(from, dest, mode, prefix = nil) + $stderr.puts "install #{from} #{dest}" if verbose? + return if no_harm? + + realdest = prefix ? prefix + File.expand_path(dest) : dest + realdest = File.join(realdest, File.basename(from)) if File.dir?(realdest) + str = File.binread(from) + if diff?(str, realdest) + verbose_off { + rm_f realdest if File.exist?(realdest) + } + File.open(realdest, 'wb') {|f| + f.write str + } + File.chmod mode, realdest + + File.open("#{objdir_root()}/InstalledFiles", 'a') {|f| + if prefix + f.puts realdest.sub(prefix, '') + else + f.puts realdest + end + } + end + end + + def diff?(new_content, path) + return true unless File.exist?(path) + new_content != File.binread(path) + end + + def command(*args) + $stderr.puts args.join(' ') if verbose? + system(*args) or raise RuntimeError, + "system(#{args.map{|a| a.inspect }.join(' ')}) failed" + end + + def ruby(*args) + command config('rubyprog'), *args + end + + def make(task = nil) + command(*[config('makeprog'), task].compact) + end + + def extdir?(dir) + File.exist?("#{dir}/MANIFEST") or File.exist?("#{dir}/extconf.rb") + end + + def files_of(dir) + Dir.open(dir) {|d| + return d.select {|ent| File.file?("#{dir}/#{ent}") } + } + end + + DIR_REJECT = %w( . .. CVS SCCS RCS CVS.adm .svn ) + + def directories_of(dir) + Dir.open(dir) {|d| + return d.select {|ent| File.dir?("#{dir}/#{ent}") } - DIR_REJECT + } + end + +end + + +# This module requires: #srcdir_root, #objdir_root, #relpath +module HookScriptAPI + + def get_config(key) + @config[key] + end + + alias config get_config + + # obsolete: use metaconfig to change configuration + def set_config(key, val) + @config[key] = val + end + + # + # srcdir/objdir (works only in the package directory) + # + + def curr_srcdir + "#{srcdir_root()}/#{relpath()}" + end + + def curr_objdir + "#{objdir_root()}/#{relpath()}" + end + + def srcfile(path) + "#{curr_srcdir()}/#{path}" + end + + def srcexist?(path) + File.exist?(srcfile(path)) + end + + def srcdirectory?(path) + File.dir?(srcfile(path)) + end + + def srcfile?(path) + File.file?(srcfile(path)) + end + + def srcentries(path = '.') + Dir.open("#{curr_srcdir()}/#{path}") {|d| + return d.to_a - %w(. ..) + } + end + + def srcfiles(path = '.') + srcentries(path).select {|fname| + File.file?(File.join(curr_srcdir(), path, fname)) + } + end + + def srcdirectories(path = '.') + srcentries(path).select {|fname| + File.dir?(File.join(curr_srcdir(), path, fname)) + } + end + +end + + +class ToplevelInstaller + + Version = '3.4.1' + Copyright = 'Copyright (c) 2000-2005 Minero Aoki' + + TASKS = [ + [ 'all', 'do config, setup, then install' ], + [ 'config', 'saves your configurations' ], + [ 'show', 'shows current configuration' ], + [ 'setup', 'compiles ruby extentions and others' ], + [ 'install', 'installs files' ], + [ 'test', 'run all tests in test/' ], + [ 'clean', "does `make clean' for each extention" ], + [ 'distclean',"does `make distclean' for each extention" ] + ] + + def ToplevelInstaller.invoke + config = ConfigTable.new(load_rbconfig()) + config.load_standard_entries + config.load_multipackage_entries if multipackage? + config.fixup + klass = (multipackage?() ? ToplevelInstallerMulti : ToplevelInstaller) + klass.new(File.dirname($0), config).invoke + end + + def ToplevelInstaller.multipackage? + File.dir?(File.dirname($0) + '/packages') + end + + def ToplevelInstaller.load_rbconfig + if arg = ARGV.detect {|arg| /\A--rbconfig=/ =~ arg } + ARGV.delete(arg) + load File.expand_path(arg.split(/=/, 2)[1]) + $".push 'rbconfig.rb' + else + require 'rbconfig' + end + ::Config::CONFIG + end + + def initialize(ardir_root, config) + @ardir = File.expand_path(ardir_root) + @config = config + # cache + @valid_task_re = nil + end + + def config(key) + @config[key] + end + + def inspect + "#<#{self.class} #{__id__()}>" + end + + def invoke + run_metaconfigs + case task = parsearg_global() + when nil, 'all' + parsearg_config + init_installers + exec_config + exec_setup + exec_install + else + case task + when 'config', 'test' + ; + when 'clean', 'distclean' + @config.load_savefile if File.exist?(@config.savefile) + else + @config.load_savefile + end + __send__ "parsearg_#{task}" + init_installers + __send__ "exec_#{task}" + end + end + + def run_metaconfigs + @config.load_script "#{@ardir}/metaconfig" + end + + def init_installers + @installer = Installer.new(@config, @ardir, File.expand_path('.')) + end + + # + # Hook Script API bases + # + + def srcdir_root + @ardir + end + + def objdir_root + '.' + end + + def relpath + '.' + end + + # + # Option Parsing + # + + def parsearg_global + while arg = ARGV.shift + case arg + when /\A\w+\z/ + setup_rb_error "invalid task: #{arg}" unless valid_task?(arg) + return arg + when '-q', '--quiet' + @config.verbose = false + when '--verbose' + @config.verbose = true + when '--help' + print_usage $stdout + exit 0 + when '--version' + puts "#{File.basename($0)} version #{Version}" + exit 0 + when '--copyright' + puts Copyright + exit 0 + else + setup_rb_error "unknown global option '#{arg}'" + end + end + nil + end + + def valid_task?(t) + valid_task_re() =~ t + end + + def valid_task_re + @valid_task_re ||= /\A(?:#{TASKS.map {|task,desc| task }.join('|')})\z/ + end + + def parsearg_no_options + unless ARGV.empty? + task = caller(0).first.slice(%r<`parsearg_(\w+)'>, 1) + setup_rb_error "#{task}: unknown options: #{ARGV.join(' ')}" + end + end + + alias parsearg_show parsearg_no_options + alias parsearg_setup parsearg_no_options + alias parsearg_test parsearg_no_options + alias parsearg_clean parsearg_no_options + alias parsearg_distclean parsearg_no_options + + def parsearg_config + evalopt = [] + set = [] + @config.config_opt = [] + while i = ARGV.shift + if /\A--?\z/ =~ i + @config.config_opt = ARGV.dup + break + end + name, value = *@config.parse_opt(i) + if @config.value_config?(name) + @config[name] = value + else + evalopt.push [name, value] + end + set.push name + end + evalopt.each do |name, value| + @config.lookup(name).evaluate value, @config + end + # Check if configuration is valid + set.each do |n| + @config[n] if @config.value_config?(n) + end + end + + def parsearg_install + @config.no_harm = false + @config.install_prefix = '' + while a = ARGV.shift + case a + when '--no-harm' + @config.no_harm = true + when /\A--prefix=/ + path = a.split(/=/, 2)[1] + path = File.expand_path(path) unless path[0,1] == '/' + @config.install_prefix = path + else + setup_rb_error "install: unknown option #{a}" + end + end + end + + def print_usage(out) + out.puts 'Typical Installation Procedure:' + out.puts " $ ruby #{File.basename $0} config" + out.puts " $ ruby #{File.basename $0} setup" + out.puts " # ruby #{File.basename $0} install (may require root privilege)" + out.puts + out.puts 'Detailed Usage:' + out.puts " ruby #{File.basename $0} " + out.puts " ruby #{File.basename $0} [] []" + + fmt = " %-24s %s\n" + out.puts + out.puts 'Global options:' + out.printf fmt, '-q,--quiet', 'suppress message outputs' + out.printf fmt, ' --verbose', 'output messages verbosely' + out.printf fmt, ' --help', 'print this message' + out.printf fmt, ' --version', 'print version and quit' + out.printf fmt, ' --copyright', 'print copyright and quit' + out.puts + out.puts 'Tasks:' + TASKS.each do |name, desc| + out.printf fmt, name, desc + end + + fmt = " %-24s %s [%s]\n" + out.puts + out.puts 'Options for CONFIG or ALL:' + @config.each do |item| + out.printf fmt, item.help_opt, item.description, item.help_default + end + out.printf fmt, '--rbconfig=path', 'rbconfig.rb to load',"running ruby's" + out.puts + out.puts 'Options for INSTALL:' + out.printf fmt, '--no-harm', 'only display what to do if given', 'off' + out.printf fmt, '--prefix=path', 'install path prefix', '' + out.puts + end + + # + # Task Handlers + # + + def exec_config + @installer.exec_config + @config.save # must be final + end + + def exec_setup + @installer.exec_setup + end + + def exec_install + @installer.exec_install + end + + def exec_test + @installer.exec_test + end + + def exec_show + @config.each do |i| + printf "%-20s %s\n", i.name, i.value if i.value? + end + end + + def exec_clean + @installer.exec_clean + end + + def exec_distclean + @installer.exec_distclean + end + +end # class ToplevelInstaller + + +class ToplevelInstallerMulti < ToplevelInstaller + + include FileOperations + + def initialize(ardir_root, config) + super + @packages = directories_of("#{@ardir}/packages") + raise 'no package exists' if @packages.empty? + @root_installer = Installer.new(@config, @ardir, File.expand_path('.')) + end + + def run_metaconfigs + @config.load_script "#{@ardir}/metaconfig", self + @packages.each do |name| + @config.load_script "#{@ardir}/packages/#{name}/metaconfig" + end + end + + attr_reader :packages + + def packages=(list) + raise 'package list is empty' if list.empty? + list.each do |name| + raise "directory packages/#{name} does not exist"\ + unless File.dir?("#{@ardir}/packages/#{name}") + end + @packages = list + end + + def init_installers + @installers = {} + @packages.each do |pack| + @installers[pack] = Installer.new(@config, + "#{@ardir}/packages/#{pack}", + "packages/#{pack}") + end + with = extract_selection(config('with')) + without = extract_selection(config('without')) + @selected = @installers.keys.select {|name| + (with.empty? or with.include?(name)) \ + and not without.include?(name) + } + end + + def extract_selection(list) + a = list.split(/,/) + a.each do |name| + setup_rb_error "no such package: #{name}" unless @installers.key?(name) + end + a + end + + def print_usage(f) + super + f.puts 'Inluded packages:' + f.puts ' ' + @packages.sort.join(' ') + f.puts + end + + # + # Task Handlers + # + + def exec_config + run_hook 'pre-config' + each_selected_installers {|inst| inst.exec_config } + run_hook 'post-config' + @config.save # must be final + end + + def exec_setup + run_hook 'pre-setup' + each_selected_installers {|inst| inst.exec_setup } + run_hook 'post-setup' + end + + def exec_install + run_hook 'pre-install' + each_selected_installers {|inst| inst.exec_install } + run_hook 'post-install' + end + + def exec_test + run_hook 'pre-test' + each_selected_installers {|inst| inst.exec_test } + run_hook 'post-test' + end + + def exec_clean + rm_f @config.savefile + run_hook 'pre-clean' + each_selected_installers {|inst| inst.exec_clean } + run_hook 'post-clean' + end + + def exec_distclean + rm_f @config.savefile + run_hook 'pre-distclean' + each_selected_installers {|inst| inst.exec_distclean } + run_hook 'post-distclean' + end + + # + # lib + # + + def each_selected_installers + Dir.mkdir 'packages' unless File.dir?('packages') + @selected.each do |pack| + $stderr.puts "Processing the package `#{pack}' ..." if verbose? + Dir.mkdir "packages/#{pack}" unless File.dir?("packages/#{pack}") + Dir.chdir "packages/#{pack}" + yield @installers[pack] + Dir.chdir '../..' + end + end + + def run_hook(id) + @root_installer.run_hook id + end + + # module FileOperations requires this + def verbose? + @config.verbose? + end + + # module FileOperations requires this + def no_harm? + @config.no_harm? + end + +end # class ToplevelInstallerMulti + + +class Installer + + FILETYPES = %w( bin lib ext data conf man ) + + include FileOperations + include HookScriptAPI + + def initialize(config, srcroot, objroot) + @config = config + @srcdir = File.expand_path(srcroot) + @objdir = File.expand_path(objroot) + @currdir = '.' + end + + def inspect + "#<#{self.class} #{File.basename(@srcdir)}>" + end + + def noop(rel) + end + + # + # Hook Script API base methods + # + + def srcdir_root + @srcdir + end + + def objdir_root + @objdir + end + + def relpath + @currdir + end + + # + # Config Access + # + + # module FileOperations requires this + def verbose? + @config.verbose? + end + + # module FileOperations requires this + def no_harm? + @config.no_harm? + end + + def verbose_off + begin + save, @config.verbose = @config.verbose?, false + yield + ensure + @config.verbose = save + end + end + + # + # TASK config + # + + def exec_config + exec_task_traverse 'config' + end + + alias config_dir_bin noop + alias config_dir_lib noop + + def config_dir_ext(rel) + extconf if extdir?(curr_srcdir()) + end + + alias config_dir_data noop + alias config_dir_conf noop + alias config_dir_man noop + + def extconf + ruby "#{curr_srcdir()}/extconf.rb", *@config.config_opt + end + + # + # TASK setup + # + + def exec_setup + exec_task_traverse 'setup' + end + + def setup_dir_bin(rel) + files_of(curr_srcdir()).each do |fname| + update_shebang_line "#{curr_srcdir()}/#{fname}" + end + end + + alias setup_dir_lib noop + + def setup_dir_ext(rel) + make if extdir?(curr_srcdir()) + end + + alias setup_dir_data noop + alias setup_dir_conf noop + alias setup_dir_man noop + + def update_shebang_line(path) + return if no_harm? + return if config('shebang') == 'never' + old = Shebang.load(path) + if old + $stderr.puts "warning: #{path}: Shebang line includes too many args. It is not portable and your program may not work." if old.args.size > 1 + new = new_shebang(old) + return if new.to_s == old.to_s + else + return unless config('shebang') == 'all' + new = Shebang.new(config('rubypath')) + end + $stderr.puts "updating shebang: #{File.basename(path)}" if verbose? + open_atomic_writer(path) {|output| + File.open(path, 'rb') {|f| + f.gets if old # discard + output.puts new.to_s + output.print f.read + } + } + end + + def new_shebang(old) + if /\Aruby/ =~ File.basename(old.cmd) + Shebang.new(config('rubypath'), old.args) + elsif File.basename(old.cmd) == 'env' and old.args.first == 'ruby' + Shebang.new(config('rubypath'), old.args[1..-1]) + else + return old unless config('shebang') == 'all' + Shebang.new(config('rubypath')) + end + end + + def open_atomic_writer(path, &block) + tmpfile = File.basename(path) + '.tmp' + begin + File.open(tmpfile, 'wb', &block) + File.rename tmpfile, File.basename(path) + ensure + File.unlink tmpfile if File.exist?(tmpfile) + end + end + + class Shebang + def Shebang.load(path) + line = nil + File.open(path) {|f| + line = f.gets + } + return nil unless /\A#!/ =~ line + parse(line) + end + + def Shebang.parse(line) + cmd, *args = *line.strip.sub(/\A\#!/, '').split(' ') + new(cmd, args) + end + + def initialize(cmd, args = []) + @cmd = cmd + @args = args + end + + attr_reader :cmd + attr_reader :args + + def to_s + "#! #{@cmd}" + (@args.empty? ? '' : " #{@args.join(' ')}") + end + end + + # + # TASK install + # + + def exec_install + rm_f 'InstalledFiles' + exec_task_traverse 'install' + end + + def install_dir_bin(rel) + install_files targetfiles(), "#{config('bindir')}/#{rel}", 0755 + end + + def install_dir_lib(rel) + install_files libfiles(), "#{config('rbdir')}/#{rel}", 0644 + end + + def install_dir_ext(rel) + return unless extdir?(curr_srcdir()) + install_files rubyextentions('.'), + "#{config('sodir')}/#{File.dirname(rel)}", + 0555 + end + + def install_dir_data(rel) + install_files targetfiles(), "#{config('datadir')}/#{rel}", 0644 + end + + def install_dir_conf(rel) + # FIXME: should not remove current config files + # (rename previous file to .old/.org) + install_files targetfiles(), "#{config('sysconfdir')}/#{rel}", 0644 + end + + def install_dir_man(rel) + install_files targetfiles(), "#{config('mandir')}/#{rel}", 0644 + end + + def install_files(list, dest, mode) + mkdir_p dest, @config.install_prefix + list.each do |fname| + install fname, dest, mode, @config.install_prefix + end + end + + def libfiles + glob_reject(%w(*.y *.output), targetfiles()) + end + + def rubyextentions(dir) + ents = glob_select("*.#{@config.dllext}", targetfiles()) + if ents.empty? + setup_rb_error "no ruby extention exists: 'ruby #{$0} setup' first" + end + ents + end + + def targetfiles + mapdir(existfiles() - hookfiles()) + end + + def mapdir(ents) + ents.map {|ent| + if File.exist?(ent) + then ent # objdir + else "#{curr_srcdir()}/#{ent}" # srcdir + end + } + end + + # picked up many entries from cvs-1.11.1/src/ignore.c + JUNK_FILES = %w( + core RCSLOG tags TAGS .make.state + .nse_depinfo #* .#* cvslog.* ,* .del-* *.olb + *~ *.old *.bak *.BAK *.orig *.rej _$* *$ + + *.org *.in .* + ) + + def existfiles + glob_reject(JUNK_FILES, (files_of(curr_srcdir()) | files_of('.'))) + end + + def hookfiles + %w( pre-%s post-%s pre-%s.rb post-%s.rb ).map {|fmt| + %w( config setup install clean ).map {|t| sprintf(fmt, t) } + }.flatten + end + + def glob_select(pat, ents) + re = globs2re([pat]) + ents.select {|ent| re =~ ent } + end + + def glob_reject(pats, ents) + re = globs2re(pats) + ents.reject {|ent| re =~ ent } + end + + GLOB2REGEX = { + '.' => '\.', + '$' => '\$', + '#' => '\#', + '*' => '.*' + } + + def globs2re(pats) + /\A(?:#{ + pats.map {|pat| pat.gsub(/[\.\$\#\*]/) {|ch| GLOB2REGEX[ch] } }.join('|') + })\z/ + end + + # + # TASK test + # + + TESTDIR = 'test' + + def exec_test + unless File.directory?('test') + $stderr.puts 'no test in this package' if verbose? + return + end + $stderr.puts 'Running tests...' if verbose? + begin + require 'test/unit' + rescue LoadError + setup_rb_error 'test/unit cannot loaded. You need Ruby 1.8 or later to invoke this task.' + end + runner = Test::Unit::AutoRunner.new(true) + runner.to_run << TESTDIR + runner.run + end + + # + # TASK clean + # + + def exec_clean + exec_task_traverse 'clean' + rm_f @config.savefile + rm_f 'InstalledFiles' + end + + alias clean_dir_bin noop + alias clean_dir_lib noop + alias clean_dir_data noop + alias clean_dir_conf noop + alias clean_dir_man noop + + def clean_dir_ext(rel) + return unless extdir?(curr_srcdir()) + make 'clean' if File.file?('Makefile') + end + + # + # TASK distclean + # + + def exec_distclean + exec_task_traverse 'distclean' + rm_f @config.savefile + rm_f 'InstalledFiles' + end + + alias distclean_dir_bin noop + alias distclean_dir_lib noop + + def distclean_dir_ext(rel) + return unless extdir?(curr_srcdir()) + make 'distclean' if File.file?('Makefile') + end + + alias distclean_dir_data noop + alias distclean_dir_conf noop + alias distclean_dir_man noop + + # + # Traversing + # + + def exec_task_traverse(task) + run_hook "pre-#{task}" + FILETYPES.each do |type| + if type == 'ext' and config('without-ext') == 'yes' + $stderr.puts 'skipping ext/* by user option' if verbose? + next + end + traverse task, type, "#{task}_dir_#{type}" + end + run_hook "post-#{task}" + end + + def traverse(task, rel, mid) + dive_into(rel) { + run_hook "pre-#{task}" + __send__ mid, rel.sub(%r[\A.*?(?:/|\z)], '') + directories_of(curr_srcdir()).each do |d| + traverse task, "#{rel}/#{d}", mid + end + run_hook "post-#{task}" + } + end + + def dive_into(rel) + return unless File.dir?("#{@srcdir}/#{rel}") + + dir = File.basename(rel) + Dir.mkdir dir unless File.dir?(dir) + prevdir = Dir.pwd + Dir.chdir dir + $stderr.puts '---> ' + rel if verbose? + @currdir = rel + yield + Dir.chdir prevdir + $stderr.puts '<--- ' + rel if verbose? + @currdir = File.dirname(rel) + end + + def run_hook(id) + path = [ "#{curr_srcdir()}/#{id}", + "#{curr_srcdir()}/#{id}.rb" ].detect {|cand| File.file?(cand) } + return unless path + begin + instance_eval File.read(path), path, 1 + rescue + raise if $DEBUG + setup_rb_error "hook #{path} failed:\n" + $!.message + end + end + +end # class Installer + + +class SetupError < StandardError; end + +def setup_rb_error(msg) + raise SetupError, msg +end + +if $0 == __FILE__ + begin + ToplevelInstaller.invoke + rescue SetupError + raise if $DEBUG + $stderr.puts $!.message + $stderr.puts "Try 'ruby #{$0} --help' for detailed usage." + exit 1 + end +end diff --git a/spec/gchart_spec.rb b/spec/gchart_spec.rb new file mode 100644 index 0000000..989471a --- /dev/null +++ b/spec/gchart_spec.rb @@ -0,0 +1,277 @@ +require File.dirname(__FILE__) + '/spec_helper.rb' +require File.dirname(__FILE__) + '/../lib/gchart' + +# Time to add your specs! +# http://rspec.rubyforge.org/ +describe "generating a default Gchart" do + + before(:each) do + @chart = Gchart.line + end + + it "should include the Google URL" do + @chart.include?("http://chart.apis.google.com/chart?").should be_true + end + + it "should have a default size" do + @chart.include?('chs=300x200').should be_true + end + + it "should be able to have a custom size" do + Gchart.line(:size => '400x600').include?('chs=400x600').should be_true + Gchart.line(:width => 400, :height => 600).include?('chs=400x600').should be_true + end + + it "should have a type" do + @chart.include?('cht=lc').should be_true + end + + it "should use the simple encoding by default with auto max value" do + # 9 is the max value in simple encoding, 26 being our max value the 2nd encoded value should be 9 + Gchart.line(:data => [0, 26]).include?('chd=s:A9').should be_true + Gchart.line(:data => [0, 26], :max_value => 26).should == Gchart.line(:data => [0, 26]) + end + + it "should support simple encoding with and without max_value" do + Gchart.line(:data => [0, 26], :max_value => 26).include?('chd=s:A9').should be_true + Gchart.line(:data => [0, 26], :max_value => false).include?('chd=s:Aa').should be_true + end + + it "should support the extended encoding and encode properly" do + Gchart.line(:data => [0, 10], :encoding => 'extended', :max_value => false).include?('chd=e:AA').should be_true + Gchart.line(:encoding => 'extended', + :max_value => false, + :data => [[0,25,26,51,52,61,62,63], [64,89,90,115,4084]] + ).include?('chd=e:AAAZAaAzA0A9A-A.,BABZBaBz.0').should be_true + end + + it "should auto set the max value for extended encoding" do + Gchart.line(:data => [0, 25], :encoding => 'extended', :max_value => false).include?('chd=e:AAAZ').should be_true + # Extended encoding max value is '..' + Gchart.line(:data => [0, 25], :encoding => 'extended').include?('chd=e:AA..').should be_true + end + + it "should be able to have data with text encoding" do + Gchart.line(:data => [10, 5.2, 4, 45, 78], :encoding => 'text').include?('chd=t:10,5.2,4,45,78').should be_true + end + + it "should be able to have muliple set of data with text encoding" do + Gchart.line(:data => [[10, 5.2, 4, 45, 78], [20, 40, 70, 15, 99]], :encoding => 'text').include?(Gchart.jstize('chd=t:10,5.2,4,45,78|20,40,70,15,99')).should be_true + end + + it "should be able to receive a custom param" do + Gchart.line(:custom => 'ceci_est_une_pipe').include?('ceci_est_une_pipe').should be_true + end + + it "should be able to set label axis" do + Gchart.line(:axis_with_labels => 'x,y,r').include?('chxt=x,y,r').should be_true + Gchart.line(:axis_with_labels => ['x','y','r']).include?('chxt=x,y,r').should be_true + end + + it "should be able to have axis labels" do + Gchart.line(:axis_labels => ['Jan|July|Jan|July|Jan', '0|100', 'A|B|C', '2005|2006|2007']).include?(Gchart.jstize('chxl=0:|Jan|July|Jan|July|Jan|1:|0|100|2:|A|B|C|3:|2005|2006|2007')).should be_true + Gchart.line(:axis_labels => ['Jan|July|Jan|July|Jan']).include?(Gchart.jstize('chxl=0:|Jan|July|Jan|July|Jan')).should be_true + Gchart.line(:axis_labels => [['Jan','July','Jan','July','Jan']]).include?(Gchart.jstize('chxl=0:|Jan|July|Jan|July|Jan')).should be_true + Gchart.line(:axis_labels => [['Jan','July','Jan','July','Jan'], ['0','100'], ['A','B','C'], ['2005','2006','2007']]).include?(Gchart.jstize('chxl=0:|Jan|July|Jan|July|Jan|1:|0|100|2:|A|B|C|3:|2005|2006|2007')).should be_true + end + +end + +describe "generating different type of charts" do + + it "should be able to generate a line chart" do + Gchart.line.should be_an_instance_of(String) + Gchart.line.include?('cht=lc').should be_true + end + + it "should be able to generate a line xy chart" do + Gchart.line_xy.should be_an_instance_of(String) + Gchart.line_xy.include?('cht=lxy').should be_true + end + + it "should be able to generate a scatter chart" do + Gchart.scatter.should be_an_instance_of(String) + Gchart.scatter.include?('cht=s').should be_true + end + + it "should be able to generate a bar chart" do + Gchart.bar.should be_an_instance_of(String) + Gchart.bar.include?('cht=bvs').should be_true + end + + it "should be able to generate a Venn diagram" do + Gchart.venn.should be_an_instance_of(String) + Gchart.venn.include?('cht=v').should be_true + end + + it "should be able to generate a Pie Chart" do + Gchart.pie.should be_an_instance_of(String) + Gchart.pie.include?('cht=p').should be_true + end + + it "should not support other types" do + Gchart.sexy.should == "sexy is not a supported chart format, please use one of the following: #{Gchart.supported_types}." + end + +end + +describe "a bar graph" do + + it "should have a default vertical orientation" do + Gchart.bar.include?('cht=bvs').should be_true + end + + it "should be able to have a different orientation" do + Gchart.bar(:orientation => 'vertical').include?('cht=bvs').should be_true + Gchart.bar(:orientation => 'v').include?('cht=bvs').should be_true + Gchart.bar(:orientation => 'h').include?('cht=bhs').should be_true + Gchart.bar(:orientation => 'horizontal').include?('cht=bhs').should be_true + Gchart.bar(:horizontal => false).include?('cht=bvs').should be_true + end + + it "should be set to be stacked by default" do + Gchart.bar.include?('cht=bvs').should be_true + end + + it "should be able to stacked or grouped" do + Gchart.bar(:stacked => true).include?('cht=bvs').should be_true + Gchart.bar(:stacked => false).include?('cht=bvg').should be_true + Gchart.bar(:grouped => true).include?('cht=bvg').should be_true + Gchart.bar(:grouped => false).include?('cht=bvs').should be_true + end + + it "should be able to have different bar colors" do + Gchart.bar(:bar_colors => 'efefef,00ffff').include?('chco=').should be_true + Gchart.bar(:bar_colors => 'efefef,00ffff').include?('chco=efefef,00ffff').should be_true + # alias + Gchart.bar(:bar_color => 'efefef').include?('chco=efefef').should be_true + end + + it "should be able to have different bar colors when using an array of colors" do + Gchart.bar(:bar_colors => ['efefef','00ffff']).include?('chco=efefef,00ffff').should be_true + end + +end + +describe "a line chart" do + + before(:each) do + @title = 'Chart Title' + @legend = ['first data set label', 'n data set label'] + @chart = Gchart.line(:title => @title, :legend => @legend) + end + + it 'should be able have a chart title' do + @chart.include?("chtt=Chart+Title").should be_true + end + + it "should be able to a custom color and size title" do + Gchart.line(:title => @title, :title_color => 'FF0000').include?('chts=FF0000').should be_true + Gchart.line(:title => @title, :title_size => '20').include?('chts=454545,20').should be_true + end + + it "should be able to have multiple legends" do + @chart.include?(Gchart.jstize("chdl=first+data+set+label|n+data+set+label")).should be_true + end + + it "should be able to have one legend" do + chart = Gchart.line(:legend => 'legend label') + chart.include?("chdl=legend+label").should be_true + end + + it "should be able to set the background fill" do + Gchart.line(:bg => 'efefef').include?("chf=bg,s,efefef").should be_true + Gchart.line(:bg => {:color => 'efefef', :type => 'solid'}).include?("chf=bg,s,efefef").should be_true + + Gchart.line(:bg => {:color => 'efefef', :type => 'gradient'}).include?("chf=bg,lg,0,efefef,0,ffffff,1").should be_true + Gchart.line(:bg => {:color => 'efefef,0,ffffff,1', :type => 'gradient'}).include?("chf=bg,lg,0,efefef,0,ffffff,1").should be_true + Gchart.line(:bg => {:color => 'efefef', :type => 'gradient', :angle => 90}).include?("chf=bg,lg,90,efefef,0,ffffff,1").should be_true + + Gchart.line(:bg => {:color => 'efefef', :type => 'stripes'}).include?("chf=bg,ls,90,efefef,0.2,ffffff,0.2").should be_true + end + + it "should be able to set a graph fill" do + Gchart.line(:graph_bg => 'efefef').include?("chf=c,s,efefef").should be_true + Gchart.line(:graph_bg => {:color => 'efefef', :type => 'solid'}).include?("chf=c,s,efefef").should be_true + Gchart.line(:graph_bg => {:color => 'efefef', :type => 'gradient'}).include?("chf=c,lg,0,efefef,0,ffffff,1").should be_true + Gchart.line(:graph_bg => {:color => 'efefef,0,ffffff,1', :type => 'gradient'}).include?("chf=c,lg,0,efefef,0,ffffff,1").should be_true + Gchart.line(:graph_bg => {:color => 'efefef', :type => 'gradient', :angle => 90}).include?("chf=c,lg,90,efefef,0,ffffff,1").should be_true + end + + it "should be able to set both a graph and a background fill" do + Gchart.line(:bg => 'efefef', :graph_bg => '76A4FB').include?("bg,s,efefef").should be_true + Gchart.line(:bg => 'efefef', :graph_bg => '76A4FB').include?("c,s,76A4FB").should be_true + Gchart.line(:bg => 'efefef', :graph_bg => '76A4FB').include?(Gchart.jstize("chf=c,s,76A4FB|bg,s,efefef")).should be_true + end + + it "should be able to have different line colors" do + Gchart.line(:line_colors => 'efefef|00ffff').include?(Gchart.jstize('chco=efefef|00ffff')).should be_true + Gchart.line(:line_color => 'efefef|00ffff').include?(Gchart.jstize('chco=efefef|00ffff')).should be_true + end + +end + +describe "a pie chart" do + + before(:each) do + @title = 'Chart Title' + @legend = ['first data set label', 'n data set label'] + @jstized_legend = Gchart.jstize(@legend.join('|')) + @data = [12,8,40,15,5] + @chart = Gchart.pie(:title => @title, :legend => @legend, :data => @data) + end + + it "should create a pie" do + @chart.include?('cht=p').should be_true + end + + it "should be able to be in 3d" do + Gchart.pie_3d(:title => @title, :legend => @legend, :data => @data).include?('cht=p3').should be_true + end + + it "should be able to set labels by using the legend or labesl accessor" do + Gchart.pie_3d(:title => @title, :legend => @legend, :data => @data).include?("chl=#{@jstized_legend}").should be_true + Gchart.pie_3d(:title => @title, :labels => @legend, :data => @data).include?("chl=#{@jstized_legend}").should be_true + Gchart.pie_3d(:title => @title, :labels => @legend, :data => @data).should == Gchart.pie_3d(:title => @title, :legend => @legend, :data => @data) + end + +end + +describe 'exporting a chart' do + + it "should be available in the url format by default" do + Gchart.line(:data => [0, 26], :format => 'url').should == Gchart.line(:data => [0, 26]) + end + + it "should be available as the img tag" do + Gchart.line(:data => [0, 26], :format => 'img_tag').should match(//) + end + + it "should be available as a file" do + File.delete('chart.png') if File.exist?('chart.png') + Gchart.line(:data => [0, 26], :format => 'file') + File.exist?('chart.png').should be_true + File.delete('chart.png') if File.exist?('chart.png') + end + + it "should be available as a file using a custom file name" do + File.delete('custom_file_name.png') if File.exist?('custom_file_name.png') + Gchart.line(:data => [0, 26], :format => 'file', :filename => 'custom_file_name.png') + File.exist?('custom_file_name.png').should be_true + File.delete('custom_file_name.png') if File.exist?('custom_file_name.png') + end + + it "should work even with multiple attrs" do + File.delete('foo.png') if File.exist?('foo.png') + Gchart.line(:size => '400x200', + :data => [1,2,3,4,5], + :axis_labels => [[1,2,3,4, 5], %w[foo bar]], + :axis_with_labels => 'x,r', + :format => "file", + :filename => "foo.png" + ) + File.exist?('foo.png').should be_true + File.delete('foo.png') if File.exist?('foo.png') + end + +end \ No newline at end of file diff --git a/spec/spec.opts b/spec/spec.opts new file mode 100644 index 0000000..cf6add7 --- /dev/null +++ b/spec/spec.opts @@ -0,0 +1 @@ +--colour \ No newline at end of file diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 0000000..f1ca062 --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,7 @@ +begin + require 'spec' +rescue LoadError + require 'rubygems' + gem 'rspec' + require 'spec' +end \ No newline at end of file diff --git a/tasks/deployment.rake b/tasks/deployment.rake new file mode 100644 index 0000000..2f43742 --- /dev/null +++ b/tasks/deployment.rake @@ -0,0 +1,34 @@ +desc 'Release the website and new gem version' +task :deploy => [:check_version, :website, :release] do + puts "Remember to create SVN tag:" + puts "svn copy svn+ssh://#{rubyforge_username}@rubyforge.org/var/svn/#{PATH}/trunk " + + "svn+ssh://#{rubyforge_username}@rubyforge.org/var/svn/#{PATH}/tags/REL-#{VERS} " + puts "Suggested comment:" + puts "Tagging release #{CHANGES}" +end + +desc 'Runs tasks website_generate and install_gem as a local deployment of the gem' +task :local_deploy => [:website_generate, :install_gem] + +task :check_version do + unless ENV['VERSION'] + puts 'Must pass a VERSION=x.y.z release version' + exit + end + unless ENV['VERSION'] == VERS + puts "Please update your version.rb to match the release version, currently #{VERS}" + exit + end +end + +desc 'Install the package as a gem, without generating documentation(ri/rdoc)' +task :install_gem_no_doc => [:clean, :package] do + sh "#{'sudo ' unless Hoe::WINDOZE }gem install pkg/*.gem --no-rdoc --no-ri" +end + +namespace :manifest do + desc 'Recreate Manifest.txt to include ALL files' + task :refresh do + `rake check_manifest | patch -p0 > Manifest.txt` + end +end \ No newline at end of file diff --git a/tasks/environment.rake b/tasks/environment.rake new file mode 100644 index 0000000..691ed3b --- /dev/null +++ b/tasks/environment.rake @@ -0,0 +1,7 @@ +task :ruby_env do + RUBY_APP = if RUBY_PLATFORM =~ /java/ + "jruby" + else + "ruby" + end unless defined? RUBY_APP +end diff --git a/tasks/rspec.rake b/tasks/rspec.rake new file mode 100644 index 0000000..b256d1c --- /dev/null +++ b/tasks/rspec.rake @@ -0,0 +1,21 @@ +begin + require 'spec' +rescue LoadError + require 'rubygems' + require 'spec' +end +begin + require 'spec/rake/spectask' +rescue LoadError + puts <<-EOS +To use rspec for testing you must install rspec gem: + gem install rspec +EOS + exit(0) +end + +desc "Run the specs under spec/models" +Spec::Rake::SpecTask.new do |t| + t.spec_opts = ['--options', "spec/spec.opts"] + t.spec_files = FileList['spec/*_spec.rb'] +end diff --git a/tasks/website.rake b/tasks/website.rake new file mode 100644 index 0000000..93e03fa --- /dev/null +++ b/tasks/website.rake @@ -0,0 +1,17 @@ +desc 'Generate website files' +task :website_generate => :ruby_env do + (Dir['website/**/*.txt'] - Dir['website/version*.txt']).each do |txt| + sh %{ #{RUBY_APP} script/txt2html #{txt} > #{txt.gsub(/txt$/,'html')} } + end +end + +desc 'Upload website files to rubyforge' +task :website_upload do + host = "#{rubyforge_username}@rubyforge.org" + remote_dir = "/var/www/gforge-projects/#{PATH}/" + local_dir = 'website' + sh %{rsync -aCv #{local_dir}/ #{host}:#{remote_dir}} +end + +desc 'Generate and upload website files' +task :website => [:website_generate, :website_upload, :publish_docs] diff --git a/website/index.html b/website/index.html new file mode 100644 index 0000000..c161346 --- /dev/null +++ b/website/index.html @@ -0,0 +1,601 @@ + + + + + + + Googlecharts + + + + + + +
+ +

Googlecharts

+
+

Get Version

+ 1.1.0 +
+

→ ‘Sexy Charts using Google API & Ruby’

+ + +

What

+ + +
A nice and simple wrapper for Google Chart API
+ + +

Installing

+ + +

sudo gem install googlecharts

+ + +

The basics

+ + +

This gem supports the following types of charts:

+ + +

Line Gchart.line()

+ + +

line_xy Gchart.line_xy()

+ + +

scatter Gchart.scatter()

+ + +

bar Gchart.bar()

+ + +

venn Gchart.venn()

+ + +

pie Gchart.pie()

+ + +

pie_3d Gchart.pie_3d()

+ + +

Demonstration of usage

+ + +install: +sudo gem install googlecharts + +

require: +

require 'gchart'

+ + +

Gchart.line(  :size => '200x300', 
+              :title => "example title",
+              :bg => 'efefef',
+              :legend => ['first data set label', 'second data set label'],
+              :data => [10, 30, 120, 45, 72])

+ + +
+ + +

simple line chart +

+  Gchart.line(:data => [0, 40, 10, 70, 20])
+

+ + +

Generate the following url: http://chart.apis.google.com/chart?chs=300×200&chd=s:AiI9R&cht=lc

+ + +

Inserted in an image tag, it will look like that:

+ + +

simple line chart

+ + +

multiple line charts +

+  Gchart.line(:data => [[0, 40, 10, 70, 20],[41, 10, 80, 50]])
+

+ + +

multiple lines chart

+ + +

set line colors

+ + +

+  Gchart.line(:data => [[0, 40, 10, 70, 20],[41, 10, 80, 50]], :line_colors => "FF0000,00FF00")
+

+ + +

line colors

+ + +

more info about color settings

+ + +

bar chart

+ + +

+  Gchart.bar(:data => [300, 100, 30, 200])
+
+bars

+ + +

set the bar chart orientation

+ + +

+  Gchart.bar(:data => [300, 100, 30, 200], :orientation => 'horizontal')
+
+bars

+ + +

multiple bars chart

+ + +

+  Gchart.bar(:data => [[300, 100, 30, 200], [100, 200, 300, 10]])
+

+ + +

stacked multiple bars

+ + +

The problem is that by default the bars are stacked, so we need to set the colors:

+ + +

+  Gchart.bar(:data => [[300, 100, 30, 200], [100, 200, 300, 10]], :bar_colors => 'FF0000,00FF00')
+

+ + +

If you prefer you can use this other syntax:

+ + +

+  Gchart.bar(:data => [[300, 100, 30, 200], [100, 200, 300, 10]], :bar_colors => ['FF0000', '00FF00'])
+

+ + +

colors

+ + +

The problem now, is that we can’t see the first value of the second dataset since it’s lower than the first value of the first dataset. Let’s unstack the bars:

+ + +

+  Gchart.bar( :data => [[300, 100, 30, 200], [100, 200, 300, 10]], 
+              :bar_colors => 'FF0000,00FF00',
+              :stacked => false )
+

+ + +

grouped bars

+ + +

pie chart

+ + +

+  Gchart.pie(:data => [20, 35, 45])
+
+Pie Chart

+ + +

3D pie chart

+ + +

+  Gchart.pie_3d(:data => [20, 35, 45])
+
+Pie Chart

+ + +

venn diagram

+ + +

Google documentation

+ + +Data set: +
    +
  • the first three values specify the relative sizes of three circles, A, B, and C
  • +
  • the fourth value specifies the area of A intersecting B
  • +
  • the fifth value specifies the area of B intersecting C
  • +
  • the sixth value specifies the area of C intersecting A
  • +
  • the seventh value specifies the area of A intersecting B intersecting C
  • +
+ + +

+  Gchart.venn(:data => [100, 80, 60, 30, 30, 30, 10])
+
+Venn

+ + +

scatter plot

+ + +

Google Documentation

+ + +

Supply two data sets, the first data set specifies x coordinates, the second set specifies y coordinates, the third set the data point size.

+ + +

+  Gchart.scatter(:data => [[1, 2, 3, 4, 5], [1, 2, 3, 4 ,5], [5, 4, 3, 2, 1]])
+

+ + +

scatter

+ + +
+ + +

set a chart title

+ + +

+  Gchart.bar(:title => "Recent Chart Sexyness", :data => [15, 30, 10, 20, 100, 20, 40, 100])
+

+ + +

chart title

+ + +

set the title size

+ + +

+  Gchart.bar(:title => "Recent Chart Sexyness", :title_size => 20, :data => [15, 30, 10, 20, 100, 20, 40, 100])
+

+ + +

title size

+ + +

set the title color

+ + +

+  Gchart.bar(:title => "Recent Chart Sexyness", :title_color => 'FF0000', :data => [15, 30, 10, 20, 100, 20, 40, 100])
+

+ + +

Title color

+ + +

set the chart’s size

+ + +

+  Gchart.bar( :title => "Recent Chart Sexyness", 
+              :data => [15, 30, 10, 20, 100, 20, 40, 100],
+              :size => '600x400')
+

+ + +

size

+ + +

set the image background color

+ + +

+  Gchart.bar( :title => "Matt's Mojo", 
+              :data => [15, 30, 10, 20, 100, 20, 40, 100, 90, 100, 80],
+              :background => 'FF9994')
+

+ + +

Background

+ + +

set the chart background color

+ + +

+  Gchart.bar( :title => "Matt's Mojo", 
+              :data => [15, 30, 10, 20, 100, 20, 40, 100, 90, 100, 80],
+              :background => 'FF9994', :chart_background => '000000')
+

+ + +

chart background

+ + +

Set bar/line colors

+ + +

+  Gchart.bar( :title => "Matt's Mojo", 
+              :data => [15, 30, 10, 20, 100, 20, 40, 100, 90, 100, 80],
+              :bar_colors => '76A4FB',
+              :background => 'EEEEEE', :chart_background => 'CCCCCC')
+

+ + +

bar colors

+ + +

+  Gchart.line( :title => "Matt's Mojo", 
+              :data => [15, 30, 10, 20, 100, 20, 40, 100, 90, 100, 80],
+              :line_colors => '76A4FB')
+

+ + +

line colors

+ + +

legend / labels

+ + +

+  Gchart.bar( :title => "Matt vs Rob",
+              :data => [[300, 100, 30, 200], [100, 200, 300, 10]], 
+              :bar_colors => 'FF0000,00FF00',
+              :stacked => false, :size => '400x200',
+              :legend => ["Matt's Mojo", "Rob's Mojo"] )
+
+legend

+ + +

+  Gchart.line( :title => "Matt vs Rob",
+              :data => [[300, 100, 30, 200], [100, 200, 300, 10]], 
+              :bar_colors => ['FF0000','00FF00'],
+              :stacked => false, :size => '400x200',
+              :legend => ["Matt's Mojo", "Rob's Mojo"] )
+
+line legend

+ + +

+  Gchart.pie_3d(  :title => 'ruby_fu', :size => '400x200',
+                  :data => [10, 45, 45], :labels => ["DHH", "Rob", "Matt"] )
+

+ + +

labels

+ + +

Display axis labels

+ + +

+  Gchart.line( :data => [300, 100, 30, 200, 100, 200, 300, 10], :axis_with_labels => 'x,y,r')
+

+ + +

or you can use the other syntax:

+ + +

+  Gchart.line( :data => [300, 100, 30, 200, 100, 200, 300, 10], :axis_with_labels => ['x','y','r'])
+

+ + +

axis with labels

+ + +

+  Gchart.line(  :data => [300, 100, 30, 200, 100, 200, 300, 10], :axis_with_labels => 'x',
+                :axis_labels => ['Jan|July|Jan|July|Jan'])
+

+ + +

or you can use the other syntax:

+ + +

+  Gchart.line(  :data => [300, 100, 30, 200, 100, 200, 300, 10], :axis_with_labels => 'x',
+                :axis_labels => ['Jan','July','Jan','July','Jan'])
+

+ + +

x labels

+ + +

multiple axis labels

+ + +

+  Gchart.line(  :data => [300, 100, 30, 200, 100, 200, 300, 10], :axis_with_labels => 'x,r',
+                :axis_labels => ['Jan|July|Jan|July|Jan', '2005|2006|2007'])
+

+ + +

or

+ + +

+  Gchart.line(  :data => [300, 100, 30, 200, 100, 200, 300, 10], :axis_with_labels => 'x,r',
+                :axis_labels => [['Jan','July','Jan','July','Jan'], ['2005','2006','2007']])
+

+ + +

multiple axis labels

+ + +

(This syntax will probably be improved in the future)

+ + +

custom params

+ + +

I certainly didn’t cover the entire API, if you want to add your own params:

+ + +

+  Gchart.line( custom => 'chd=s:93zyvneTTOMJMLIJFHEAECFJGHDBFCFIERcgnpy45879,IJKNUWUWYdnswz047977315533zy1246872tnkgcaZQONHCECAAAAEII&chls=3,6,3|1,1,0')
+

+ + +

Custom

+ + +
+ + +

Save the chart as a file

+ + +

You might prefer to save the chart instead of using the url, not a problem:

+ + +

+  Gchart.line(:data => [0, 26], :format => 'file')
+

+ + +

You might want to specify the path and/or the filename used to save your chart:

+ + +

+  Gchart.line(:data => [0, 26], :format => 'file', :filename => 'custom_filename.png')
+

+ + +

Insert as an image tag

+ + +

Because, I’m lazy, I also added a custom format:

+ + +

+  Gchart.line(:data => [0, 26], :format => 'img_tag')
+

+ + +

<img src=’http://chart.apis.google.com/chart?chs=300×200&chd=s:A9&cht=lc’/>

+ + +
+ + +

Encoding

+ + +

Google Chart API offers 3 types of data encoding

+ + +
    +
  • simple
  • +
  • text
  • +
  • extended
  • +
+ + +

By default this library uses the simple encoding, if you need a different type of encoding, you can change it really easily:

+ + +

default / simple: chd=s:9UGoUo9C +

+  Gchart.line( :data => [300, 100, 30, 200, 100, 200, 300, 10] )
+

+ + +

extended: chd=e:..VVGZqqVVqq..CI +

+  Gchart.line( :data => [300, 100, 30, 200, 100, 200, 300, 10], :encoding => 'extended' )
+

+ + +

text: chd=t:300,100,30,200,100,200,300,10 +

+  Gchart.line( :data => [300, 100, 30, 200, 100, 200, 300, 10], :encoding => 'text' )
+

+ + +

(note that the text encoding doesn’t use a max value and therefore should be under 100)

+ + +

Max value

+ + +

Simple and extended encoding support the max value option.

+ + +

The max value option is a simple way of scaling your graph. The data is converted in chart value with the highest chart value being the highest point on the graph. By default, the calculation is done for you. However you can specify your own maximum or not use a maximum at all.

+ + +

+  Gchart.line( :data => [300, 100, 30, 200, 100, 200, 300, 10] )
+

+ + +

Title

+ + +

+  Gchart.line( :data => [300, 100, 30, 200, 100, 200, 300, 10], :max_value => 500 )
+

+ + +

max 500

+ + +

+  Gchart.line( :data => [100, 20, 30, 20, 10, 14, 30, 10], :max_value => false )
+

+ + +

real size

+ + +

Repository

+ + +

The trunk repository is svn://rubyforge.org/var/svn/googlecharts/trunk for anonymous access.

+ + +

License

+ + +

This code is free to use under the terms of the MIT license.

+ + +

Contact

+ + +

Comments are welcome. Send an email to Matt Aimonetti

+

+ Matt Aimonetti, 25th February 2008
+ Theme extended from Paul Battley +

+
+ + + + + + diff --git a/website/index.txt b/website/index.txt new file mode 100644 index 0000000..365ce85 --- /dev/null +++ b/website/index.txt @@ -0,0 +1,417 @@ +h1. Googlecharts + +h2. → 'Sexy Charts using Google API & Ruby' + + +h2. What + + A nice and simple wrapper for "Google Chart API":http://code.google.com/apis/chart/ + +h2. Installing + +
sudo gem install googlecharts
+ +h2. The basics + +This gem supports the following types of charts: + +!http://chart.apis.google.com/chart?cht=lc&chs=200x125&chd=s:helloWorld&chxt=x,y&chxl=0:|Mar|Apr|May|June|July|1:||50+Kb(Line)! Gchart.line() + +!http://chart.apis.google.com/chart?cht=lxy&chs=200x125&chd=t:0,30,60,70,90,95,100|20,30,40,50,60,70,80|10,30,40,45,52|100,90,40,20,10|-1|5,33,50,55,7&chco=3072F3,ff0000,00aaaa&chls=2,4,1&chm=s,FF0000,0,-1,5|s,0000ff,1,-1,5|s,00aa00,2,-1,5(line_xy)! Gchart.line_xy() + +!http://chart.apis.google.com/chart?cht=s&chd=s:984sttvuvkQIBLKNCAIi,DEJPgq0uov17zwopQODS,AFLPTXaflptx159gsDrn&chxt=x,y&chxl=0:|0|2|3|4|5|6|7|8|9|10|1:|0|25|50|75|100&chs=200x125(scatter)! Gchart.scatter() + +!http://chart.apis.google.com/chart?cht=bvg&chs=200x125&chd=s:hello,world&chco=cc0000,00aa00(bar)! Gchart.bar() + +!http://chart.apis.google.com/chart?cht=v&chs=200x100&chd=t:100,80,60,30,30,30,10(venn)! Gchart.venn() + +!http://chart.apis.google.com/chart?cht=p&chd=s:world5&chs=200x125&chl=A|B|C|D|E|Fe(pie)! Gchart.pie() + +!http://chart.apis.google.com/chart?cht=p3&chd=s:Uf9a&chs=200x100&chl=A|B|C|D(pie_3d)! Gchart.pie_3d() + + +h2. Demonstration of usage + +install: +sudo gem install googlecharts + +require: +
require 'gchart'
+ +
Gchart.line(  :size => '200x300', 
+              :title => "example title",
+              :bg => 'efefef',
+              :legend => ['first data set label', 'second data set label'],
+              :data => [10, 30, 120, 45, 72])
+ +--- + +*simple line chart* +
+  Gchart.line(:data => [0, 40, 10, 70, 20])
+
+ +Generate the following url: http://chart.apis.google.com/chart?chs=300x200&chd=s:AiI9R&cht=lc + +Inserted in an image tag, it will look like that: + +!http://chart.apis.google.com/chart?chs=300x200&chd=s:AiI9R&cht=lc(simple line chart)! + +*multiple line charts* +
+  Gchart.line(:data => [[0, 40, 10, 70, 20],[41, 10, 80, 50]])
+
+ +!http://chart.apis.google.com/chart?cht=lc&chs=300x200&chd=s:AeH1P,fH9m(multiple lines chart)! + +*set line colors* + +
+  Gchart.line(:data => [[0, 40, 10, 70, 20],[41, 10, 80, 50]], :line_colors => "FF0000,00FF00")
+
+ +!http://chart.apis.google.com/chart?cht=lc&chs=300x200&chd=s:AeH1P,fH9m&chco=FF0000,00FF00(line colors)! + +"more info about color settings":http://code.google.com/apis/chart/#chart_colors + +*bar chart* + +
+  Gchart.bar(:data => [300, 100, 30, 200])
+
+!http://chart.apis.google.com/chart?cht=bvs&chs=300x200&chd=s:9UGo(bars)! + +*set the bar chart orientation* + +
+  Gchart.bar(:data => [300, 100, 30, 200], :orientation => 'horizontal')
+
+!http://chart.apis.google.com/chart?cht=bhs&chs=300x200&chd=s:9UGo(bars)! + +*multiple bars chart* + +
+  Gchart.bar(:data => [[300, 100, 30, 200], [100, 200, 300, 10]])
+
+ +!http://chart.apis.google.com/chart?cht=bvs&chs=300x200&chd=s:9UGo,Uo9C(stacked multiple bars)! + +The problem is that by default the bars are stacked, so we need to set the colors: + +
+  Gchart.bar(:data => [[300, 100, 30, 200], [100, 200, 300, 10]], :bar_colors => 'FF0000,00FF00')
+
+ +If you prefer you can use this other syntax: + +
+  Gchart.bar(:data => [[300, 100, 30, 200], [100, 200, 300, 10]], :bar_colors => ['FF0000', '00FF00'])
+
+ +!http://chart.apis.google.com/chart?cht=bvs&chs=300x200&chd=s:9UGo,Uo9C&chco=FF0000,00FF00(colors)! + +The problem now, is that we can't see the first value of the second dataset since it's lower than the first value of the first dataset. Let's unstack the bars: + +
+  Gchart.bar( :data => [[300, 100, 30, 200], [100, 200, 300, 10]], 
+              :bar_colors => 'FF0000,00FF00',
+              :stacked => false )
+
+ +!http://chart.apis.google.com/chart?cht=bvg&chs=300x200&chd=s:9UGo,Uo9C&chco=FF0000,00FF00(grouped bars)! + + +*pie chart* + +
+  Gchart.pie(:data => [20, 35, 45])
+
+!http://chart.apis.google.com/chart?cht=p&chs=300x200&chd=s:bv9(Pie Chart)! + +*3D pie chart* + +
+  Gchart.pie_3d(:data => [20, 35, 45])
+
+!http://chart.apis.google.com/chart?cht=p3&chs=300x200&chd=s:bv9(Pie Chart)! + +*venn diagram* + +"Google documentation":http://code.google.com/apis/chart/#venn + +Data set: +* the first three values specify the relative sizes of three circles, A, B, and C +* the fourth value specifies the area of A intersecting B +* the fifth value specifies the area of B intersecting C +* the sixth value specifies the area of C intersecting A +* the seventh value specifies the area of A intersecting B intersecting C + +
+  Gchart.venn(:data => [100, 80, 60, 30, 30, 30, 10])
+
+!http://chart.apis.google.com/chart?cht=v&chs=300x200&chd=s:9wkSSSG(Venn)! + +*scatter plot* + +"Google Documentation":http://code.google.com/apis/chart/#scatter_plot + +Supply two data sets, the first data set specifies x coordinates, the second set specifies y coordinates, the third set the data point size. + +
+  Gchart.scatter(:data => [[1, 2, 3, 4, 5], [1, 2, 3, 4 ,5], [5, 4, 3, 2, 1]])
+
+ +!http://chart.apis.google.com/chart?cht=s&chs=300x200&chd=s:MYkw9,MYkw9,9wkYM(scatter)! + +--- + +*set a chart title* + +
+  Gchart.bar(:title => "Recent Chart Sexyness", :data => [15, 30, 10, 20, 100, 20, 40, 100])
+
+ +!http://chart.apis.google.com/chart?cht=bvs&chs=300x200&chd=s:JSGM9MY9&chtt=Recent+Chart+Sexyness(chart title)! + +*set the title size* + +
+  Gchart.bar(:title => "Recent Chart Sexyness", :title_size => 20, :data => [15, 30, 10, 20, 100, 20, 40, 100])
+
+ +!http://chart.apis.google.com/chart?cht=bvs&chs=300x200&chd=s:JSGM9MY9&chtt=Recent+Chart+Sexyness&chts=454545,20(title size)! + +*set the title color* + +
+  Gchart.bar(:title => "Recent Chart Sexyness", :title_color => 'FF0000', :data => [15, 30, 10, 20, 100, 20, 40, 100])
+
+ +!http://chart.apis.google.com/chart?cht=bvs&chs=300x200&chd=s:JSGM9MY9&chtt=Recent+Chart+Sexyness&chts=FF0000(Title color)! + +*set the chart's size* + +
+  Gchart.bar( :title => "Recent Chart Sexyness", 
+              :data => [15, 30, 10, 20, 100, 20, 40, 100],
+              :size => '600x400')
+
+ +!http://chart.apis.google.com/chart?cht=bvs&chs=600x400&chd=s:JSGM9MY9&chtt=Recent+Chart+Sexyness(size)! + +*set the image background color* + +
+  Gchart.bar( :title => "Matt's Mojo", 
+              :data => [15, 30, 10, 20, 100, 20, 40, 100, 90, 100, 80],
+              :background => 'FF9994')
+
+ +!http://chart.apis.google.com/chart?chf=bg,s,FF9994&cht=bvs&chs=300x200&chd=s:JSGM9MY929w&chtt=Matt's+Mojo(Background)! + +*set the chart background color* + +
+  Gchart.bar( :title => "Matt's Mojo", 
+              :data => [15, 30, 10, 20, 100, 20, 40, 100, 90, 100, 80],
+              :background => 'FF9994', :chart_background => '000000')
+
+ +!http://chart.apis.google.com/chart?chf=c,s,000000|bg,s,FF9994&cht=bvs&chs=300x200&chd=s:JSGM9MY929w&chtt=Matt's+Mojo(chart background)! + +*Set bar/line colors* + +
+  Gchart.bar( :title => "Matt's Mojo", 
+              :data => [15, 30, 10, 20, 100, 20, 40, 100, 90, 100, 80],
+              :bar_colors => '76A4FB',
+              :background => 'EEEEEE', :chart_background => 'CCCCCC')
+
+ +!http://chart.apis.google.com/chart?chf=c,s,CCCCCC|bg,s,EEEEEE&cht=bvs&chs=300x200&chd=s:JSGM9MY929w&chco=76A4FB&chtt=Matt's+Mojo(bar colors)! + +
+  Gchart.line( :title => "Matt's Mojo", 
+              :data => [15, 30, 10, 20, 100, 20, 40, 100, 90, 100, 80],
+              :line_colors => '76A4FB')
+
+ +!http://chart.apis.google.com/chart?cht=lc&chs=300x200&chd=s:JSGM9MY929w&chco=76A4FB&chtt=Matt's+Mojo(line colors)! + +*legend / labels* + +
+  Gchart.bar( :title => "Matt vs Rob",
+              :data => [[300, 100, 30, 200], [100, 200, 300, 10]], 
+              :bar_colors => 'FF0000,00FF00',
+              :stacked => false, :size => '400x200',
+              :legend => ["Matt's Mojo", "Rob's Mojo"] )
+
+!http://chart.apis.google.com/chart?cht=bvg&chdl=Matt's+Mojo|Rob's+Mojo&chs=400x200&chd=s:9UGo,Uo9C&chco=FF0000,00FF00&chtt=Matt+vs+Rob(legend)! + +
+  Gchart.line( :title => "Matt vs Rob",
+              :data => [[300, 100, 30, 200], [100, 200, 300, 10]], 
+              :bar_colors => ['FF0000','00FF00'],
+              :stacked => false, :size => '400x200',
+              :legend => ["Matt's Mojo", "Rob's Mojo"] )
+
+!http://chart.apis.google.com/chart?cht=lc&chdl=Matt's+Mojo|Rob's+Mojo&chs=400x200&chd=s:9UGo,Uo9C&chco=FF0000,00FF00&chtt=Matt+vs+Rob(line legend)! + + +
+  Gchart.pie_3d(  :title => 'ruby_fu', :size => '400x200',
+                  :data => [10, 45, 45], :labels => ["DHH", "Rob", "Matt"] )
+
+ +!http://chart.apis.google.com/chart?cht=p3&chl=DHH|Rob|Matt&chs=400x200&chd=s:N99&chtt=ruby_fu(labels)! + +*Display axis labels* + +
+  Gchart.line( :data => [300, 100, 30, 200, 100, 200, 300, 10], :axis_with_labels => 'x,y,r')
+
+ +or you can use the other syntax: + +
+  Gchart.line( :data => [300, 100, 30, 200, 100, 200, 300, 10], :axis_with_labels => ['x','y','r'])
+
+ +!http://chart.apis.google.com/chart?cht=lc&chs=300x200&chxt=x,y,r&chd=s:9UGoUo9C(axis with labels)! + +
+  Gchart.line(  :data => [300, 100, 30, 200, 100, 200, 300, 10], :axis_with_labels => 'x',
+                :axis_labels => ['Jan|July|Jan|July|Jan'])
+
+ +or you can use the other syntax: + +
+  Gchart.line(  :data => [300, 100, 30, 200, 100, 200, 300, 10], :axis_with_labels => 'x',
+                :axis_labels => ['Jan','July','Jan','July','Jan'])
+
+ +!http://chart.apis.google.com/chart?cht=lc&chxl=0:|Jan|July|Jan|July|Jan&chs=300x200&chxt=x&chd=s:9UGoUo9C(x labels)! + +*multiple axis labels* + +
+  Gchart.line(  :data => [300, 100, 30, 200, 100, 200, 300, 10], :axis_with_labels => 'x,r',
+                :axis_labels => ['Jan|July|Jan|July|Jan', '2005|2006|2007'])
+
+ +or + +
+  Gchart.line(  :data => [300, 100, 30, 200, 100, 200, 300, 10], :axis_with_labels => 'x,r',
+                :axis_labels => [['Jan','July','Jan','July','Jan'], ['2005','2006','2007']])
+
+ +!http://chart.apis.google.com/chart?cht=lc&chxl=0:|Jan|July|Jan|July|Jan|1:|2005|2006|2007&chs=300x200&chxt=x,r&chd=s:9UGoUo9C(multiple axis labels)! + +(This syntax will probably be improved in the future) + +*custom params* + +I certainly didn't cover the entire API, if you want to add your own params: + +
+  Gchart.line( custom => 'chd=s:93zyvneTTOMJMLIJFHEAECFJGHDBFCFIERcgnpy45879,IJKNUWUWYdnswz047977315533zy1246872tnkgcaZQONHCECAAAAEII&chls=3,6,3|1,1,0')
+
+ +!http://chart.apis.google.com/chart?cht=lc&chs=300x200&chd=s:93zyvneTTOMJMLIJFHEAECFJGHDBFCFIERcgnpy45879,IJKNUWUWYdnswz047977315533zy1246872tnkgcaZQONHCECAAAAEII&chls=3,6,3|1,1,0(Custom)! + +--- + +*Save the chart as a file* + +You might prefer to save the chart instead of using the url, not a problem: + +
+  Gchart.line(:data => [0, 26], :format => 'file')
+
+ +You might want to specify the path and/or the filename used to save your chart: + +
+  Gchart.line(:data => [0, 26], :format => 'file', :filename => 'custom_filename.png')
+
+ +*Insert as an image tag* + +Because, I'm lazy, I also added a custom format: + +
+  Gchart.line(:data => [0, 26], :format => 'img_tag')
+
+ +<img src='http://chart.apis.google.com/chart?chs=300x200&chd=s:A9&cht=lc'/> + +--- + +*Encoding* + +Google Chart API offers "3 types of data encoding":http://code.google.com/apis/chart/#chart_data + + * simple + * text + * extended + +By default this library uses the simple encoding, if you need a different type of encoding, you can change it really easily: + +default / simple: chd=s:9UGoUo9C +
+  Gchart.line( :data => [300, 100, 30, 200, 100, 200, 300, 10] )
+
+ +extended: chd=e:..VVGZqqVVqq..CI +
+  Gchart.line( :data => [300, 100, 30, 200, 100, 200, 300, 10], :encoding => 'extended' )
+
+ +text: chd=t:300,100,30,200,100,200,300,10 +
+  Gchart.line( :data => [300, 100, 30, 200, 100, 200, 300, 10], :encoding => 'text' )
+
+ +(note that the text encoding doesn't use a max value and therefore should be under 100) + +*Max value* + +Simple and extended encoding support the max value option. + +The max value option is a simple way of scaling your graph. The data is converted in chart value with the highest chart value being the highest point on the graph. By default, the calculation is done for you. However you can specify your own maximum or not use a maximum at all. + +
+  Gchart.line( :data => [300, 100, 30, 200, 100, 200, 300, 10] )
+
+ +!http://chart.apis.google.com/chart?cht=lc&chs=300x200&chd=s:9UGoUo9C(Title)! + +
+  Gchart.line( :data => [300, 100, 30, 200, 100, 200, 300, 10], :max_value => 500 )
+
+ +!http://chart.apis.google.com/chart?cht=lc&chs=300x200&chd=s:kMDYMYkB(max 500)! + +
+  Gchart.line( :data => [100, 20, 30, 20, 10, 14, 30, 10], :max_value => false )
+
+ +!http://chart.apis.google.com/chart?cht=lc&chs=300x200&chd=s:_UeUKOeK(real size)! + + + +h2. Repository + +The trunk repository is svn://rubyforge.org/var/svn/googlecharts/trunk for anonymous access. + +h2. License + +This code is free to use under the terms of the MIT license. + +h2. Contact + +Comments are welcome. Send an email to "Matt Aimonetti":mailto:mattaimonetti@gmail.com + diff --git a/website/javascripts/rounded_corners_lite.inc.js b/website/javascripts/rounded_corners_lite.inc.js new file mode 100644 index 0000000..afc3ea3 --- /dev/null +++ b/website/javascripts/rounded_corners_lite.inc.js @@ -0,0 +1,285 @@ + + /**************************************************************** + * * + * curvyCorners * + * ------------ * + * * + * This script generates rounded corners for your divs. * + * * + * Version 1.2.9 * + * Copyright (c) 2006 Cameron Cooke * + * By: Cameron Cooke and Tim Hutchison. * + * * + * * + * Website: http://www.curvycorners.net * + * Email: info@totalinfinity.com * + * Forum: http://www.curvycorners.net/forum/ * + * * + * * + * This library is free software; you can redistribute * + * it and/or modify it under the terms of the GNU * + * Lesser General Public License as published by the * + * Free Software Foundation; either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will * + * be useful, but WITHOUT ANY WARRANTY; without even the * + * implied warranty of MERCHANTABILITY or FITNESS FOR A * + * PARTICULAR PURPOSE. See the GNU Lesser General Public * + * License for more details. * + * * + * You should have received a copy of the GNU Lesser * + * General Public License along with this library; * + * Inc., 59 Temple Place, Suite 330, Boston, * + * MA 02111-1307 USA * + * * + ****************************************************************/ + +var isIE = navigator.userAgent.toLowerCase().indexOf("msie") > -1; var isMoz = document.implementation && document.implementation.createDocument; var isSafari = ((navigator.userAgent.toLowerCase().indexOf('safari')!=-1)&&(navigator.userAgent.toLowerCase().indexOf('mac')!=-1))?true:false; function curvyCorners() +{ if(typeof(arguments[0]) != "object") throw newCurvyError("First parameter of curvyCorners() must be an object."); if(typeof(arguments[1]) != "object" && typeof(arguments[1]) != "string") throw newCurvyError("Second parameter of curvyCorners() must be an object or a class name."); if(typeof(arguments[1]) == "string") +{ var startIndex = 0; var boxCol = getElementsByClass(arguments[1]);} +else +{ var startIndex = 1; var boxCol = arguments;} +var curvyCornersCol = new Array(); if(arguments[0].validTags) +var validElements = arguments[0].validTags; else +var validElements = ["div"]; for(var i = startIndex, j = boxCol.length; i < j; i++) +{ var currentTag = boxCol[i].tagName.toLowerCase(); if(inArray(validElements, currentTag) !== false) +{ curvyCornersCol[curvyCornersCol.length] = new curvyObject(arguments[0], boxCol[i]);} +} +this.objects = curvyCornersCol; this.applyCornersToAll = function() +{ for(var x = 0, k = this.objects.length; x < k; x++) +{ this.objects[x].applyCorners();} +} +} +function curvyObject() +{ this.box = arguments[1]; this.settings = arguments[0]; this.topContainer = null; this.bottomContainer = null; this.masterCorners = new Array(); this.contentDIV = null; var boxHeight = get_style(this.box, "height", "height"); var boxWidth = get_style(this.box, "width", "width"); var borderWidth = get_style(this.box, "borderTopWidth", "border-top-width"); var borderColour = get_style(this.box, "borderTopColor", "border-top-color"); var boxColour = get_style(this.box, "backgroundColor", "background-color"); var backgroundImage = get_style(this.box, "backgroundImage", "background-image"); var boxPosition = get_style(this.box, "position", "position"); var boxPadding = get_style(this.box, "paddingTop", "padding-top"); this.boxHeight = parseInt(((boxHeight != "" && boxHeight != "auto" && boxHeight.indexOf("%") == -1)? boxHeight.substring(0, boxHeight.indexOf("px")) : this.box.scrollHeight)); this.boxWidth = parseInt(((boxWidth != "" && boxWidth != "auto" && boxWidth.indexOf("%") == -1)? boxWidth.substring(0, boxWidth.indexOf("px")) : this.box.scrollWidth)); this.borderWidth = parseInt(((borderWidth != "" && borderWidth.indexOf("px") !== -1)? borderWidth.slice(0, borderWidth.indexOf("px")) : 0)); this.boxColour = format_colour(boxColour); this.boxPadding = parseInt(((boxPadding != "" && boxPadding.indexOf("px") !== -1)? boxPadding.slice(0, boxPadding.indexOf("px")) : 0)); this.borderColour = format_colour(borderColour); this.borderString = this.borderWidth + "px" + " solid " + this.borderColour; this.backgroundImage = ((backgroundImage != "none")? backgroundImage : ""); this.boxContent = this.box.innerHTML; if(boxPosition != "absolute") this.box.style.position = "relative"; this.box.style.padding = "0px"; if(isIE && boxWidth == "auto" && boxHeight == "auto") this.box.style.width = "100%"; if(this.settings.autoPad == true && this.boxPadding > 0) +this.box.innerHTML = ""; this.applyCorners = function() +{ for(var t = 0; t < 2; t++) +{ switch(t) +{ case 0: +if(this.settings.tl || this.settings.tr) +{ var newMainContainer = document.createElement("DIV"); newMainContainer.style.width = "100%"; newMainContainer.style.fontSize = "1px"; newMainContainer.style.overflow = "hidden"; newMainContainer.style.position = "absolute"; newMainContainer.style.paddingLeft = this.borderWidth + "px"; newMainContainer.style.paddingRight = this.borderWidth + "px"; var topMaxRadius = Math.max(this.settings.tl ? this.settings.tl.radius : 0, this.settings.tr ? this.settings.tr.radius : 0); newMainContainer.style.height = topMaxRadius + "px"; newMainContainer.style.top = 0 - topMaxRadius + "px"; newMainContainer.style.left = 0 - this.borderWidth + "px"; this.topContainer = this.box.appendChild(newMainContainer);} +break; case 1: +if(this.settings.bl || this.settings.br) +{ var newMainContainer = document.createElement("DIV"); newMainContainer.style.width = "100%"; newMainContainer.style.fontSize = "1px"; newMainContainer.style.overflow = "hidden"; newMainContainer.style.position = "absolute"; newMainContainer.style.paddingLeft = this.borderWidth + "px"; newMainContainer.style.paddingRight = this.borderWidth + "px"; var botMaxRadius = Math.max(this.settings.bl ? this.settings.bl.radius : 0, this.settings.br ? this.settings.br.radius : 0); newMainContainer.style.height = botMaxRadius + "px"; newMainContainer.style.bottom = 0 - botMaxRadius + "px"; newMainContainer.style.left = 0 - this.borderWidth + "px"; this.bottomContainer = this.box.appendChild(newMainContainer);} +break;} +} +if(this.topContainer) this.box.style.borderTopWidth = "0px"; if(this.bottomContainer) this.box.style.borderBottomWidth = "0px"; var corners = ["tr", "tl", "br", "bl"]; for(var i in corners) +{ if(i > -1 < 4) +{ var cc = corners[i]; if(!this.settings[cc]) +{ if(((cc == "tr" || cc == "tl") && this.topContainer != null) || ((cc == "br" || cc == "bl") && this.bottomContainer != null)) +{ var newCorner = document.createElement("DIV"); newCorner.style.position = "relative"; newCorner.style.fontSize = "1px"; newCorner.style.overflow = "hidden"; if(this.backgroundImage == "") +newCorner.style.backgroundColor = this.boxColour; else +newCorner.style.backgroundImage = this.backgroundImage; switch(cc) +{ case "tl": +newCorner.style.height = topMaxRadius - this.borderWidth + "px"; newCorner.style.marginRight = this.settings.tr.radius - (this.borderWidth*2) + "px"; newCorner.style.borderLeft = this.borderString; newCorner.style.borderTop = this.borderString; newCorner.style.left = -this.borderWidth + "px"; break; case "tr": +newCorner.style.height = topMaxRadius - this.borderWidth + "px"; newCorner.style.marginLeft = this.settings.tl.radius - (this.borderWidth*2) + "px"; newCorner.style.borderRight = this.borderString; newCorner.style.borderTop = this.borderString; newCorner.style.backgroundPosition = "-" + (topMaxRadius + this.borderWidth) + "px 0px"; newCorner.style.left = this.borderWidth + "px"; break; case "bl": +newCorner.style.height = botMaxRadius - this.borderWidth + "px"; newCorner.style.marginRight = this.settings.br.radius - (this.borderWidth*2) + "px"; newCorner.style.borderLeft = this.borderString; newCorner.style.borderBottom = this.borderString; newCorner.style.left = -this.borderWidth + "px"; newCorner.style.backgroundPosition = "-" + (this.borderWidth) + "px -" + (this.boxHeight + (botMaxRadius + this.borderWidth)) + "px"; break; case "br": +newCorner.style.height = botMaxRadius - this.borderWidth + "px"; newCorner.style.marginLeft = this.settings.bl.radius - (this.borderWidth*2) + "px"; newCorner.style.borderRight = this.borderString; newCorner.style.borderBottom = this.borderString; newCorner.style.left = this.borderWidth + "px" +newCorner.style.backgroundPosition = "-" + (botMaxRadius + this.borderWidth) + "px -" + (this.boxHeight + (botMaxRadius + this.borderWidth)) + "px"; break;} +} +} +else +{ if(this.masterCorners[this.settings[cc].radius]) +{ var newCorner = this.masterCorners[this.settings[cc].radius].cloneNode(true);} +else +{ var newCorner = document.createElement("DIV"); newCorner.style.height = this.settings[cc].radius + "px"; newCorner.style.width = this.settings[cc].radius + "px"; newCorner.style.position = "absolute"; newCorner.style.fontSize = "1px"; newCorner.style.overflow = "hidden"; var borderRadius = parseInt(this.settings[cc].radius - this.borderWidth); for(var intx = 0, j = this.settings[cc].radius; intx < j; intx++) +{ if((intx +1) >= borderRadius) +var y1 = -1; else +var y1 = (Math.floor(Math.sqrt(Math.pow(borderRadius, 2) - Math.pow((intx+1), 2))) - 1); if(borderRadius != j) +{ if((intx) >= borderRadius) +var y2 = -1; else +var y2 = Math.ceil(Math.sqrt(Math.pow(borderRadius,2) - Math.pow(intx, 2))); if((intx+1) >= j) +var y3 = -1; else +var y3 = (Math.floor(Math.sqrt(Math.pow(j ,2) - Math.pow((intx+1), 2))) - 1);} +if((intx) >= j) +var y4 = -1; else +var y4 = Math.ceil(Math.sqrt(Math.pow(j ,2) - Math.pow(intx, 2))); if(y1 > -1) this.drawPixel(intx, 0, this.boxColour, 100, (y1+1), newCorner, -1, this.settings[cc].radius); if(borderRadius != j) +{ for(var inty = (y1 + 1); inty < y2; inty++) +{ if(this.settings.antiAlias) +{ if(this.backgroundImage != "") +{ var borderFract = (pixelFraction(intx, inty, borderRadius) * 100); if(borderFract < 30) +{ this.drawPixel(intx, inty, this.borderColour, 100, 1, newCorner, 0, this.settings[cc].radius);} +else +{ this.drawPixel(intx, inty, this.borderColour, 100, 1, newCorner, -1, this.settings[cc].radius);} +} +else +{ var pixelcolour = BlendColour(this.boxColour, this.borderColour, pixelFraction(intx, inty, borderRadius)); this.drawPixel(intx, inty, pixelcolour, 100, 1, newCorner, 0, this.settings[cc].radius, cc);} +} +} +if(this.settings.antiAlias) +{ if(y3 >= y2) +{ if (y2 == -1) y2 = 0; this.drawPixel(intx, y2, this.borderColour, 100, (y3 - y2 + 1), newCorner, 0, 0);} +} +else +{ if(y3 >= y1) +{ this.drawPixel(intx, (y1 + 1), this.borderColour, 100, (y3 - y1), newCorner, 0, 0);} +} +var outsideColour = this.borderColour;} +else +{ var outsideColour = this.boxColour; var y3 = y1;} +if(this.settings.antiAlias) +{ for(var inty = (y3 + 1); inty < y4; inty++) +{ this.drawPixel(intx, inty, outsideColour, (pixelFraction(intx, inty , j) * 100), 1, newCorner, ((this.borderWidth > 0)? 0 : -1), this.settings[cc].radius);} +} +} +this.masterCorners[this.settings[cc].radius] = newCorner.cloneNode(true);} +if(cc != "br") +{ for(var t = 0, k = newCorner.childNodes.length; t < k; t++) +{ var pixelBar = newCorner.childNodes[t]; var pixelBarTop = parseInt(pixelBar.style.top.substring(0, pixelBar.style.top.indexOf("px"))); var pixelBarLeft = parseInt(pixelBar.style.left.substring(0, pixelBar.style.left.indexOf("px"))); var pixelBarHeight = parseInt(pixelBar.style.height.substring(0, pixelBar.style.height.indexOf("px"))); if(cc == "tl" || cc == "bl"){ pixelBar.style.left = this.settings[cc].radius -pixelBarLeft -1 + "px";} +if(cc == "tr" || cc == "tl"){ pixelBar.style.top = this.settings[cc].radius -pixelBarHeight -pixelBarTop + "px";} +switch(cc) +{ case "tr": +pixelBar.style.backgroundPosition = "-" + Math.abs((this.boxWidth - this.settings[cc].radius + this.borderWidth) + pixelBarLeft) + "px -" + Math.abs(this.settings[cc].radius -pixelBarHeight -pixelBarTop - this.borderWidth) + "px"; break; case "tl": +pixelBar.style.backgroundPosition = "-" + Math.abs((this.settings[cc].radius -pixelBarLeft -1) - this.borderWidth) + "px -" + Math.abs(this.settings[cc].radius -pixelBarHeight -pixelBarTop - this.borderWidth) + "px"; break; case "bl": +pixelBar.style.backgroundPosition = "-" + Math.abs((this.settings[cc].radius -pixelBarLeft -1) - this.borderWidth) + "px -" + Math.abs((this.boxHeight + this.settings[cc].radius + pixelBarTop) -this.borderWidth) + "px"; break;} +} +} +} +if(newCorner) +{ switch(cc) +{ case "tl": +if(newCorner.style.position == "absolute") newCorner.style.top = "0px"; if(newCorner.style.position == "absolute") newCorner.style.left = "0px"; if(this.topContainer) this.topContainer.appendChild(newCorner); break; case "tr": +if(newCorner.style.position == "absolute") newCorner.style.top = "0px"; if(newCorner.style.position == "absolute") newCorner.style.right = "0px"; if(this.topContainer) this.topContainer.appendChild(newCorner); break; case "bl": +if(newCorner.style.position == "absolute") newCorner.style.bottom = "0px"; if(newCorner.style.position == "absolute") newCorner.style.left = "0px"; if(this.bottomContainer) this.bottomContainer.appendChild(newCorner); break; case "br": +if(newCorner.style.position == "absolute") newCorner.style.bottom = "0px"; if(newCorner.style.position == "absolute") newCorner.style.right = "0px"; if(this.bottomContainer) this.bottomContainer.appendChild(newCorner); break;} +} +} +} +var radiusDiff = new Array(); radiusDiff["t"] = Math.abs(this.settings.tl.radius - this.settings.tr.radius) +radiusDiff["b"] = Math.abs(this.settings.bl.radius - this.settings.br.radius); for(z in radiusDiff) +{ if(z == "t" || z == "b") +{ if(radiusDiff[z]) +{ var smallerCornerType = ((this.settings[z + "l"].radius < this.settings[z + "r"].radius)? z +"l" : z +"r"); var newFiller = document.createElement("DIV"); newFiller.style.height = radiusDiff[z] + "px"; newFiller.style.width = this.settings[smallerCornerType].radius+ "px" +newFiller.style.position = "absolute"; newFiller.style.fontSize = "1px"; newFiller.style.overflow = "hidden"; newFiller.style.backgroundColor = this.boxColour; switch(smallerCornerType) +{ case "tl": +newFiller.style.bottom = "0px"; newFiller.style.left = "0px"; newFiller.style.borderLeft = this.borderString; this.topContainer.appendChild(newFiller); break; case "tr": +newFiller.style.bottom = "0px"; newFiller.style.right = "0px"; newFiller.style.borderRight = this.borderString; this.topContainer.appendChild(newFiller); break; case "bl": +newFiller.style.top = "0px"; newFiller.style.left = "0px"; newFiller.style.borderLeft = this.borderString; this.bottomContainer.appendChild(newFiller); break; case "br": +newFiller.style.top = "0px"; newFiller.style.right = "0px"; newFiller.style.borderRight = this.borderString; this.bottomContainer.appendChild(newFiller); break;} +} +var newFillerBar = document.createElement("DIV"); newFillerBar.style.position = "relative"; newFillerBar.style.fontSize = "1px"; newFillerBar.style.overflow = "hidden"; newFillerBar.style.backgroundColor = this.boxColour; newFillerBar.style.backgroundImage = this.backgroundImage; switch(z) +{ case "t": +if(this.topContainer) +{ if(this.settings.tl.radius && this.settings.tr.radius) +{ newFillerBar.style.height = topMaxRadius - this.borderWidth + "px"; newFillerBar.style.marginLeft = this.settings.tl.radius - this.borderWidth + "px"; newFillerBar.style.marginRight = this.settings.tr.radius - this.borderWidth + "px"; newFillerBar.style.borderTop = this.borderString; if(this.backgroundImage != "") +newFillerBar.style.backgroundPosition = "-" + (topMaxRadius + this.borderWidth) + "px 0px"; this.topContainer.appendChild(newFillerBar);} +this.box.style.backgroundPosition = "0px -" + (topMaxRadius - this.borderWidth) + "px";} +break; case "b": +if(this.bottomContainer) +{ if(this.settings.bl.radius && this.settings.br.radius) +{ newFillerBar.style.height = botMaxRadius - this.borderWidth + "px"; newFillerBar.style.marginLeft = this.settings.bl.radius - this.borderWidth + "px"; newFillerBar.style.marginRight = this.settings.br.radius - this.borderWidth + "px"; newFillerBar.style.borderBottom = this.borderString; if(this.backgroundImage != "") +newFillerBar.style.backgroundPosition = "-" + (botMaxRadius + this.borderWidth) + "px -" + (this.boxHeight + (topMaxRadius + this.borderWidth)) + "px"; this.bottomContainer.appendChild(newFillerBar);} +} +break;} +} +} +if(this.settings.autoPad == true && this.boxPadding > 0) +{ var contentContainer = document.createElement("DIV"); contentContainer.style.position = "relative"; contentContainer.innerHTML = this.boxContent; contentContainer.className = "autoPadDiv"; var topPadding = Math.abs(topMaxRadius - this.boxPadding); var botPadding = Math.abs(botMaxRadius - this.boxPadding); if(topMaxRadius < this.boxPadding) +contentContainer.style.paddingTop = topPadding + "px"; if(botMaxRadius < this.boxPadding) +contentContainer.style.paddingBottom = botMaxRadius + "px"; contentContainer.style.paddingLeft = this.boxPadding + "px"; contentContainer.style.paddingRight = this.boxPadding + "px"; this.contentDIV = this.box.appendChild(contentContainer);} +} +this.drawPixel = function(intx, inty, colour, transAmount, height, newCorner, image, cornerRadius) +{ var pixel = document.createElement("DIV"); pixel.style.height = height + "px"; pixel.style.width = "1px"; pixel.style.position = "absolute"; pixel.style.fontSize = "1px"; pixel.style.overflow = "hidden"; var topMaxRadius = Math.max(this.settings["tr"].radius, this.settings["tl"].radius); if(image == -1 && this.backgroundImage != "") +{ pixel.style.backgroundImage = this.backgroundImage; pixel.style.backgroundPosition = "-" + (this.boxWidth - (cornerRadius - intx) + this.borderWidth) + "px -" + ((this.boxHeight + topMaxRadius + inty) -this.borderWidth) + "px";} +else +{ pixel.style.backgroundColor = colour;} +if (transAmount != 100) +setOpacity(pixel, transAmount); pixel.style.top = inty + "px"; pixel.style.left = intx + "px"; newCorner.appendChild(pixel);} +} +function insertAfter(parent, node, referenceNode) +{ parent.insertBefore(node, referenceNode.nextSibling);} +function BlendColour(Col1, Col2, Col1Fraction) +{ var red1 = parseInt(Col1.substr(1,2),16); var green1 = parseInt(Col1.substr(3,2),16); var blue1 = parseInt(Col1.substr(5,2),16); var red2 = parseInt(Col2.substr(1,2),16); var green2 = parseInt(Col2.substr(3,2),16); var blue2 = parseInt(Col2.substr(5,2),16); if(Col1Fraction > 1 || Col1Fraction < 0) Col1Fraction = 1; var endRed = Math.round((red1 * Col1Fraction) + (red2 * (1 - Col1Fraction))); if(endRed > 255) endRed = 255; if(endRed < 0) endRed = 0; var endGreen = Math.round((green1 * Col1Fraction) + (green2 * (1 - Col1Fraction))); if(endGreen > 255) endGreen = 255; if(endGreen < 0) endGreen = 0; var endBlue = Math.round((blue1 * Col1Fraction) + (blue2 * (1 - Col1Fraction))); if(endBlue > 255) endBlue = 255; if(endBlue < 0) endBlue = 0; return "#" + IntToHex(endRed)+ IntToHex(endGreen)+ IntToHex(endBlue);} +function IntToHex(strNum) +{ base = strNum / 16; rem = strNum % 16; base = base - (rem / 16); baseS = MakeHex(base); remS = MakeHex(rem); return baseS + '' + remS;} +function MakeHex(x) +{ if((x >= 0) && (x <= 9)) +{ return x;} +else +{ switch(x) +{ case 10: return "A"; case 11: return "B"; case 12: return "C"; case 13: return "D"; case 14: return "E"; case 15: return "F";} +} +} +function pixelFraction(x, y, r) +{ var pixelfraction = 0; var xvalues = new Array(1); var yvalues = new Array(1); var point = 0; var whatsides = ""; var intersect = Math.sqrt((Math.pow(r,2) - Math.pow(x,2))); if ((intersect >= y) && (intersect < (y+1))) +{ whatsides = "Left"; xvalues[point] = 0; yvalues[point] = intersect - y; point = point + 1;} +var intersect = Math.sqrt((Math.pow(r,2) - Math.pow(y+1,2))); if ((intersect >= x) && (intersect < (x+1))) +{ whatsides = whatsides + "Top"; xvalues[point] = intersect - x; yvalues[point] = 1; point = point + 1;} +var intersect = Math.sqrt((Math.pow(r,2) - Math.pow(x+1,2))); if ((intersect >= y) && (intersect < (y+1))) +{ whatsides = whatsides + "Right"; xvalues[point] = 1; yvalues[point] = intersect - y; point = point + 1;} +var intersect = Math.sqrt((Math.pow(r,2) - Math.pow(y,2))); if ((intersect >= x) && (intersect < (x+1))) +{ whatsides = whatsides + "Bottom"; xvalues[point] = intersect - x; yvalues[point] = 0;} +switch (whatsides) +{ case "LeftRight": +pixelfraction = Math.min(yvalues[0],yvalues[1]) + ((Math.max(yvalues[0],yvalues[1]) - Math.min(yvalues[0],yvalues[1]))/2); break; case "TopRight": +pixelfraction = 1-(((1-xvalues[0])*(1-yvalues[1]))/2); break; case "TopBottom": +pixelfraction = Math.min(xvalues[0],xvalues[1]) + ((Math.max(xvalues[0],xvalues[1]) - Math.min(xvalues[0],xvalues[1]))/2); break; case "LeftBottom": +pixelfraction = (yvalues[0]*xvalues[1])/2; break; default: +pixelfraction = 1;} +return pixelfraction;} +function rgb2Hex(rgbColour) +{ try{ var rgbArray = rgb2Array(rgbColour); var red = parseInt(rgbArray[0]); var green = parseInt(rgbArray[1]); var blue = parseInt(rgbArray[2]); var hexColour = "#" + IntToHex(red) + IntToHex(green) + IntToHex(blue);} +catch(e){ alert("There was an error converting the RGB value to Hexadecimal in function rgb2Hex");} +return hexColour;} +function rgb2Array(rgbColour) +{ var rgbValues = rgbColour.substring(4, rgbColour.indexOf(")")); var rgbArray = rgbValues.split(", "); return rgbArray;} +function setOpacity(obj, opacity) +{ opacity = (opacity == 100)?99.999:opacity; if(isSafari && obj.tagName != "IFRAME") +{ var rgbArray = rgb2Array(obj.style.backgroundColor); var red = parseInt(rgbArray[0]); var green = parseInt(rgbArray[1]); var blue = parseInt(rgbArray[2]); obj.style.backgroundColor = "rgba(" + red + ", " + green + ", " + blue + ", " + opacity/100 + ")";} +else if(typeof(obj.style.opacity) != "undefined") +{ obj.style.opacity = opacity/100;} +else if(typeof(obj.style.MozOpacity) != "undefined") +{ obj.style.MozOpacity = opacity/100;} +else if(typeof(obj.style.filter) != "undefined") +{ obj.style.filter = "alpha(opacity:" + opacity + ")";} +else if(typeof(obj.style.KHTMLOpacity) != "undefined") +{ obj.style.KHTMLOpacity = opacity/100;} +} +function inArray(array, value) +{ for(var i = 0; i < array.length; i++){ if (array[i] === value) return i;} +return false;} +function inArrayKey(array, value) +{ for(key in array){ if(key === value) return true;} +return false;} +function addEvent(elm, evType, fn, useCapture) { if (elm.addEventListener) { elm.addEventListener(evType, fn, useCapture); return true;} +else if (elm.attachEvent) { var r = elm.attachEvent('on' + evType, fn); return r;} +else { elm['on' + evType] = fn;} +} +function removeEvent(obj, evType, fn, useCapture){ if (obj.removeEventListener){ obj.removeEventListener(evType, fn, useCapture); return true;} else if (obj.detachEvent){ var r = obj.detachEvent("on"+evType, fn); return r;} else { alert("Handler could not be removed");} +} +function format_colour(colour) +{ var returnColour = "#ffffff"; if(colour != "" && colour != "transparent") +{ if(colour.substr(0, 3) == "rgb") +{ returnColour = rgb2Hex(colour);} +else if(colour.length == 4) +{ returnColour = "#" + colour.substring(1, 2) + colour.substring(1, 2) + colour.substring(2, 3) + colour.substring(2, 3) + colour.substring(3, 4) + colour.substring(3, 4);} +else +{ returnColour = colour;} +} +return returnColour;} +function get_style(obj, property, propertyNS) +{ try +{ if(obj.currentStyle) +{ var returnVal = eval("obj.currentStyle." + property);} +else +{ if(isSafari && obj.style.display == "none") +{ obj.style.display = ""; var wasHidden = true;} +var returnVal = document.defaultView.getComputedStyle(obj, '').getPropertyValue(propertyNS); if(isSafari && wasHidden) +{ obj.style.display = "none";} +} +} +catch(e) +{ } +return returnVal;} +function getElementsByClass(searchClass, node, tag) +{ var classElements = new Array(); if(node == null) +node = document; if(tag == null) +tag = '*'; var els = node.getElementsByTagName(tag); var elsLen = els.length; var pattern = new RegExp("(^|\s)"+searchClass+"(\s|$)"); for (i = 0, j = 0; i < elsLen; i++) +{ if(pattern.test(els[i].className)) +{ classElements[j] = els[i]; j++;} +} +return classElements;} +function newCurvyError(errorMessage) +{ return new Error("curvyCorners Error:\n" + errorMessage) +} diff --git a/website/stylesheets/screen.css b/website/stylesheets/screen.css new file mode 100644 index 0000000..2c84cd0 --- /dev/null +++ b/website/stylesheets/screen.css @@ -0,0 +1,138 @@ +body { + background-color: #E1D1F1; + font-family: "Georgia", sans-serif; + font-size: 16px; + line-height: 1.6em; + padding: 1.6em 0 0 0; + color: #333; +} +h1, h2, h3, h4, h5, h6 { + color: #444; +} +h1 { + font-family: sans-serif; + font-weight: normal; + font-size: 4em; + line-height: 0.8em; + letter-spacing: -0.1ex; + margin: 5px; +} +li { + padding: 0; + margin: 0; + list-style-type: square; +} +a { + color: #5E5AFF; + background-color: #DAC; + font-weight: normal; + text-decoration: underline; +} +blockquote { + font-size: 90%; + font-style: italic; + border-left: 1px solid #111; + padding-left: 1em; +} +.caps { + font-size: 80%; +} + +#main { + width: 45em; + padding: 0; + margin: 0 auto; +} +.coda { + text-align: right; + color: #77f; + font-size: smaller; +} + +table { + font-size: 90%; + line-height: 1.4em; + color: #ff8; + background-color: #111; + padding: 2px 10px 2px 10px; + border-style: dashed; +} + +th { + color: #fff; +} + +td { + padding: 2px 10px 2px 10px; +} + +.success { + color: #0CC52B; +} + +.failed { + color: #E90A1B; +} + +.unknown { + color: #995000; +} +pre, code { + font-family: monospace; + font-size: 90%; + line-height: 1.4em; + color: #ff8; + background-color: #111; + padding: 2px 10px 2px 10px; +} +.comment { color: #aaa; font-style: italic; } +.keyword { color: #eff; font-weight: bold; } +.punct { color: #eee; font-weight: bold; } +.symbol { color: #0bb; } +.string { color: #6b4; } +.ident { color: #ff8; } +.constant { color: #66f; } +.regex { color: #ec6; } +.number { color: #F99; } +.expr { color: #227; } + +#version { + float: right; + text-align: right; + font-family: sans-serif; + font-weight: normal; + background-color: #B3ABFF; + color: #141331; + padding: 15px 20px 10px 20px; + margin: 0 auto; + margin-top: 15px; + border: 3px solid #141331; +} + +#version .numbers { + display: block; + font-size: 4em; + line-height: 0.8em; + letter-spacing: -0.1ex; + margin-bottom: 15px; +} + +#version p { + text-decoration: none; + color: #141331; + background-color: #B3ABFF; + margin: 0; + padding: 0; +} + +#version a { + text-decoration: none; + color: #141331; + background-color: #B3ABFF; +} + +.clickable { + cursor: pointer; + cursor: hand; +} + diff --git a/website/template.rhtml b/website/template.rhtml new file mode 100644 index 0000000..cad593b --- /dev/null +++ b/website/template.rhtml @@ -0,0 +1,53 @@ + + + + + + + <%= title %> + + + + + + +
+ +

<%= title %>

+
+

Get Version

+ <%= version %> +
+ <%= body %> +

+ Matt Aimonetti, <%= modified.pretty %>
+ Theme extended from Paul Battley +

+
+ + + + + +