]> projects.mako.cc - selectricity-live/commitdiff
Added the Gruff library to the lib/ directory of the the rails folder, and the
author<jlsharps@mit.edu> <>
Thu, 30 Aug 2007 22:41:45 +0000 (18:41 -0400)
committer<jlsharps@mit.edu> <>
Thu, 30 Aug 2007 22:41:45 +0000 (18:41 -0400)
environment require statement now uses that one. Made modifcations to Gruff
library to alternate placement of labels and crowded bar graphs. Also made a
table for approval vote method.

72 files changed:
app/controllers/graph_controller.rb
app/controllers/quickvote_controller.rb
app/views/quickvote/_approval_table.rhtml [new file with mode: 0644]
app/views/quickvote/_pref_tables.rhtml
app/views/quickvote/_result_approval.rhtml
config/environment.rb
lib/gruff-0.2.8/CHANGELOG [new file with mode: 0644]
lib/gruff-0.2.8/MIT-LICENSE [new file with mode: 0644]
lib/gruff-0.2.8/Manifest.txt [new file with mode: 0644]
lib/gruff-0.2.8/README.txt [new file with mode: 0644]
lib/gruff-0.2.8/Rakefile [new file with mode: 0644]
lib/gruff-0.2.8/assets/city_scene/background/0000.png [new file with mode: 0644]
lib/gruff-0.2.8/assets/city_scene/background/0600.png [new file with mode: 0644]
lib/gruff-0.2.8/assets/city_scene/background/2000.png [new file with mode: 0644]
lib/gruff-0.2.8/assets/city_scene/clouds/cloudy.png [new file with mode: 0644]
lib/gruff-0.2.8/assets/city_scene/clouds/partly_cloudy.png [new file with mode: 0644]
lib/gruff-0.2.8/assets/city_scene/clouds/stormy.png [new file with mode: 0644]
lib/gruff-0.2.8/assets/city_scene/grass/default.png [new file with mode: 0644]
lib/gruff-0.2.8/assets/city_scene/haze/true.png [new file with mode: 0644]
lib/gruff-0.2.8/assets/city_scene/number_sample/1.png [new file with mode: 0644]
lib/gruff-0.2.8/assets/city_scene/number_sample/2.png [new file with mode: 0644]
lib/gruff-0.2.8/assets/city_scene/number_sample/default.png [new file with mode: 0644]
lib/gruff-0.2.8/assets/city_scene/sky/0000.png [new file with mode: 0644]
lib/gruff-0.2.8/assets/city_scene/sky/0200.png [new file with mode: 0644]
lib/gruff-0.2.8/assets/city_scene/sky/0400.png [new file with mode: 0644]
lib/gruff-0.2.8/assets/city_scene/sky/0600.png [new file with mode: 0644]
lib/gruff-0.2.8/assets/city_scene/sky/0800.png [new file with mode: 0644]
lib/gruff-0.2.8/assets/city_scene/sky/1000.png [new file with mode: 0644]
lib/gruff-0.2.8/assets/city_scene/sky/1200.png [new file with mode: 0644]
lib/gruff-0.2.8/assets/city_scene/sky/1400.png [new file with mode: 0644]
lib/gruff-0.2.8/assets/city_scene/sky/1500.png [new file with mode: 0644]
lib/gruff-0.2.8/assets/city_scene/sky/1700.png [new file with mode: 0644]
lib/gruff-0.2.8/assets/city_scene/sky/2000.png [new file with mode: 0644]
lib/gruff-0.2.8/assets/pc306715.jpg [new file with mode: 0644]
lib/gruff-0.2.8/lib/gruff.rb [new file with mode: 0644]
lib/gruff-0.2.8/lib/gruff/accumulator_bar.rb [new file with mode: 0644]
lib/gruff-0.2.8/lib/gruff/area.rb [new file with mode: 0644]
lib/gruff-0.2.8/lib/gruff/bar.rb [new file with mode: 0644]
lib/gruff-0.2.8/lib/gruff/bar_conversion.rb [new file with mode: 0644]
lib/gruff-0.2.8/lib/gruff/base.rb [new file with mode: 0644]
lib/gruff-0.2.8/lib/gruff/deprecated.rb [new file with mode: 0644]
lib/gruff-0.2.8/lib/gruff/line.rb [new file with mode: 0644]
lib/gruff-0.2.8/lib/gruff/mini/bar.rb [new file with mode: 0644]
lib/gruff-0.2.8/lib/gruff/mini/legend.rb [new file with mode: 0644]
lib/gruff-0.2.8/lib/gruff/mini/pie.rb [new file with mode: 0644]
lib/gruff-0.2.8/lib/gruff/mini/side_bar.rb [new file with mode: 0644]
lib/gruff-0.2.8/lib/gruff/net.rb [new file with mode: 0644]
lib/gruff-0.2.8/lib/gruff/photo_bar.rb [new file with mode: 0644]
lib/gruff-0.2.8/lib/gruff/pie.rb [new file with mode: 0644]
lib/gruff-0.2.8/lib/gruff/scene.rb [new file with mode: 0644]
lib/gruff-0.2.8/lib/gruff/side_bar.rb [new file with mode: 0644]
lib/gruff-0.2.8/lib/gruff/side_stacked_bar.rb [new file with mode: 0644]
lib/gruff-0.2.8/lib/gruff/spider.rb [new file with mode: 0644]
lib/gruff-0.2.8/lib/gruff/stacked_bar.rb [new file with mode: 0644]
lib/gruff-0.2.8/test/gruff_test_case.rb [new file with mode: 0644]
lib/gruff-0.2.8/test/test_accumulator_bar.rb [new file with mode: 0644]
lib/gruff-0.2.8/test/test_area.rb [new file with mode: 0644]
lib/gruff-0.2.8/test/test_bar.rb [new file with mode: 0644]
lib/gruff-0.2.8/test/test_base.rb [new file with mode: 0644]
lib/gruff-0.2.8/test/test_legend.rb [new file with mode: 0644]
lib/gruff-0.2.8/test/test_line.rb [new file with mode: 0644]
lib/gruff-0.2.8/test/test_mini_bar.rb [new file with mode: 0644]
lib/gruff-0.2.8/test/test_mini_pie.rb [new file with mode: 0644]
lib/gruff-0.2.8/test/test_mini_side_bar.rb [new file with mode: 0644]
lib/gruff-0.2.8/test/test_net.rb [new file with mode: 0644]
lib/gruff-0.2.8/test/test_photo.rb [new file with mode: 0644]
lib/gruff-0.2.8/test/test_pie.rb [new file with mode: 0644]
lib/gruff-0.2.8/test/test_scene.rb [new file with mode: 0644]
lib/gruff-0.2.8/test/test_side_bar.rb [new file with mode: 0644]
lib/gruff-0.2.8/test/test_sidestacked_bar.rb [new file with mode: 0644]
lib/gruff-0.2.8/test/test_spider.rb [new file with mode: 0644]
lib/gruff-0.2.8/test/test_stacked_bar.rb [new file with mode: 0644]

index b019aeb52b91e230eab971af3b4e47d8ccf5a1ac..413ebb0c10617d9e5ac38e2cc1a3ed0e74933008 100644 (file)
@@ -108,14 +108,25 @@ class GraphController < ApplicationController
     @election.results unless @election.borda_result
     data, labels = get_borda_points(@election.borda_result)
     
     @election.results unless @election.borda_result
     data, labels = get_borda_points(@election.borda_result)
     
+    size = "400x300"
+    size = "580x300" if @election.candidates.size >= 5
+    
+   if @election.candidates.size >= 5
+     marker_font_size = 17
+   else
+     marker_font_size = 20
+   end
+    
     graph = GruffGraff.new( :graph_type => Gruff::Bar,
                             :data_name => @election.name,
                             :data => data,
                             :interval_labels => labels,
     graph = GruffGraff.new( :graph_type => Gruff::Bar,
                             :data_name => @election.name,
                             :data => data,
                             :interval_labels => labels,
+                            :size => size,
                             :title => "Points Per Candidate",
                             :marker_color => '#999999',
                             :title => "Points Per Candidate",
                             :marker_color => '#999999',
+                            :marker_font_size => marker_font_size,
                             :y_axis_label => "Points",
                             :y_axis_label => "Points",
-                            :x_axis_label => "Candidate")
+                            :x_axis_label => "Candidates")
     send_data(*graph.output)
   end
   #Acording to Tufte, small, concomparitive, highly labeled data sets usually
     send_data(*graph.output)
   end
   #Acording to Tufte, small, concomparitive, highly labeled data sets usually
index a5c8065035ef1147d1428831cc09dcd7a067c907..da2903d1a01fa3fecf1f5d0a665c49801f5ff538 100644 (file)
@@ -202,6 +202,7 @@ class QuickvoteController < ApplicationController
     @results = @election.results
     @candidates = {}
     @election.candidates.each {|c| @candidates[c.id] = c}
     @results = @election.results
     @candidates = {}
     @election.candidates.each {|c| @candidates[c.id] = c}
+    @names = @election.names_by_id
     @sidebar_content = render_to_string :partial => 'results_sidebar'
   end
   
     @sidebar_content = render_to_string :partial => 'results_sidebar'
   end
   
diff --git a/app/views/quickvote/_approval_table.rhtml b/app/views/quickvote/_approval_table.rhtml
new file mode 100644 (file)
index 0000000..85185ee
--- /dev/null
@@ -0,0 +1,15 @@
+<table class="preftable">
+  <tr>
+       <td>Candidate</td>
+         <% @election.approval_result.points.keys.sort.each do |candidate| %>
+               <th><%=h @names[candidate] %></th>
+         <% end -%>
+  </tr>
+       
+  <tr>
+       <td> Times Approved </td>
+       <% @election.approval_result.points.values.sort.each do |points| %>
+               <td><%= points %></td>
+       <% end -%>
+  </tr>
+</table>
\ No newline at end of file
index 18e5aa38f81f3cc5d91aa23c76f4d4063fbb585f..628ce55ca5d873474e149fdc4097109cf8512799 100644 (file)
@@ -2,7 +2,6 @@
 <% voters = @election.voters.size %>
 <% matrix = @election.condorcet_result.matrix %>
 <% victories = @election.condorcet_result.victories_and_ties %>
 <% voters = @election.voters.size %>
 <% matrix = @election.condorcet_result.matrix %>
 <% victories = @election.condorcet_result.victories_and_ties %>
-<% names = @election.names_by_id %>
 
 <!-- This table shows how many times each choice was ranked above the other, 
        with percentages-->
 
 <!-- This table shows how many times each choice was ranked above the other, 
        with percentages-->
   <tr>
        <td></td>
        <% candidates.each do |candidate| -%>
   <tr>
        <td></td>
        <% candidates.each do |candidate| -%>
-         <th><%=h names[candidate] -%></th>
+         <th><%=h @names[candidate] -%></th>
   <% end -%>
  </tr>
 
 <% candidates.each do |winner| -%>
   <tr>
   <% end -%>
  </tr>
 
 <% candidates.each do |winner| -%>
   <tr>
-       <th><%=h names[winner] %></th>
+       <th><%=h @names[winner] %></th>
   <% candidates.each do |loser| -%> 
     <% if winner == loser -%>
       <td> -- </td>
   <% candidates.each do |loser| -%> 
     <% if winner == loser -%>
       <td> -- </td>
 <table class="preftable">
   <% candidates.each do |victor| %>
   <tr>
 <table class="preftable">
   <% candidates.each do |victor| %>
   <tr>
-    <th><%=h names[victor] %></th>
+    <th><%=h @names[victor] %></th>
        <% victories[victor].keys.each do |loser| %>
        <% margin = victories[victor][loser]%>
        <% victories[victor].keys.each do |loser| %>
        <% margin = victories[victor][loser]%>
-       <td><%=h names[loser] %> 
+       <td><%=h @names[loser] %> 
            <% if margin == 0%>
                  Tied!
                <% else -%>
            <% if margin == 0%>
                  Tied!
                <% else -%>
index c93ce1d5a34f4952ea0e718260595c00308989a3..a7e11ce52ad7c87224ed781a017ccb631a030c8a 100644 (file)
@@ -13,4 +13,6 @@ chooses.  Approval voting is a limited form of range voting, where the
 range that voters are allowed to express is extremely constrained:
 accept or not.</p>
 
 range that voters are allowed to express is extremely constrained:
 accept or not.</p>
 
-</div>
\ No newline at end of file
+</div>
+
+<%= render :partial => 'approval_table' %>
\ No newline at end of file
index 80f19b46ee25983f4511ac36c4142d8daf895857..49b0c48a0334cacd9e1f4c35038b7cbb176f3606 100644 (file)
@@ -63,7 +63,7 @@ MAIL_CONFIG = { :from => 'Selectricity <info@selectricity.media.mit.edu>'}
 
 require 'uniq_token'
 require 'randarray'
 
 require 'uniq_token'
 require 'randarray'
-require 'gruff'
+require 'gruff-0.2.8/lib/gruff'
 require 'sparklines'
 
 require 'rubyvote'
 require 'sparklines'
 
 require 'rubyvote'
diff --git a/lib/gruff-0.2.8/CHANGELOG b/lib/gruff-0.2.8/CHANGELOG
new file mode 100644 (file)
index 0000000..3bb9d94
--- /dev/null
@@ -0,0 +1,81 @@
+CHANGELOG
+
+== 0.2.8
+
+* New accumulator bar graph (experimental)
+* Better mini graphs
+* Bug fixes
+
+== 0.2.7
+
+* Regenerated Manifest.txt
+* Added scene sample to package
+* Added mini side_bar (EXPERIMENTAL)
+* Added @zero_degree option to Gruff::Pie so first slice can start somewhere other than 3 o'clock
+* Increased size of numbers in Gruff::Mini::Pie
+* Added legend_box_size accessor
+
+== 0.2.6
+
+* Fixed missing side_bar.rb in Manifest.txt
+
+== 0.2.5
+
+* New mini graph types (Experimental)
+* Marker lines can be different color than text labels
+* Theme definition cleanup
+
+== 0.2.4
+
+* Added option to hide line numbers
+* Fixed code that was causing warnings
+
+== 0.2.3
+
+* Cleaned up measurements so the graph expands to fill the available space
+* Added x-axis and y-axis label options
+
+== 0.1.2
+
+* minimum_value and maximum_value can be set after data() to manually scale the graph
+* Fixed infinite loop bug when values are all equal
+* Added experimental net and spider graphs
+* Added non-linear scene graph for a simple interface to complex layered graphs
+* Initial refactoring of tests
+* A host of other bug fixes
+
+== 0.0.8
+
+* NEW Sidestacked Bar Graphs. [Alun Eyre]
+* baseline_value larger than data will now show correctly. [Mike Perham]
+* hide_dots and hide_lines are now options for line graphs.
+
+== 0.0.6
+
+* Fixed hang when no data is passed.
+
+== 0.0.4
+
+* Added bar graphs
+* Added area graphs
+* Added pie graphs
+* Added render_image_background for using images as background on a theme
+* Fixed small size legend centering issue
+* Added initial line marker rounding to significant digits (Christian Winkler)
+* Line graphs line width is scaled with number of points being drawn (Christian Winkler)
+
+== 0.0.3
+
+* Added option to draw line graphs without the lines (points only), thanks to Eric Hodel
+* Removed font-minimum check so graphs look better at 300px width
+
+== 0.0.2
+
+* Fixed to_blob (thanks to Carlos Villela)
+* Added bar graphs (initial functionality...will be enhanced)
+* Removed rendered test output from gem
+
+== 0.0.1
+
+* Initial release.
+* Line graphs only. Other graph styles coming soon.
diff --git a/lib/gruff-0.2.8/MIT-LICENSE b/lib/gruff-0.2.8/MIT-LICENSE
new file mode 100644 (file)
index 0000000..097f8c9
--- /dev/null
@@ -0,0 +1,21 @@
+Copyright (c) 2005 Geoffrey Grosenbach boss@topfunky.com
+
+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.
+
diff --git a/lib/gruff-0.2.8/Manifest.txt b/lib/gruff-0.2.8/Manifest.txt
new file mode 100644 (file)
index 0000000..e126637
--- /dev/null
@@ -0,0 +1,67 @@
+CHANGELOG
+Manifest.txt
+MIT-LICENSE
+Rakefile
+README.txt
+assets/pc306715.jpg
+assets/city_scene/background/0000.png
+assets/city_scene/background/0600.png
+assets/city_scene/background/2000.png
+assets/city_scene/clouds/cloudy.png
+assets/city_scene/clouds/partly_cloudy.png
+assets/city_scene/clouds/stormy.png
+assets/city_scene/grass/default.png
+assets/city_scene/haze/true.png
+assets/city_scene/number_sample/1.png
+assets/city_scene/number_sample/2.png
+assets/city_scene/number_sample/default.png
+assets/city_scene/sky/0000.png
+assets/city_scene/sky/0200.png
+assets/city_scene/sky/0400.png
+assets/city_scene/sky/0600.png
+assets/city_scene/sky/0800.png
+assets/city_scene/sky/1000.png
+assets/city_scene/sky/1200.png
+assets/city_scene/sky/1400.png
+assets/city_scene/sky/1500.png
+assets/city_scene/sky/1700.png
+assets/city_scene/sky/2000.png
+lib/gruff.rb
+lib/gruff/area.rb
+lib/gruff/bar.rb
+lib/gruff/bar_conversion.rb
+lib/gruff/accumulator_bar.rb
+lib/gruff/base.rb
+lib/gruff/deprecated.rb
+lib/gruff/line.rb
+lib/gruff/net.rb
+lib/gruff/photo_bar.rb
+lib/gruff/pie.rb
+lib/gruff/scene.rb
+lib/gruff/side_bar.rb
+lib/gruff/side_stacked_bar.rb
+lib/gruff/spider.rb
+lib/gruff/stacked_bar.rb
+lib/gruff/mini/bar.rb
+lib/gruff/mini/legend.rb
+lib/gruff/mini/pie.rb
+lib/gruff/mini/side_bar.rb
+test/gruff_test_case.rb
+test/output
+test/test_area.rb
+test/test_accumulator_bar.rb
+test/test_bar.rb
+test/test_base.rb
+test/test_legend.rb
+test/test_line.rb
+test/test_mini_bar.rb
+test/test_mini_pie.rb
+test/test_mini_side_bar.rb
+test/test_net.rb
+test/test_photo.rb
+test/test_pie.rb
+test/test_scene.rb
+test/test_side_bar.rb
+test/test_sidestacked_bar.rb
+test/test_spider.rb
+test/test_stacked_bar.rb
diff --git a/lib/gruff-0.2.8/README.txt b/lib/gruff-0.2.8/README.txt
new file mode 100644 (file)
index 0000000..6808687
--- /dev/null
@@ -0,0 +1,15 @@
+== Gruff Graphs
+
+A library for making beautiful graphs.
+
+See samples at http://nubyonrails.com/pages/gruff
+
+See the test suite in test/line_test.rb for examples.
+
+== Documentation
+
+Most of the documentation is in the Gruff::Base class.
+
+== WARNING
+
+This is beta-quality software. It works well according to my tests, but the API may change and other features will be added.
diff --git a/lib/gruff-0.2.8/Rakefile b/lib/gruff-0.2.8/Rakefile
new file mode 100644 (file)
index 0000000..6e9c2cd
--- /dev/null
@@ -0,0 +1,35 @@
+require 'rubygems'
+require 'hoe'
+$:.unshift(File.dirname(__FILE__) + "/lib")
+require 'gruff'
+
+Hoe.new('Gruff', Gruff::VERSION) do |p|
+  p.name = "gruff"
+  p.author = "Geoffrey Grosenbach"
+  p.description = "Beautiful graphs for one or multiple datasets. Can be used on websites or in documents."
+  p.email = 'boss@topfunky.com'
+  p.summary = "Beautiful graphs for one or multiple datasets."
+  p.url = "http://nubyonrails.com/pages/gruff"
+  p.clean_globs = ['test/output/*.png']
+end
+
+desc "Simple test on packaged files to make sure they are all there"
+task :verify => :package do
+  # An error message will be displayed if files are missing
+  if system %(ruby -e "require 'pkg/gruff-#{Gruff::VERSION}/lib/gruff'")
+    puts "\nThe library files are present"
+  end
+end
+
+##
+# Catch unmatched tasks and run them as a unit test.
+#
+# Makes it possible to do
+#  
+#  rake pie
+#
+# To run the +test/test_pie+ file.
+
+rule '' do |t|
+  system "ruby test/test_#{t.name}.rb"
+end
diff --git a/lib/gruff-0.2.8/assets/city_scene/background/0000.png b/lib/gruff-0.2.8/assets/city_scene/background/0000.png
new file mode 100644 (file)
index 0000000..fe1d790
Binary files /dev/null and b/lib/gruff-0.2.8/assets/city_scene/background/0000.png differ
diff --git a/lib/gruff-0.2.8/assets/city_scene/background/0600.png b/lib/gruff-0.2.8/assets/city_scene/background/0600.png
new file mode 100644 (file)
index 0000000..a345866
Binary files /dev/null and b/lib/gruff-0.2.8/assets/city_scene/background/0600.png differ
diff --git a/lib/gruff-0.2.8/assets/city_scene/background/2000.png b/lib/gruff-0.2.8/assets/city_scene/background/2000.png
new file mode 100644 (file)
index 0000000..fe1d790
Binary files /dev/null and b/lib/gruff-0.2.8/assets/city_scene/background/2000.png differ
diff --git a/lib/gruff-0.2.8/assets/city_scene/clouds/cloudy.png b/lib/gruff-0.2.8/assets/city_scene/clouds/cloudy.png
new file mode 100644 (file)
index 0000000..94dbbc2
Binary files /dev/null and b/lib/gruff-0.2.8/assets/city_scene/clouds/cloudy.png differ
diff --git a/lib/gruff-0.2.8/assets/city_scene/clouds/partly_cloudy.png b/lib/gruff-0.2.8/assets/city_scene/clouds/partly_cloudy.png
new file mode 100644 (file)
index 0000000..d073e8e
Binary files /dev/null and b/lib/gruff-0.2.8/assets/city_scene/clouds/partly_cloudy.png differ
diff --git a/lib/gruff-0.2.8/assets/city_scene/clouds/stormy.png b/lib/gruff-0.2.8/assets/city_scene/clouds/stormy.png
new file mode 100644 (file)
index 0000000..b542ba1
Binary files /dev/null and b/lib/gruff-0.2.8/assets/city_scene/clouds/stormy.png differ
diff --git a/lib/gruff-0.2.8/assets/city_scene/grass/default.png b/lib/gruff-0.2.8/assets/city_scene/grass/default.png
new file mode 100644 (file)
index 0000000..7002fa3
Binary files /dev/null and b/lib/gruff-0.2.8/assets/city_scene/grass/default.png differ
diff --git a/lib/gruff-0.2.8/assets/city_scene/haze/true.png b/lib/gruff-0.2.8/assets/city_scene/haze/true.png
new file mode 100644 (file)
index 0000000..27122bd
Binary files /dev/null and b/lib/gruff-0.2.8/assets/city_scene/haze/true.png differ
diff --git a/lib/gruff-0.2.8/assets/city_scene/number_sample/1.png b/lib/gruff-0.2.8/assets/city_scene/number_sample/1.png
new file mode 100644 (file)
index 0000000..1786fa5
Binary files /dev/null and b/lib/gruff-0.2.8/assets/city_scene/number_sample/1.png differ
diff --git a/lib/gruff-0.2.8/assets/city_scene/number_sample/2.png b/lib/gruff-0.2.8/assets/city_scene/number_sample/2.png
new file mode 100644 (file)
index 0000000..be500fc
Binary files /dev/null and b/lib/gruff-0.2.8/assets/city_scene/number_sample/2.png differ
diff --git a/lib/gruff-0.2.8/assets/city_scene/number_sample/default.png b/lib/gruff-0.2.8/assets/city_scene/number_sample/default.png
new file mode 100644 (file)
index 0000000..7442b46
Binary files /dev/null and b/lib/gruff-0.2.8/assets/city_scene/number_sample/default.png differ
diff --git a/lib/gruff-0.2.8/assets/city_scene/sky/0000.png b/lib/gruff-0.2.8/assets/city_scene/sky/0000.png
new file mode 100644 (file)
index 0000000..1786fa5
Binary files /dev/null and b/lib/gruff-0.2.8/assets/city_scene/sky/0000.png differ
diff --git a/lib/gruff-0.2.8/assets/city_scene/sky/0200.png b/lib/gruff-0.2.8/assets/city_scene/sky/0200.png
new file mode 100644 (file)
index 0000000..be500fc
Binary files /dev/null and b/lib/gruff-0.2.8/assets/city_scene/sky/0200.png differ
diff --git a/lib/gruff-0.2.8/assets/city_scene/sky/0400.png b/lib/gruff-0.2.8/assets/city_scene/sky/0400.png
new file mode 100644 (file)
index 0000000..7442b46
Binary files /dev/null and b/lib/gruff-0.2.8/assets/city_scene/sky/0400.png differ
diff --git a/lib/gruff-0.2.8/assets/city_scene/sky/0600.png b/lib/gruff-0.2.8/assets/city_scene/sky/0600.png
new file mode 100644 (file)
index 0000000..a50ea7a
Binary files /dev/null and b/lib/gruff-0.2.8/assets/city_scene/sky/0600.png differ
diff --git a/lib/gruff-0.2.8/assets/city_scene/sky/0800.png b/lib/gruff-0.2.8/assets/city_scene/sky/0800.png
new file mode 100644 (file)
index 0000000..cfd0039
Binary files /dev/null and b/lib/gruff-0.2.8/assets/city_scene/sky/0800.png differ
diff --git a/lib/gruff-0.2.8/assets/city_scene/sky/1000.png b/lib/gruff-0.2.8/assets/city_scene/sky/1000.png
new file mode 100644 (file)
index 0000000..842535f
Binary files /dev/null and b/lib/gruff-0.2.8/assets/city_scene/sky/1000.png differ
diff --git a/lib/gruff-0.2.8/assets/city_scene/sky/1200.png b/lib/gruff-0.2.8/assets/city_scene/sky/1200.png
new file mode 100644 (file)
index 0000000..2834156
Binary files /dev/null and b/lib/gruff-0.2.8/assets/city_scene/sky/1200.png differ
diff --git a/lib/gruff-0.2.8/assets/city_scene/sky/1400.png b/lib/gruff-0.2.8/assets/city_scene/sky/1400.png
new file mode 100644 (file)
index 0000000..795c05a
Binary files /dev/null and b/lib/gruff-0.2.8/assets/city_scene/sky/1400.png differ
diff --git a/lib/gruff-0.2.8/assets/city_scene/sky/1500.png b/lib/gruff-0.2.8/assets/city_scene/sky/1500.png
new file mode 100644 (file)
index 0000000..1cd0c14
Binary files /dev/null and b/lib/gruff-0.2.8/assets/city_scene/sky/1500.png differ
diff --git a/lib/gruff-0.2.8/assets/city_scene/sky/1700.png b/lib/gruff-0.2.8/assets/city_scene/sky/1700.png
new file mode 100644 (file)
index 0000000..ddb9178
Binary files /dev/null and b/lib/gruff-0.2.8/assets/city_scene/sky/1700.png differ
diff --git a/lib/gruff-0.2.8/assets/city_scene/sky/2000.png b/lib/gruff-0.2.8/assets/city_scene/sky/2000.png
new file mode 100644 (file)
index 0000000..767bde7
Binary files /dev/null and b/lib/gruff-0.2.8/assets/city_scene/sky/2000.png differ
diff --git a/lib/gruff-0.2.8/assets/pc306715.jpg b/lib/gruff-0.2.8/assets/pc306715.jpg
new file mode 100644 (file)
index 0000000..6cbe85a
Binary files /dev/null and b/lib/gruff-0.2.8/assets/pc306715.jpg differ
diff --git a/lib/gruff-0.2.8/lib/gruff.rb b/lib/gruff-0.2.8/lib/gruff.rb
new file mode 100644 (file)
index 0000000..e281916
--- /dev/null
@@ -0,0 +1,24 @@
+# Extra full path added to fix loading errors on some installations.
+
+%w(
+  base
+  area
+  bar
+  line
+  pie
+  spider
+  net
+  stacked_bar
+  side_stacked_bar
+  side_bar
+  accumulator_bar
+
+  scene
+
+  mini/legend
+  mini/bar
+  mini/pie
+  mini/side_bar
+).each do |filename|
+  require File.dirname(__FILE__) + "/gruff/#{filename}"
+end
diff --git a/lib/gruff-0.2.8/lib/gruff/accumulator_bar.rb b/lib/gruff-0.2.8/lib/gruff/accumulator_bar.rb
new file mode 100644 (file)
index 0000000..310e193
--- /dev/null
@@ -0,0 +1,27 @@
+require File.dirname(__FILE__) + '/base'
+
+##
+# A special bar graph that shows a single dataset as a set of
+# stacked bars. The bottom bar shows the running total and 
+# the top bar shows the new value being added to the array.
+
+class Gruff::AccumulatorBar < Gruff::StackedBar
+
+  def draw
+    raise(Gruff::IncorrectNumberOfDatasetsException) unless @data.length == 1
+    
+    accumulator_array = []
+    index = 0
+
+    increment_array = @data.first[DATA_VALUES_INDEX].inject([]) {|memo, value| 
+        memo << ((index > 0) ? (value + memo.max) : value)
+        accumulator_array << memo[index] - value
+        index += 1
+        memo
+      }
+    data "Accumulator", accumulator_array
+    
+    super
+  end
+
+end
diff --git a/lib/gruff-0.2.8/lib/gruff/area.rb b/lib/gruff-0.2.8/lib/gruff/area.rb
new file mode 100644 (file)
index 0000000..323aff1
--- /dev/null
@@ -0,0 +1,58 @@
+
+require File.dirname(__FILE__) + '/base'
+
+class Gruff::Area < Gruff::Base
+
+  def draw
+    super
+
+    return unless @has_data
+
+    @x_increment = @graph_width / (@column_count - 1).to_f
+    @d = @d.stroke 'transparent'
+
+    @norm_data.each do |data_row|
+      poly_points = Array.new
+      prev_x = prev_y = 0.0
+      @d = @d.fill data_row[DATA_COLOR_INDEX]
+
+      data_row[1].each_with_index do |data_point, index|
+        # Use incremented x and scaled y
+        new_x = @graph_left + (@x_increment * index)
+        new_y = @graph_top + (@graph_height - data_point * @graph_height)
+
+        if prev_x > 0 and prev_y > 0 then
+          poly_points << new_x
+          poly_points << new_y
+          
+          #@d = @d.polyline(prev_x, prev_y, new_x, new_y)
+        else
+          poly_points << @graph_left
+          poly_points << @graph_bottom - 1
+          poly_points << new_x
+          poly_points << new_y
+          
+          #@d = @d.polyline(@graph_left, @graph_bottom, new_x, new_y)
+        end
+
+        draw_label(new_x, index)
+
+        prev_x = new_x
+        prev_y = new_y
+      end
+
+      # Add closing points, draw polygon
+      poly_points << @graph_right
+      poly_points << @graph_bottom - 1
+      poly_points << @graph_left
+      poly_points << @graph_bottom - 1
+
+      @d = @d.polyline(*poly_points)
+
+    end
+
+    @d.draw(@base_image)
+  end
+   
+end
diff --git a/lib/gruff-0.2.8/lib/gruff/bar.rb b/lib/gruff-0.2.8/lib/gruff/bar.rb
new file mode 100644 (file)
index 0000000..10c1bf8
--- /dev/null
@@ -0,0 +1,120 @@
+
+require File.dirname(__FILE__) + '/base'
+require File.dirname(__FILE__) + '/bar_conversion'
+
+class Gruff::Bar < Gruff::Base
+  
+  def draw
+    # Labels will be centered over the left of the bar if
+    # there are more labels than columns. This is basically the same 
+    # as where it would be for a line graph.
+    @center_labels_over_point = (@labels.keys.length > @column_count ? true : false)
+    
+    super
+    return unless @has_data
+
+    draw_bars
+  end
+
+protected
+
+  def draw_bars
+    # Setup spacing.
+    #
+    # Columns sit side-by-side.
+    spacing_factor = 0.9 # space between the bars
+    @bar_width = @graph_width / (@column_count * @data.length).to_f
+    
+    @d = @d.stroke_opacity 0.0
+
+    # Setup the BarConversion Object
+    conversion = Gruff::BarConversion.new()
+    conversion.graph_height = @graph_height
+    conversion.graph_top = @graph_top
+
+    # Set up the right mode [1,2,3] see BarConversion for further explanation
+    if @minimum_value >= 0 then
+      # all bars go from zero to positiv
+      conversion.mode = 1
+    else
+      # all bars go from 0 to negativ
+      if @maximum_value <= 0 then
+        conversion.mode = 2
+      else
+        # bars either go from zero to negativ or to positiv
+        conversion.mode = 3
+        conversion.spread = @spread
+        conversion.minimum_value = @minimum_value
+        conversion.zero = -@minimum_value/@spread
+      end
+    end
+
+    # iterate over all normalised data
+    @norm_data.each_with_index do |data_row, row_index|
+      count = 0
+      data_row[1].each_with_index do |data_point, point_index|
+      
+        # Use incremented x and scaled y
+        # x
+        left_x = @graph_left + (@bar_width * (row_index + point_index + ((@data.length - 1) * point_index)))
+        right_x = left_x + @bar_width * spacing_factor
+        # y
+        conv = []
+        conversion.getLeftYRightYscaled( data_point, conv )
+
+        # create new bar
+        @d = @d.fill data_row[DATA_COLOR_INDEX]
+        @d = @d.rectangle(left_x, conv[0], right_x, conv[1])
+        
+        # Calculate center based on bar_width and current row
+        label_center = @graph_left + 
+                      (@data.length * @bar_width * point_index) + 
+                      (@data.length * @bar_width / 2.0)
+        # Subtract half a bar width to center left if requested
+        draw_label(label_center - (@center_labels_over_point ? @bar_width / 2.0 : 0.0), point_index, count)
+        # custom modification to allow graph labels to alternate in height on
+        # bar graphs so they don't overlap
+        count += 1
+      end
+
+    end
+
+    # Draw the last label if requested
+    draw_label(@graph_right, @column_count) if @center_labels_over_point
+
+    @d.draw(@base_image)
+  end
+
+  def draw_axis_labels
+    unless @x_axis_label.nil?
+      # X Axis
+      # Centered vertically and horizontally by setting the
+      # height to 1.0 and the width to the width of the graph.
+      x_axis_label_y_coordinate = @graph_bottom + LABEL_MARGIN * 2.5 + @marker_caps_height
+
+      # TODO Center between graph area
+      @d.fill = @font_color
+      @d.font = @font if @font
+      @d.stroke('transparent')
+      @d.pointsize = scale_fontsize(@marker_font_size) * 1.2
+      @d.gravity = NorthGravity
+      @d = @d.annotate_scaled( @base_image, 
+                        @raw_columns, 1.0, 
+                        0.0, x_axis_label_y_coordinate, 
+                        @x_axis_label, @scale)
+      debug { @d.line 0.0, x_axis_label_y_coordinate, @raw_columns, x_axis_label_y_coordinate }
+    end
+
+    unless @y_axis_label.nil?
+      # Y Axis, rotated vertically
+      @d.rotation = 90.0
+      @d.gravity = CenterGravity
+      @d = @d.annotate_scaled( @base_image, 
+                        1.0, @raw_rows,
+                        LEFT_MARGIN + @marker_caps_height / 2.0, 0.0, 
+                        @y_axis_label, @scale)
+      @d.rotation = -90.0
+    end
+  end
+
+end
diff --git a/lib/gruff-0.2.8/lib/gruff/bar_conversion.rb b/lib/gruff-0.2.8/lib/gruff/bar_conversion.rb
new file mode 100644 (file)
index 0000000..880daca
--- /dev/null
@@ -0,0 +1,46 @@
+##
+# Original Author: David Stokar
+#
+#      This class perfoms the y coordinats conversion for the bar class.
+#
+#      There are three cases: 
+#
+#   1. Bars all go from zero in positive direction
+#              2. Bars all go from zero to negative direction  
+#              3. Bars either go from zero to positive or from zero to negative
+#
+class Gruff::BarConversion
+       attr_writer :mode
+       attr_writer :zero
+       attr_writer :graph_top
+       attr_writer :graph_height
+       attr_writer :minimum_value
+       attr_writer :spread
+       
+       def getLeftYRightYscaled( data_point, result )
+               case @mode
+               when 1 then # Case one
+                       # minimum value >= 0 ( only positiv values )
+      result[0] = @graph_top + @graph_height*(1 - data_point) + 1
+               result[1] = @graph_top + @graph_height - 1
+               when 2 then  # Case two
+                       # only negativ values
+               result[0] = @graph_top + 1
+               result[1] = @graph_top + @graph_height*(1 - data_point) - 1
+               when 3 then # Case three
+                       # positiv and negativ values
+       val = data_point-@minimum_value/@spread
+       if ( data_point >= @zero ) then
+               result[0] = @graph_top + @graph_height*(1 - (val-@zero)) + 1
+               result[1] = @graph_top + @graph_height*(1 - @zero) - 1
+       else
+                               result[0] = @graph_top + @graph_height*(1 - (val-@zero)) + 1
+               result[1] = @graph_top + @graph_height*(1 - @zero) - 1
+       end
+               else
+                       result[0] = 0.0
+                       result[1] = 0.0
+               end                             
+       end     
+
+end
diff --git a/lib/gruff-0.2.8/lib/gruff/base.rb b/lib/gruff-0.2.8/lib/gruff/base.rb
new file mode 100644 (file)
index 0000000..1c3fff3
--- /dev/null
@@ -0,0 +1,1008 @@
+#
+# = Gruff. Graphs.
+#
+# Author:: Geoffrey Grosenbach boss@topfunky.com
+#
+# Originally Created:: October 23, 2005
+#
+# Extra thanks to Tim Hunter for writing RMagick, 
+# and also contributions by
+# Jarkko Laine, Mike Perham, Andreas Schwarz, 
+# Alun Eyre, Guillaume Theoret, David Stokar, 
+# Paul Rogers, Dave Woodward, Frank Oxener,
+# Kevin Clark, Cies Breijs, Richard Cowin,
+# and a cast of thousands.
+#
+
+require 'rubygems'
+require 'RMagick'
+require File.dirname(__FILE__) + '/deprecated'
+
+module Gruff
+  
+  VERSION = '0.2.8'
+  
+  class Base
+    
+    include Magick
+    include Deprecated
+
+    # Draw extra lines showing where the margins and text centers are
+    DEBUG = false
+
+    # Used for navigating the array of data to plot
+    DATA_LABEL_INDEX = 0
+    DATA_VALUES_INDEX = 1
+    DATA_COLOR_INDEX = 2
+
+    # Blank space around the edges of the graph
+    TOP_MARGIN = BOTTOM_MARGIN = RIGHT_MARGIN = LEFT_MARGIN = 20.0
+    
+    # Space around text elements. Mostly used for vertical spacing
+    LEGEND_MARGIN = TITLE_MARGIN = LABEL_MARGIN = 10.0
+    
+    # A hash of names for the individual columns, where the key is the array index for the column this label represents.
+    #
+    # Not all columns need to be named.
+    #
+    # Example: 0 => 2005, 3 => 2006, 5 => 2007, 7 => 2008
+    attr_accessor :labels
+
+    # Used internally for spacing.
+    #
+    # By default, labels are centered over the point they represent.
+    attr_accessor :center_labels_over_point
+
+    # Used internally for horizontal graph types.
+    attr_accessor :has_left_labels
+
+    # A label for the bottom of the graph
+    attr_accessor :x_axis_label
+    
+    # A label for the left side of the graph
+    attr_accessor :y_axis_label
+
+    # attr_accessor :x_axis_increment
+    
+    # Manually set increment of the horizontal marking lines
+    attr_accessor :y_axis_increment
+
+    # Get or set the list of colors that will be used to draw the bars or lines.
+    attr_accessor :colors
+
+    # The large title of the graph displayed at the top
+    attr_accessor :title
+
+    # Font used for titles, labels, etc. Works best if you provide the full path to the TTF font file.
+    # RMagick must be built with the Freetype libraries for this to work properly.
+    #
+    # Tries to find Bitstream Vera (Vera.ttf) in the location specified by
+    # ENV['MAGICK_FONT_PATH']. Uses default RMagick font otherwise.
+    attr_reader :font
+
+    attr_accessor :font_color
+
+    # Hide various elements
+    attr_accessor :hide_line_markers, :hide_legend, :hide_title, :hide_line_numbers
+
+    # Message shown when there is no data. Fits up to 20 characters. Defaults to "No Data."
+    attr_accessor :no_data_message
+
+    # The font size of the large title at the top of the graph
+    attr_accessor :title_font_size
+
+    # Optionally set the size of the font. Based on an 800x600px graph. Default is 20.
+    #
+    # Will be scaled down if graph is smaller than 800px wide.
+    attr_accessor :legend_font_size
+
+    # The font size of the labels around the graph
+    attr_accessor :marker_font_size
+    
+    # The color of the auxiliary lines
+    attr_accessor :marker_color
+
+    # The number of horizontal lines shown for reference
+    attr_accessor :marker_count
+    
+
+    # You can manually set a minimum value instead of having the values guessed for you.
+    #
+    # Set it after you have given all your data to the graph object.
+    attr_accessor :minimum_value
+
+    # You can manually set a maximum value, such as a percentage-based graph that always goes to 100.
+    #
+    # If you use this, you must set it after you have given all your data to the graph object.
+    attr_accessor :maximum_value
+    
+    # Set to false if you don't want the data to be sorted with largest avg values at the back.
+    attr_accessor :sort
+    
+    # Experimental
+    attr_accessor :additional_line_values
+    
+    # Experimental
+    attr_accessor :stacked
+    
+    
+    # Optionally set the size of the colored box by each item in the legend. Default is 20.0
+    #
+    # Will be scaled down if graph is smaller than 800px wide.
+    attr_accessor :legend_box_size
+
+
+    # If one numerical argument is given, the graph is drawn at 4/3 ratio according to the given width (800 results in 800x600, 400 gives 400x300, etc.).
+    #
+    # Or, send a geometry string for other ratios ('800x400', '400x225'). 
+    #
+    # Looks for Bitstream Vera as the default font. Expects an environment var of MAGICK_FONT_PATH to be set. (Uses RMagick's default font otherwise.)
+    def initialize(target_width=800)
+
+      if not Numeric === target_width
+        geometric_width, geometric_height = target_width.split('x')
+        @columns = geometric_width.to_f
+        @rows = geometric_height.to_f
+      else
+        @columns = target_width.to_f
+        @rows = target_width.to_f * 0.75        
+      end
+      
+      initialize_ivars
+
+      reset_themes
+      theme_keynote
+    end
+
+    ##
+    # Set instance variables for this object.
+    #
+    # Subclasses can override this, call super, then set values separately.
+    #
+    # This makes it possible to set defaults in a subclass but still allow
+    # developers to change this values in their program.
+    
+    def initialize_ivars
+      # Internal for calculations
+      @raw_columns = 800.0
+      @raw_rows = 800.0 * (@rows/@columns)
+      @column_count = 0
+      @marker_count = nil
+      @maximum_value = @minimum_value = nil
+      @has_data = false
+      @data = Array.new
+      @labels = Hash.new
+      @labels_seen = Hash.new
+      @sort = true
+      @title = nil
+
+      @scale = @columns / @raw_columns
+
+      vera_font_path = File.expand_path('Vera.ttf', ENV['MAGICK_FONT_PATH'])
+      @font = File.exists?(vera_font_path) ? vera_font_path : nil
+
+      @marker_font_size = 21.0
+      @legend_font_size = 20.0
+      @title_font_size = 36.0
+      
+      @legend_box_size = 20.0
+
+      @no_data_message = "No Data"
+
+      @hide_line_markers = @hide_legend = @hide_title = @hide_line_numbers = false
+      @center_labels_over_point = true
+      @has_left_labels = false
+
+      @additional_line_values = []      
+      @additional_line_colors = []
+      @theme_options = {}
+      
+      @x_axis_label = @y_axis_label = nil
+      @y_axis_increment = nil
+      @stacked = nil
+      @norm_data = nil
+    end
+    
+    def font=(font_path)
+      @font = font_path
+      @d.font = @font
+    end
+
+    # Add a color to the list of available colors for lines.
+    #
+    # Example: 
+    #  add_color('#c0e9d3')
+    def add_color(colorname)
+      @colors << colorname
+    end
+
+
+    # Replace the entire color list with a new array of colors. You need to have one more color
+    # than the number of datasets you intend to draw. Also aliased as the colors= setter method.
+    #
+    # Example: 
+    #  replace_colors('#cc99cc', '#d9e043', '#34d8a2')
+    def replace_colors(color_list=[])
+      @colors = color_list
+    end
+
+
+    # You can set a theme manually. Assign a hash to this method before you send your data.
+    #
+    #  graph.theme = {
+    #    :colors => %w(orange purple green white red),
+    #    :marker_color => 'blue',
+    #    :background_colors => %w(black grey)
+    #  }
+    #
+    # :background_image => 'squirrel.png' is also possible.
+    #
+    # (Or hopefully something better looking than that.)
+    #
+    def theme=(options)
+      reset_themes()
+      
+      defaults = {
+        :colors => ['black', 'white'],
+        :additional_line_colors => [],
+        :marker_color => 'white',
+        :font_color => 'black',
+        :background_colors => nil,
+        :background_image => nil
+      }
+      @theme_options = defaults.merge options
+
+      @colors = @theme_options[:colors]
+      @marker_color = @theme_options[:marker_color]
+      @font_color = @theme_options[:font_color] || @marker_color
+      @additional_line_colors = @theme_options[:additional_line_colors]
+      
+      render_background
+    end
+    
+    # A color scheme similar to the popular presentation software.
+    def theme_keynote
+      # Colors
+      @blue = '#6886B4'
+      @yellow = '#FDD84E'
+      @green = '#72AE6E'
+      @red = '#D1695E'
+      @purple = '#8A6EAF'
+      @orange = '#EFAA43'
+      @white = 'white'
+      @colors = [@yellow, @blue, @green, @red, @purple, @orange, @white]
+
+      self.theme = {
+        :colors => @colors,
+        :marker_color => 'white',
+        :font_color => 'white',
+        :background_colors => ['black', '#4a465a']
+      }
+    end
+    
+    # A color scheme plucked from the colors on the popular usability blog.
+    def theme_37signals
+      # Colors
+      @green = '#339933'
+      @purple = '#cc99cc'
+      @blue = '#336699'
+      @yellow = '#FFF804'
+      @red = '#ff0000'
+      @orange = '#cf5910'
+      @black = 'black'
+      @colors = [@yellow, @blue, @green, @red, @purple, @orange, @black]
+
+      self.theme = {
+        :colors => @colors,
+        :marker_color => 'black',
+        :font_color => 'black',
+        :background_colors => ['#d1edf5', 'white']
+      }
+    end
+
+    # A color scheme from the colors used on the 2005 Rails keynote presentation at RubyConf.
+    def theme_rails_keynote
+      # Colors
+      @green = '#00ff00'
+      @grey = '#333333'
+      @orange = '#ff5d00'
+      @red = '#f61100'
+      @white = 'white'
+      @light_grey = '#999999'
+      @black = 'black'
+      @colors = [@green, @grey, @orange, @red, @white, @light_grey, @black]
+      
+      self.theme = {
+        :colors => @colors,
+        :marker_color => 'white',
+        :font_color => 'white',
+        :background_colors => ['#0083a3', '#0083a3']
+      }
+    end
+
+    # A color scheme similar to that used on the popular podcast site.
+    def theme_odeo
+      # Colors
+      @grey = '#202020'
+      @white = 'white'
+      @dark_pink = '#a21764'
+      @green = '#8ab438'
+      @light_grey = '#999999'
+      @dark_blue = '#3a5b87'
+      @black = 'black'
+      @colors = [@grey, @white, @dark_blue, @dark_pink, @green, @light_grey, @black]
+      
+      self.theme = {
+        :colors => @colors,
+        :marker_color => 'white',
+        :font_color => 'white',
+        :background_colors => ['#ff47a4', '#ff1f81']
+      }
+    end
+
+    # Parameters are an array where the first element is the name of the dataset
+    # and the value is an array of values to plot.
+    #
+    # Can be called multiple times with different datasets for a multi-valued graph.
+    #
+    # If the color argument is nil, the next color from the default theme will be used.
+    #
+    # NOTE: If you want to use a preset theme, you must set it before calling data().
+    #
+    # Example:
+    #
+    #  data("Bart S.", [95, 45, 78, 89, 88, 76], '#ffcc00')
+    #
+    def data(name, data_points=[], color=nil)
+      data_points = Array(data_points) # make sure it's an array
+      @data << [name, data_points, (color || increment_color)]
+      # Set column count if this is larger than previous counts
+      @column_count = (data_points.length > @column_count) ? data_points.length : @column_count
+
+      # Pre-normalize
+      data_points.each_with_index do |data_point, index|
+        next if data_point.nil?
+        
+        # Setup max/min so spread starts at the low end of the data points
+        if @maximum_value.nil? && @minimum_value.nil?
+          @maximum_value = @minimum_value = data_point
+        end
+
+        # TODO Doesn't work with stacked bar graphs
+        # Original: @maximum_value = larger_than_max?(data_point, index) ? max(data_point, index) : @maximum_value
+        @maximum_value = larger_than_max?(data_point) ? data_point : @maximum_value
+        @has_data = true if @maximum_value > 0
+        
+        @minimum_value = less_than_min?(data_point) ? data_point : @minimum_value
+         @has_data = true if @minimum_value < 0
+      end
+    end
+
+    # Writes the graph to a file. Defaults to 'graph.png'
+    #
+    # Example: write('graphs/my_pretty_graph.png')
+    def write(filename="graph.png")
+      draw()
+      @base_image.write(filename)
+    end
+
+    # Return the graph as a rendered binary blob.
+    def to_blob(fileformat='PNG')
+      draw()
+      return @base_image.to_blob do
+         self.format = fileformat
+      end
+    end
+
+protected
+
+    # Overridden by subclasses to do the actual plotting of the graph.
+    #
+    # Subclasses should start by calling super() for this method.
+    def draw
+      make_stacked if @stacked
+      setup_drawing
+      
+      debug {
+        # Outer margin
+        @d.rectangle( LEFT_MARGIN, TOP_MARGIN, 
+                            @raw_columns - RIGHT_MARGIN, @raw_rows - BOTTOM_MARGIN)
+        # Graph area box
+        @d.rectangle( @graph_left, @graph_top, @graph_right, @graph_bottom)
+      }
+    end
+
+    ##
+    # Calculates size of drawable area and draws the decorations.
+    #
+    # * line markers
+    # * legend
+    # * title
+    
+    def setup_drawing
+      # Maybe should be done in one of the following functions for more granularity.
+      unless @has_data
+        draw_no_data()
+        return
+      end
+      
+      normalize()
+      setup_graph_measurements()
+      sort_norm_data() if @sort # Sort norm_data with avg largest values set first (for display)
+      
+      draw_legend()
+      draw_line_markers()
+      draw_axis_labels()
+      draw_title
+    end
+
+    # Make copy of data with values scaled between 0-100
+    def normalize(force=false)
+      if @norm_data.nil? || force
+        @norm_data = []
+        return unless @has_data
+                
+        calculate_spread
+        
+        @data.each do |data_row|
+          norm_data_points = []
+          data_row[DATA_VALUES_INDEX].each do |data_point|
+            if data_point.nil?
+              norm_data_points << nil
+            else
+              norm_data_points << ((data_point.to_f - @minimum_value.to_f ) / @spread)
+            end
+          end
+          @norm_data << [data_row[DATA_LABEL_INDEX], norm_data_points, data_row[DATA_COLOR_INDEX]]
+        end
+      end
+    end
+
+    def calculate_spread
+      @spread = @maximum_value.to_f - @minimum_value.to_f
+      @spread = @spread > 0 ? @spread : 1
+    end
+
+    ##
+    # Calculates size of drawable area, general font dimensions, etc.
+    
+    def setup_graph_measurements
+      @marker_caps_height = calculate_caps_height(@marker_font_size)
+      @title_caps_height = calculate_caps_height(@title_font_size)
+      @legend_caps_height = calculate_caps_height(@legend_font_size)
+      
+      if @hide_line_markers
+        (@graph_left, 
+         @graph_right_margin, 
+         @graph_bottom_margin) = [LEFT_MARGIN, RIGHT_MARGIN, BOTTOM_MARGIN]
+      else
+        longest_left_label_width = 0
+        if @has_left_labels
+          longest_left_label_width =  calculate_width(@marker_font_size,
+                                      labels.values.inject('') { |value, memo| (value.to_s.length > memo.to_s.length) ? value : memo }) * 1.25
+        else
+          longest_left_label_width = calculate_width(@marker_font_size, 
+                          label(@maximum_value.to_f))
+        end
+      
+        # Shift graph if left line numbers are hidden
+        line_number_width = @hide_line_numbers && !@has_left_labels ? 
+                              0.0 : 
+                              (longest_left_label_width + LABEL_MARGIN * 2)
+
+        @graph_left = LEFT_MARGIN + 
+                      line_number_width + 
+                      (@y_axis_label.nil? ? 0.0 : @marker_caps_height + LABEL_MARGIN * 2)
+        # Make space for half the width of the rightmost column label.
+        # Might be greater than the number of columns if between-style bar markers are used.
+        last_label = @labels.keys.sort.last.to_i
+        extra_room_for_long_label = (last_label >= (@column_count-1) && @center_labels_over_point) ?
+          calculate_width(@marker_font_size, @labels[last_label])/2.0 :
+          0
+        @graph_right_margin =   RIGHT_MARGIN + extra_room_for_long_label
+                                
+        @graph_bottom_margin =  BOTTOM_MARGIN + 
+                                @marker_caps_height + LABEL_MARGIN
+      end
+
+      @graph_right = @raw_columns - @graph_right_margin
+      @graph_width = @raw_columns - @graph_left - @graph_right_margin
+
+      # When @hide title, leave a TITLE_MARGIN space for aesthetics.
+      # Same with @hide_legend
+      @graph_top = TOP_MARGIN + 
+                    (@hide_title ? TITLE_MARGIN : @title_caps_height + TITLE_MARGIN * 2) +
+                    (@hide_legend ? LEGEND_MARGIN : @legend_caps_height + LEGEND_MARGIN * 2)
+
+      @graph_bottom = @raw_rows - @graph_bottom_margin -
+                      (@x_axis_label.nil? ? 0.0 : @marker_caps_height + LABEL_MARGIN)
+      
+      @graph_height = @graph_bottom - @graph_top
+    end
+
+    # Draw the optional labels for the x axis and y axis.
+    def draw_axis_labels
+      unless @x_axis_label.nil?
+        # X Axis
+        # Centered vertically and horizontally by setting the
+        # height to 1.0 and the width to the width of the graph.
+        x_axis_label_y_coordinate = @graph_bottom + LABEL_MARGIN * 2 + @marker_caps_height
+
+        # TODO Center between graph area
+        @d.fill = @font_color
+        @d.font = @font if @font
+        @d.stroke('transparent')
+        @d.pointsize = scale_fontsize(@marker_font_size)
+        @d.gravity = NorthGravity
+        @d = @d.annotate_scaled( @base_image, 
+                          @raw_columns, 1.0, 
+                          0.0, x_axis_label_y_coordinate, 
+                          @x_axis_label, @scale)
+        debug { @d.line 0.0, x_axis_label_y_coordinate, @raw_columns, x_axis_label_y_coordinate }
+      end
+
+      unless @y_axis_label.nil?
+        # Y Axis, rotated vertically
+        @d.rotation = 90.0
+        @d.gravity = CenterGravity
+        @d = @d.annotate_scaled( @base_image, 
+                          1.0, @raw_rows,
+                          LEFT_MARGIN + @marker_caps_height / 2.0, 0.0, 
+                          @y_axis_label, @scale)
+        @d.rotation = -90.0
+      end
+    end
+
+    # Draws horizontal background lines and labels
+    def draw_line_markers
+      return if @hide_line_markers
+      
+      @d = @d.stroke_antialias false
+            
+      if @y_axis_increment.nil?
+        # Try to use a number of horizontal lines that will come out even.
+        #
+        # TODO Do the same for larger numbers...100, 75, 50, 25
+        if @marker_count.nil?
+          (3..7).each do |lines|
+            if @spread % lines == 0.0
+              @marker_count = lines
+              break
+            end
+          end
+          @marker_count ||= 4
+        end
+        @increment = (@spread > 0) ? significant(@spread / @marker_count) : 1
+      else
+        # TODO Make this work for negative values
+        @maximum_value = [@maximum_value.ceil, @y_axis_increment].max
+        @minimum_value = @minimum_value.floor
+        calculate_spread
+        normalize(true)
+        
+        @marker_count = (@spread / @y_axis_increment).to_i
+        @increment = @y_axis_increment
+      end
+      @increment_scaled = @graph_height.to_f / (@spread / @increment)
+
+      # Draw horizontal line markers and annotate with numbers
+      (0..@marker_count).each do |index|
+        y = @graph_top + @graph_height - index.to_f * @increment_scaled
+        
+        @d = @d.stroke(@marker_color)
+        @d = @d.stroke_width 1
+        @d = @d.line(@graph_left, y, @graph_right, y)
+
+        marker_label = index * @increment + @minimum_value.to_f
+
+        unless @hide_line_numbers
+          @d.fill = @font_color
+          @d.font = @font if @font
+          @d.stroke('transparent')
+          @d.pointsize = scale_fontsize(@marker_font_size)
+          @d.gravity = EastGravity
+        
+          # Vertically center with 1.0 for the height
+          @d = @d.annotate_scaled( @base_image, 
+                            @graph_left - LABEL_MARGIN, 1.0,
+                            0.0, y,
+                            label(marker_label), @scale)
+        end
+      end
+      
+      # # Submitted by a contibutor...the utility escapes me
+      # i = 0
+      # @additional_line_values.each do |value|
+      #   @increment_scaled = @graph_height.to_f / (@maximum_value.to_f / value)
+      # 
+      #   y = @graph_top + @graph_height - @increment_scaled
+      # 
+      #   @d = @d.stroke(@additional_line_colors[i])
+      #   @d = @d.line(@graph_left, y, @graph_right, y)
+      # 
+      # 
+      #   @d.fill = @additional_line_colors[i]
+      #   @d.font = @font if @font
+      #   @d.stroke('transparent')
+      #   @d.pointsize = scale_fontsize(@marker_font_size)
+      #   @d.gravity = EastGravity
+      #   @d = @d.annotate_scaled( @base_image, 
+      #                     100, 20,
+      #                     -10, y - (@marker_font_size/2.0), 
+      #                     "", @scale)
+      #   i += 1   
+      # end
+      
+      @d = @d.stroke_antialias true
+    end
+
+    # Draws a legend with the names of the datasets 
+    # matched to the colors used to draw them.
+    def draw_legend
+      return if @hide_legend
+
+      @legend_labels = @data.collect {|item| item[DATA_LABEL_INDEX] }
+
+      legend_square_width = @legend_box_size # small square with color of this item
+
+      # May fix legend drawing problem at small sizes
+      @d.font = @font if @font
+      @d.pointsize = @legend_font_size
+
+      metrics = @d.get_type_metrics(@base_image, @legend_labels.join(''))
+      legend_text_width = metrics.width
+      legend_width = legend_text_width + 
+                    (@legend_labels.length * legend_square_width * 2.7)
+      legend_left = (@raw_columns - legend_width) / 2
+      legend_increment = legend_width / @legend_labels.length.to_f
+
+      current_x_offset = legend_left
+      current_y_offset =  @hide_title ? 
+                          TOP_MARGIN + LEGEND_MARGIN : 
+                          TOP_MARGIN + 
+                          TITLE_MARGIN + @title_caps_height +
+                          LEGEND_MARGIN
+
+      debug { @d.line 0.0, current_y_offset, @raw_columns, current_y_offset }
+                                                    
+      @legend_labels.each_with_index do |legend_label, index|        
+
+        # Draw label
+        @d.fill = @font_color
+        @d.font = @font if @font
+        @d.pointsize = scale_fontsize(@legend_font_size)
+        @d.stroke('transparent')
+        @d.font_weight = NormalWeight
+        @d.gravity = WestGravity
+        @d = @d.annotate_scaled( @base_image, 
+                          @raw_columns, 1.0,
+                          current_x_offset + (legend_square_width * 1.7), current_y_offset, 
+                          legend_label.to_s, @scale)
+        
+        # Now draw box with color of this dataset
+        @d = @d.stroke('transparent')
+        @d = @d.fill @data[index][DATA_COLOR_INDEX]
+        @d = @d.rectangle(current_x_offset, 
+                          current_y_offset - legend_square_width / 2.0, 
+                          current_x_offset + legend_square_width, 
+                          current_y_offset + legend_square_width / 2.0)
+
+        @d.pointsize = @legend_font_size
+        metrics = @d.get_type_metrics(@base_image, legend_label.to_s)
+        current_string_offset = metrics.width + (legend_square_width * 2.7)
+        current_x_offset += current_string_offset
+      end
+      @color_index = 0
+    end
+
+    def draw_title
+      return if (@hide_title || @title.nil?)
+      
+      @d.fill = @font_color
+      @d.font = @font if @font
+      @d.stroke('transparent')
+      @d.pointsize = scale_fontsize(@title_font_size)
+      @d.font_weight = BoldWeight
+      @d.gravity = NorthGravity
+      @d = @d.annotate_scaled( @base_image, 
+                        @raw_columns, 1.0,
+                        0, TOP_MARGIN, 
+                        @title, @scale)
+    end
+
+    ##
+    # Draws column labels below graph, centered over x_offset
+    #
+    # TODO Allow WestGravity as an option
+    
+    def draw_label(x_offset, index, count=nil)
+      return if @hide_line_markers
+
+      if !@labels[index].nil? && @labels_seen[index].nil?
+        y_offset = @graph_bottom + LABEL_MARGIN
+        
+        if count && (count % 2) == 1
+          y_offset += 1.5 * LABEL_MARGIN
+        end
+        
+        @d.fill = @font_color
+        @d.font = @font if @font
+        @d.stroke('transparent')
+        @d.font_weight = NormalWeight
+        @d.pointsize = scale_fontsize(@marker_font_size)
+        @d.gravity = NorthGravity
+        @d = @d.annotate_scaled(@base_image,
+                                1.0, 1.0,
+                                x_offset, y_offset,
+                                @labels[index], @scale)
+        @labels_seen[index] = 1
+        debug { @d.line 0.0, y_offset, @raw_columns, y_offset }
+      end
+    end
+
+    def draw_no_data
+        @d.fill = @font_color
+        @d.font = @font if @font
+        @d.stroke('transparent')
+        @d.font_weight = NormalWeight
+        @d.pointsize = scale_fontsize(80)
+        @d.gravity = CenterGravity
+        @d = @d.annotate_scaled( @base_image, 
+                        @raw_columns, @raw_rows/2.0,
+                        0, 10, 
+                        @no_data_message, @scale)
+    end
+
+    ##
+    # Finds the best background to render based on the provided theme options.
+    #
+    # Creates a @base_image to draw on.
+    #
+    def render_background
+      case @theme_options[:background_colors]
+      when Array
+        @base_image = render_gradiated_background(*@theme_options[:background_colors])
+      when String
+        @base_image = render_solid_background(@theme_options[:background_colors])
+      else
+        @base_image = render_image_background(*@theme_options[:background_image])
+      end
+    end
+
+    ##
+    # Make a new image at the current size with a solid +color+.
+    
+    def render_solid_background(color)
+      Image.new(@columns, @rows) {
+        self.background_color = color
+      }
+    end
+
+    # Use with a theme definition method to draw a gradiated background.
+    def render_gradiated_background(top_color, bottom_color)
+      Image.new(@columns, @rows, 
+          GradientFill.new(0, 0, 100, 0, top_color, bottom_color))
+    end
+
+    # Use with a theme to use an image (800x600 original) background.
+    def render_image_background(image_path)
+      image = Image.read(image_path)
+      if @scale != 1.0
+        image[0].resize!(@scale) # TODO Resize with new scale (crop if necessary for wide graph)
+      end
+      image[0]
+    end
+    
+    # Use with a theme to make a transparent background
+    def render_transparent_background
+      Image.new(@columns, @rows) do
+        self.background_color = 'transparent'
+      end
+    end
+
+    def reset_themes
+      @color_index = 0
+      @labels_seen = {}
+      @theme_options = {}
+      
+      @d = Draw.new
+      # Scale down from 800x600 used to calculate drawing.
+      @d = @d.scale(@scale, @scale)
+    end
+
+    def scale(value)
+      value * @scale
+    end
+    
+    # Return a comparable fontsize for the current graph.
+    def scale_fontsize(value)
+      new_fontsize = value * @scale
+      # return new_fontsize < 10.0 ? 10.0 : new_fontsize
+      return new_fontsize
+    end
+
+    def clip_value_if_greater_than(value, max_value)
+      (value > max_value) ? max_value : value
+    end
+
+    # Overridden by subclasses such as stacked bar.
+    def larger_than_max?(data_point, index=0)
+      data_point > @maximum_value
+    end
+
+         def less_than_min?(data_point, index=0)
+      data_point < @minimum_value
+    end
+
+    ##
+    # Overridden by subclasses that need it.
+    def max(data_point, index)
+      data_point
+    end
+
+    ##
+    # Overridden by subclasses that need it.
+         def min(data_point, index)
+      data_point
+    end
+   
+    def significant(inc)
+      return 1.0 if inc == 0 # Keep from going into infinite loop
+      factor = 1.0
+      while (inc < 10)
+        inc *= 10
+        factor /= 10
+      end
+
+      while (inc > 100)
+        inc /= 10
+        factor *= 10
+      end
+
+      res = inc.floor * factor
+      if (res.to_i.to_f == res)
+        res.to_i
+      else
+        res
+      end
+    end
+
+    # Sort with largest overall summed value at front of array 
+    # so it shows up correctly in the drawn graph.
+    def sort_norm_data
+      @norm_data.sort! { |a,b| sums(b[1]) <=> sums(a[1]) }
+    end
+
+    def sums(data_set)
+      total_sum = 0
+      data_set.collect {|num| total_sum += num.to_f }
+      total_sum
+    end
+
+    ##
+    # Used by StackedBar and child classes.
+    #
+    # May need to be moved to the StackedBar class.
+    
+    def get_maximum_by_stack
+      # Get sum of each stack
+      max_hash = {}
+      @data.each do |data_set|
+        data_set[DATA_VALUES_INDEX].each_with_index do |data_point, i|
+          max_hash[i] = 0.0 unless max_hash[i]
+          max_hash[i] += data_point.to_f
+        end
+      end
+
+      # @maximum_value = 0
+      max_hash.keys.each do |key|
+        @maximum_value = max_hash[key] if max_hash[key] > @maximum_value
+      end
+      @minimum_value = 0
+    end
+
+    def make_stacked
+      stacked_values = Array.new(@column_count, 0)
+      @data.each do |value_set|
+        value_set[1].each_with_index do |value, index|
+          stacked_values[index] += value
+        end
+        value_set[1] = stacked_values.dup
+      end
+    end
+
+private
+    
+    # Takes a block and draws it if DEBUG is true.
+    #
+    #   debug { @d.rectangle x1, y1, x2, y2 }
+    #
+    def debug
+      if DEBUG
+        @d = @d.fill 'transparent'
+        @d = @d.stroke 'turquoise'
+        @d = yield
+      end
+    end
+    
+    def increment_color
+      if @color_index == 0
+        @color_index += 1
+        return @colors[0]
+      else
+        if @color_index < @colors.length
+          @color_index += 1
+          return @colors[@color_index - 1]
+        else
+          # Start over
+          @color_index = 0
+          return @colors[-1]
+        end
+      end
+    end
+
+    ##
+    # Return a formatted string representing a number value that should be printed as a label.   
+
+    def label(value)      
+      if (@spread.to_f % @marker_count.to_f == 0) || !@y_axis_increment.nil?
+        return value.to_i.to_s
+      end
+      
+      if @spread > 10.0
+        sprintf("%0i", value)
+      elsif @spread >= 3.0
+        sprintf("%0.2f", value)
+      else
+        value.to_s
+      end
+    end
+
+    ##
+    # Returns the height of the capital letter 'X' for the current font and size.
+    #
+    # Not scaled since it deals with dimensions that the regular 
+    # scaling will handle.
+    #
+    def calculate_caps_height(font_size)
+      @d.pointsize = font_size
+      @d.get_type_metrics(@base_image, 'X').height
+    end
+
+    ##
+    # Returns the width of a string at this pointsize.
+    #
+    # Not scaled since it deals with dimensions that the regular 
+    # scaling will handle.
+    #    
+    def calculate_width(font_size, text)
+      @d.pointsize = font_size
+      @d.get_type_metrics(@base_image, text.to_s).width
+    end
+
+  end # Gruff::Base
+  
+  class IncorrectNumberOfDatasetsException < StandardError; end
+          
+end # Gruff
+
+
+module Magick
+  
+  class Draw
+    
+    # Additional method since Draw.scale doesn't affect annotations.
+    def annotate_scaled(img, width, height, x, y, text, scale)
+      scaled_width = (width * scale) >= 1 ? (width * scale) : 1
+      scaled_height = (height * scale) >= 1 ? (height * scale) : 1
+      
+      self.annotate( img, 
+                      scaled_width, scaled_height,
+                      x * scale, y * scale,
+                      text)
+    end
+    
+  end
+  
+end # Magick
+
diff --git a/lib/gruff-0.2.8/lib/gruff/deprecated.rb b/lib/gruff-0.2.8/lib/gruff/deprecated.rb
new file mode 100644 (file)
index 0000000..be254b5
--- /dev/null
@@ -0,0 +1,39 @@
+
+##
+# A mixin for methods that need to be deleted or have been
+# replaced by cleaner code.
+
+module Gruff
+  module Deprecated
+  
+    def scale_measurements
+      setup_graph_measurements
+    end
+    
+    def total_height
+      @rows + 10
+    end
+    
+    def graph_top
+      @graph_top * @scale
+    end
+    
+    def graph_height
+      @graph_height * @scale
+    end
+    
+    def graph_left 
+      @graph_left * @scale 
+    end
+    
+    def graph_width
+      @graph_width * @scale
+    end
+    
+    # TODO Should be calculate_graph_height
+    # def setup_graph_height
+    #   @graph_height = @graph_bottom - @graph_top
+    # end
+  
+  end
+end
diff --git a/lib/gruff-0.2.8/lib/gruff/line.rb b/lib/gruff-0.2.8/lib/gruff/line.rb
new file mode 100644 (file)
index 0000000..72416b2
--- /dev/null
@@ -0,0 +1,94 @@
+
+require File.dirname(__FILE__) + '/base'
+
+class Gruff::Line < Gruff::Base
+
+  # Draw a dashed line at the given value
+  attr_accessor :baseline_value
+       
+  # Color of the baseline
+  attr_accessor :baseline_color
+  
+  # Hide parts of the graph to fit more datapoints, or for a different appearance.
+  attr_accessor :hide_dots, :hide_lines
+
+  # Call with target pixel width of graph (800, 400, 300), and/or 'false' to omit lines (points only).
+  #
+  #  g = Gruff::Line.new(400) # 400px wide with lines
+  #
+  #  g = Gruff::Line.new(400, false) # 400px wide, no lines (for backwards compatibility)
+  #
+  #  g = Gruff::Line.new(false) # Defaults to 800px wide, no lines (for backwards compatibility)
+  # 
+  # The preferred way is to call hide_dots or hide_lines instead.
+  def initialize(*args)
+    raise ArgumentError, "Wrong number of arguments" if args.length > 2
+    if args.empty? or ((not Numeric === args.first) && (not String === args.first)) then
+      super()
+    else
+      super args.shift
+    end
+    
+    @hide_dots = @hide_lines = false
+    @baseline_color = 'red'
+    @baseline_value = nil
+  end
+
+  def draw
+    super
+
+    return unless @has_data
+    
+    # Check to see if more than one datapoint was given. NaN can result otherwise.  
+    @x_increment = (@column_count > 1) ? (@graph_width / (@column_count - 1).to_f) : @graph_width
+     
+    if (defined?(@norm_baseline)) then
+      level = @graph_top + (@graph_height - @norm_baseline * @graph_height)
+      @d = @d.push
+      @d.stroke_color @baseline_color
+      @d.fill_opacity 0.0
+      @d.stroke_dasharray(10, 20)
+      @d.stroke_width 5
+      @d.line(@graph_left, level, @graph_left + @graph_width, level)
+      @d = @d.pop
+    end
+
+    @norm_data.each do |data_row|      
+      prev_x = prev_y = nil
+
+      data_row[1].each_with_index do |data_point, index|
+        new_x = @graph_left + (@x_increment * index)
+        next if data_point.nil?
+
+        draw_label(new_x, index)
+
+        new_y = @graph_top + (@graph_height - data_point * @graph_height)
+
+        # Reset each time to avoid thin-line errors
+        @d = @d.stroke data_row[DATA_COLOR_INDEX]
+        @d = @d.fill data_row[DATA_COLOR_INDEX]
+        @d = @d.stroke_opacity 1.0
+        @d = @d.stroke_width clip_value_if_greater_than(@columns / (@norm_data.first[1].size * 4), 5.0)
+
+        if !@hide_lines and !prev_x.nil? and !prev_y.nil? then          
+          @d = @d.line(prev_x, prev_y, new_x, new_y)
+        end
+        circle_radius = clip_value_if_greater_than(@columns / (@norm_data.first[1].size * 2.5), 5.0)
+        @d = @d.circle(new_x, new_y, new_x - circle_radius, new_y) unless @hide_dots
+
+        prev_x = new_x
+        prev_y = new_y
+      end
+
+    end
+
+    @d.draw(@base_image)
+  end
+
+  def normalize
+    @maximum_value = [@maximum_value.to_f, @baseline_value.to_f].max
+    super
+    @norm_baseline = (@baseline_value.to_f / @maximum_value.to_f) if @baseline_value
+  end
+  
+end
diff --git a/lib/gruff-0.2.8/lib/gruff/mini/bar.rb b/lib/gruff-0.2.8/lib/gruff/mini/bar.rb
new file mode 100644 (file)
index 0000000..6410d68
--- /dev/null
@@ -0,0 +1,32 @@
+##
+#
+# Makes a small bar graph suitable for display at 200px or even smaller.
+#
+module Gruff
+  module Mini
+
+    class Bar < Gruff::Bar
+
+      include Gruff::Mini::Legend
+      
+      def draw
+        @hide_legend = true
+        @hide_title = true
+        @hide_line_numbers = true
+
+        @marker_font_size = 50.0
+        @minimum_value = 0.0
+        @legend_font_size = 60.0
+
+        expand_canvas_for_vertical_legend
+
+        super
+
+        draw_vertical_legend
+        @d.draw(@base_image)
+      end
+
+    end
+  
+  end
+end
diff --git a/lib/gruff-0.2.8/lib/gruff/mini/legend.rb b/lib/gruff-0.2.8/lib/gruff/mini/legend.rb
new file mode 100644 (file)
index 0000000..25039cf
--- /dev/null
@@ -0,0 +1,77 @@
+module Gruff
+  module Mini
+    module Legend
+      
+      ##
+      # The canvas needs to be bigger so we can put the legend beneath it.
+
+      def expand_canvas_for_vertical_legend
+        @original_rows = @raw_rows
+        @rows += @data.length * calculate_caps_height(scale_fontsize(@legend_font_size)) * 1.7
+        render_background
+      end
+      
+      ##
+      # Draw the legend beneath the existing graph.
+
+      def draw_vertical_legend
+                
+        @legend_labels = @data.collect {|item| item[Gruff::Base::DATA_LABEL_INDEX] }
+        
+        legend_square_width = 40.0 # small square with color of this item
+        legend_square_margin = 10.0
+        @legend_left_margin = 40.0
+        legend_top_margin = 40.0
+
+        # May fix legend drawing problem at small sizes
+        @d.font = @font if @font
+        @d.pointsize = @legend_font_size
+
+        current_x_offset = @graph_left + @legend_left_margin
+        current_y_offset = @original_rows + legend_top_margin
+
+        debug { @d.line 0.0, current_y_offset, @raw_columns, current_y_offset }
+
+        @legend_labels.each_with_index do |legend_label, index|        
+
+          # Draw label
+          @d.fill = @font_color
+          @d.font = @font if @font
+          @d.pointsize = scale_fontsize(@legend_font_size)
+          @d.stroke = 'transparent'
+          @d.font_weight = Magick::NormalWeight
+          @d.gravity = Magick::WestGravity
+          @d = @d.annotate_scaled( @base_image, 
+                            @raw_columns, 1.0,
+                            current_x_offset + (legend_square_width * 1.7), current_y_offset, 
+                            truncate_legend_label(legend_label), @scale)
+
+          # Now draw box with color of this dataset
+          @d = @d.stroke 'transparent'
+          @d = @d.fill @data[index][Gruff::Base::DATA_COLOR_INDEX]
+          @d = @d.rectangle(current_x_offset, 
+                            current_y_offset - legend_square_width / 2.0, 
+                            current_x_offset + legend_square_width, 
+                            current_y_offset + legend_square_width / 2.0)
+          
+          current_y_offset += calculate_caps_height(@legend_font_size) * 1.7
+        end
+        @color_index = 0
+      end
+
+      ##
+      # Shorten long labels so they will fit on the canvas.
+      #
+      #   Department of Hu...
+      
+      def truncate_legend_label(label)
+        truncated_label = label.to_s
+        while calculate_width(scale_fontsize(@legend_font_size), truncated_label) > (@columns - @legend_left_margin - Gruff::Base::RIGHT_MARGIN) && (truncated_label.length > 1)
+          truncated_label = truncated_label[0..truncated_label.length-2]
+        end
+        truncated_label + (truncated_label.length < label.to_s.length ? "…" : '')
+      end
+      
+    end
+  end
+end
diff --git a/lib/gruff-0.2.8/lib/gruff/mini/pie.rb b/lib/gruff-0.2.8/lib/gruff/mini/pie.rb
new file mode 100644 (file)
index 0000000..7822ba5
--- /dev/null
@@ -0,0 +1,36 @@
+##
+#
+# Makes a small pie graph suitable for display at 200px or even smaller.
+#
+module Gruff
+  module Mini
+
+    class Pie < Gruff::Pie
+
+      include Gruff::Mini::Legend
+
+      def initialize_ivars
+        super
+        
+        @hide_legend = true
+        @hide_title = true
+        @hide_line_numbers = true
+  
+        @marker_font_size = 60.0
+        @legend_font_size = 60.0
+      end
+
+      def draw
+        expand_canvas_for_vertical_legend
+        
+        super
+        
+        draw_vertical_legend
+        
+        @d.draw(@base_image)
+      end # def draw
+
+    end # class Pie
+  
+  end
+end
diff --git a/lib/gruff-0.2.8/lib/gruff/mini/side_bar.rb b/lib/gruff-0.2.8/lib/gruff/mini/side_bar.rb
new file mode 100644 (file)
index 0000000..9774a50
--- /dev/null
@@ -0,0 +1,22 @@
+##
+#
+# Makes a small pie graph suitable for display at 200px or even smaller.
+#
+module Gruff
+  module Mini
+
+    class SideBar < Gruff::SideBar
+
+      def initialize_ivars
+        super
+        @hide_legend = true
+        @hide_title = true
+        @hide_line_numbers = true
+
+        @marker_font_size = 50.0
+      end
+      
+    end
+  
+  end
+end
diff --git a/lib/gruff-0.2.8/lib/gruff/net.rb b/lib/gruff-0.2.8/lib/gruff/net.rb
new file mode 100644 (file)
index 0000000..debf9f7
--- /dev/null
@@ -0,0 +1,142 @@
+
+require File.dirname(__FILE__) + '/base'
+
+# Experimental!!! See also the Spider graph.
+class Gruff::Net < Gruff::Base
+
+  # Hide parts of the graph to fit more datapoints, or for a different appearance.
+  attr_accessor :hide_dots
+
+  def initialize(*args)
+    super
+    
+    @hide_dots = false
+  end
+
+  def draw
+
+    super
+
+    return unless @has_data
+
+    @radius = @graph_height / 2.0
+    @center_x = @graph_left + (@graph_width / 2.0)
+    @center_y = @graph_top + (@graph_height / 2.0) - 10 # Move graph up a bit
+
+    @x_increment = @graph_width / (@column_count - 1).to_f
+    circle_radius = clip_value_if_greater_than(@columns / (@norm_data.first[1].size * 2.5), 5.0)
+
+    @d = @d.stroke_opacity 1.0
+    @d = @d.stroke_width clip_value_if_greater_than(@columns / (@norm_data.first[1].size * 4), 5.0)
+
+    if (defined?(@norm_baseline)) then
+      level = @graph_top + (@graph_height - @norm_baseline * @graph_height)
+      @d = @d.push
+      @d.stroke_color @baseline_color
+      @d.fill_opacity 0.0
+      @d.stroke_dasharray(10, 20)
+      @d.stroke_width 5
+      @d.line(@graph_left, level, @graph_left + @graph_width, level)
+      @d = @d.pop
+    end
+
+    @norm_data.each do |data_row|
+      prev_x = prev_y = nil
+      @d = @d.stroke data_row[DATA_COLOR_INDEX]
+      @d = @d.fill data_row[DATA_COLOR_INDEX]
+
+      data_row[1].each_with_index do |data_point, index|
+        next if data_point.nil?
+
+        rad_pos = index * Math::PI * 2 / @column_count
+        point_distance = data_point * @radius
+        start_x = @center_x + Math::sin(rad_pos) * point_distance
+        start_y = @center_y - Math::cos(rad_pos) * point_distance
+
+        next_index = index + 1 < data_row[1].length ? index + 1 : 0
+
+        next_rad_pos = next_index * Math::PI * 2 / @column_count
+        next_point_distance = data_row[1][next_index] * @radius
+        end_x = @center_x + Math::sin(next_rad_pos) * next_point_distance
+        end_y = @center_y - Math::cos(next_rad_pos) * next_point_distance
+
+        @d = @d.line(start_x, start_y, end_x, end_y)
+
+        @d = @d.circle(start_x, start_y, start_x - circle_radius, start_y) unless @hide_dots
+      end
+
+    end
+
+    @d.draw(@base_image)
+  end
+
+
+  # the lines connecting in the center, with the first line vertical
+  def draw_line_markers
+    return if @hide_line_markers
+
+
+    # have to do this here (AGAIN)... see draw() in this class
+    # because this funtion is called before the @radius, @center_x and @center_y are set
+    @radius = @graph_height / 2.0
+    @center_x = @graph_left + (@graph_width / 2.0)
+    @center_y = @graph_top + (@graph_height / 2.0) - 10 # Move graph up a bit
+
+
+    # Draw horizontal line markers and annotate with numbers
+    @d = @d.stroke(@marker_color)
+    @d = @d.stroke_width 1
+
+
+    (0..@column_count-1).each do |index|
+      rad_pos = index * Math::PI * 2 / @column_count
+
+      @d = @d.line(@center_x, @center_y, @center_x + Math::sin(rad_pos) * @radius, @center_y - Math::cos(rad_pos) * @radius)
+
+
+      marker_label = labels[index] ? labels[index].to_s : '000'
+
+      draw_label(@center_x, @center_y, rad_pos * 360 / (2 * Math::PI), @radius, marker_label)
+    end
+  end
+
+private
+
+  def draw_label(center_x, center_y, angle, radius, amount)
+    r_offset = 1.1
+    x_offset = center_x # + 15 # The label points need to be tweaked slightly
+    y_offset = center_y # + 0  # This one doesn't though
+    x = x_offset + (radius * r_offset * Math.sin(angle.deg2rad))
+    y = y_offset - (radius * r_offset * Math.cos(angle.deg2rad))
+
+    # Draw label
+    @d.fill = @marker_color
+    @d.font = @font if @font
+    @d.pointsize = scale_fontsize(20)
+    @d.stroke = 'transparent'
+    @d.font_weight = BoldWeight
+    s = angle.deg2rad / (2*Math::PI)
+    @d.gravity = SouthGravity     if s >= 0.96 or s < 0.04
+    @d.gravity = SouthWestGravity if s >= 0.04 or s < 0.21
+    @d.gravity = WestGravity      if s >= 0.21 or s < 0.29
+    @d.gravity = NorthWestGravity if s >= 0.29 or s < 0.46
+    @d.gravity = NorthGravity     if s >= 0.46 or s < 0.54
+    @d.gravity = NorthEastGravity if s >= 0.54 or s < 0.71
+    @d.gravity = EastGravity      if s >= 0.71 or s < 0.79
+    @d.gravity = SouthEastGravity if s >= 0.79 or s < 0.96
+#     @d.gravity = NorthGravity
+    @d.annotate_scaled(@base_image, 0, 0, x, y, amount, @scale)
+  end
+
+end
+
+# # This method is already in Float
+# class Float
+#   # Used for degree => radian conversions
+#   def deg2rad
+#     self * (Math::PI/180.0)
+#   end
+# end
+
+
+
diff --git a/lib/gruff-0.2.8/lib/gruff/photo_bar.rb b/lib/gruff-0.2.8/lib/gruff/photo_bar.rb
new file mode 100644 (file)
index 0000000..7240da0
--- /dev/null
@@ -0,0 +1,100 @@
+require File.dirname(__FILE__) + '/base'
+
+# EXPERIMENTAL!
+#
+# Doesn't work yet.
+#
+class Gruff::PhotoBar < Gruff::Base
+
+# TODO
+#
+# define base and cap in yml
+# allow for image directory to be located elsewhere
+# more exact measurements for bar heights (go all the way to the bottom of the graph)
+# option to tile images instead of use a single image
+# drop base label a few px lower so photo bar graphs can have a base dropping over the lower marker line
+#
+
+  # The name of a pre-packaged photo-based theme.
+  attr_reader :theme
+
+#   def initialize(target_width=800)
+#     super
+#     init_photo_bar_graphics()
+#   end
+
+  def draw
+    super
+    return unless @has_data
+
+    return # TODO Remove for further development
+
+    init_photo_bar_graphics()
+    
+    #Draw#define_clip_path()
+    #Draw#clip_path(pathname)
+    #Draw#composite....with bar graph image OverCompositeOp
+    #
+    # See also
+    #
+    # Draw.pattern # define an image to tile as the filling of a draw object
+    # 
+
+    # Setup spacing.
+    #
+    # Columns sit side-by-side.
+    spacing_factor = 0.9
+    @bar_width = @norm_data[0][DATA_COLOR_INDEX].columns
+
+    @norm_data.each_with_index do |data_row, row_index|
+  
+      data_row[1].each_with_index do |data_point, point_index|
+        data_point = 0 if data_point.nil?
+        # Use incremented x and scaled y
+        left_x = @graph_left + (@bar_width * (row_index + point_index + ((@data.length - 1) * point_index)))
+        left_y = @graph_top + (@graph_height - data_point * @graph_height) + 1
+        right_x = left_x + @bar_width * spacing_factor
+        right_y = @graph_top + @graph_height - 1
+      
+        bar_image_width = data_row[DATA_COLOR_INDEX].columns
+        bar_image_height = right_y.to_f - left_y.to_f
+      
+        # Crop to scale for data
+        bar_image = data_row[DATA_COLOR_INDEX].crop(0, 0, bar_image_width, bar_image_height)
+        
+        @d.gravity = NorthWestGravity
+        @d = @d.composite(left_x, left_y, bar_image_width, bar_image_height, bar_image)
+      
+        # Calculate center based on bar_width and current row
+        label_center = @graph_left + (@data.length * @bar_width * point_index) + (@data.length * @bar_width / 2.0)
+        draw_label(label_center, point_index)
+      end
+
+    end
+
+    @d.draw(@base_image)    
+  end
+
+
+  # Return the chosen theme or the default
+  def theme
+    @theme || 'plastik'
+  end
+
+protected
+
+  # Sets up colors with a list of images that will be used.
+  # Images should be 340px tall
+  def init_photo_bar_graphics    
+    color_list = Array.new
+    theme_dir = File.dirname(__FILE__) + '/../../assets/' + theme
+
+    Dir.open(theme_dir).each do |file|
+      next unless /\.png$/.match(file)
+      color_list << Image.read("#{theme_dir}/#{file}").first
+    end
+    @colors = color_list
+  end
+
+end
+
diff --git a/lib/gruff-0.2.8/lib/gruff/pie.rb b/lib/gruff-0.2.8/lib/gruff/pie.rb
new file mode 100644 (file)
index 0000000..b143e70
--- /dev/null
@@ -0,0 +1,108 @@
+
+require File.dirname(__FILE__) + '/base'
+
+class Gruff::Pie < Gruff::Base
+
+  TEXT_OFFSET_PERCENTAGE = 0.15
+
+  # Can be used to make the pie start cutting slices at the top (-90.0)
+  # or at another angle. Default is 0.0, which starts at 3 o'clock.
+  attr_accessor :zero_degree
+  
+  def initialize_ivars
+    super
+    @zero_degree = 0.0
+  end
+
+  def draw
+    @hide_line_markers = true
+    
+    super
+
+    return unless @has_data
+
+    diameter = @graph_height
+    radius = ([@graph_width, @graph_height].min / 2.0) * 0.8
+    top_x = @graph_left + (@graph_width - diameter) / 2.0
+    center_x = @graph_left + (@graph_width / 2.0)
+    center_y = @graph_top + (@graph_height / 2.0) - 10 # Move graph up a bit
+    total_sum = sums_for_pie()
+    prev_degrees = @zero_degree
+
+    # Use full data since we can easily calculate percentages
+    @data.sort{ |a, b| a[DATA_VALUES_INDEX][0] <=> b[DATA_VALUES_INDEX][0] }.each do |data_row|
+      if data_row[DATA_VALUES_INDEX][0] > 0
+        @d = @d.stroke data_row[DATA_COLOR_INDEX]
+        @d = @d.fill 'transparent'
+        @d.stroke_width(radius) # stroke width should be equal to radius. we'll draw centered on (radius / 2)
+
+        current_degrees = (data_row[DATA_VALUES_INDEX][0] / total_sum) * 360.0 
+
+        # ellipse will draw the the stroke centered on the first two parameters offset by the second two.
+        # therefore, in order to draw a circle of the proper diameter we must center the stroke at
+        # half the radius for both x and y
+        @d = @d.ellipse(center_x, center_y, 
+                  radius / 2.0, radius / 2.0,
+                  prev_degrees, prev_degrees + current_degrees + 0.5) # <= +0.5 'fudge factor' gets rid of the ugly gaps
+                  
+        half_angle = prev_degrees + ((prev_degrees + current_degrees) - prev_degrees) / 2
+
+        # End the string with %% to escape the single %.
+        # RMagick must use sprintf with the string and % has special significance.
+        label_string = ((data_row[DATA_VALUES_INDEX][0] / total_sum) * 100.0).round.to_s + '%%'
+        @d = draw_label(center_x,center_y, 
+                    half_angle, radius + (radius * TEXT_OFFSET_PERCENTAGE), 
+                    label_string)
+      
+        prev_degrees += current_degrees
+      end
+    end
+
+    # TODO debug a circle where the text is drawn...
+    
+    @d.draw(@base_image)
+  end
+
+private
+
+  ##
+  # Labels are drawn around a slightly wider ellipse to give room for 
+  # labels on the left and right.
+  def draw_label(center_x, center_y, angle, radius, amount)
+    # TODO Don't use so many hard-coded numbers
+    r_offset = 20.0      # The distance out from the center of the pie to get point
+    x_offset = center_x # + 15.0 # The label points need to be tweaked slightly
+    y_offset = center_y  # This one doesn't though
+    radius_offset = (radius + r_offset)
+    ellipse_factor = radius_offset * 0.15
+    x = x_offset + ((radius_offset + ellipse_factor) * Math.cos(angle.deg2rad))
+    y = y_offset + (radius_offset * Math.sin(angle.deg2rad))
+    
+    # Draw label
+    @d.fill = @font_color
+    @d.font = @font if @font
+    @d.pointsize = scale_fontsize(@marker_font_size)
+    @d.stroke = 'transparent'
+    @d.font_weight = BoldWeight
+    @d.gravity = CenterGravity
+    @d.annotate_scaled( @base_image, 
+                      0, 0,
+                      x, y, 
+                      amount, @scale)
+  end
+
+  def sums_for_pie
+    total_sum = 0.0
+    @data.collect {|data_row| total_sum += data_row[DATA_VALUES_INDEX][0] }
+    total_sum
+  end
+
+end
+
+
+class Float
+  # Used for degree => radian conversions
+  def deg2rad
+    self * (Math::PI/180.0)
+  end
+end
diff --git a/lib/gruff-0.2.8/lib/gruff/scene.rb b/lib/gruff-0.2.8/lib/gruff/scene.rb
new file mode 100644 (file)
index 0000000..b0220eb
--- /dev/null
@@ -0,0 +1,197 @@
+
+require "observer"
+require File.dirname(__FILE__) + '/base'
+
+##
+# A scene is a non-linear graph that assembles layers together to tell a story.
+# Layers are folders with appropriately named files (see below). You can group 
+# layers and control them together or just set their values individually.
+#
+# Examples:
+#
+# * A city scene that changes with the time of day and the weather conditions.
+# * A traffic map that shows red lines on streets that are crowded and green on free-flowing ones.
+#
+# Usage:
+# 
+#  g = Gruff::Scene.new("500x100", "artwork/city_scene")
+#  g.layers = %w(background haze sky clouds)
+#  g.weather_group = %w(clouds)
+#  g.time_group = %w(background sky)
+#  g.weather = "cloudy"
+#  g.time = Time.now
+#  g.haze = true
+#  g.write "hazy_daytime_city_scene.png"
+#
+#
+#
+# If there is a file named 'default.png', it will be selected (unless other values are provided to override it).
+#
+class Gruff::Scene < Gruff::Base
+    
+  # An array listing the foldernames that will be rendered, from back to front.
+  #
+  #  g.layers = %w(sky clouds buildings street people)
+  #
+  attr_reader :layers
+
+  def initialize(target_width, base_dir)
+    @base_dir = base_dir
+    @groups = {}
+    @layers = []    
+    super target_width
+  end
+
+  def draw
+    # Join all the custom paths and filter out the empty ones
+    image_paths = @layers.map { |layer| layer.path }.select { |path| !path.empty? }
+    images = Magick::ImageList.new(*image_paths)
+    @base_image = images.flatten_images
+  end
+
+  def layers=(ordered_list)
+    ordered_list.each do |layer_name|
+      @layers << Gruff::Layer.new(@base_dir, layer_name)
+    end
+  end
+
+  # Group layers to input values
+  #
+  #  g.weather_group = ["sky", "sea", "clouds"]
+  #
+  # Set input values
+  #
+  #  g.weather = "cloudy"
+  #
+  def method_missing(method_name, *args)
+    case method_name.to_s
+    when /^(\w+)_group=$/
+      add_group $1, *args
+      return
+    when /^(\w+)=$/
+      set_input $1, args.first
+      return
+    end
+    super
+  end
+
+private
+
+  def add_group(input_name, layer_names)
+    @groups[input_name] = Gruff::Group.new(input_name, @layers.select { |layer| layer_names.include?(layer.name) })
+  end
+
+  def set_input(input_name, input_value)
+    if not @groups[input_name].nil?
+      @groups[input_name].send_updates(input_value)
+    else
+      if chosen_layer = @layers.detect { |layer| layer.name == input_name }
+        chosen_layer.update input_value
+      end
+    end
+  end
+  
+end
+
+
+class Gruff::Group
+
+  include Observable
+  attr_reader :name
+
+  def initialize(folder_name, layers)
+    @name = folder_name
+    layers.each do |layer|
+      layer.observe self
+    end
+  end
+  
+  def send_updates(value)
+    changed
+    notify_observers value
+  end
+  
+end
+
+
+class Gruff::Layer
+  
+  attr_reader :name
+  
+  def initialize(base_dir, folder_name)
+    @base_dir = base_dir.to_s
+    @name = folder_name.to_s
+    @filenames = Dir.open(File.join(base_dir, folder_name)).entries.select { |file| file =~ /^[^.]+\.png$/ }
+    @selected_filename = select_default
+  end
+  
+  # Register this layer so it receives updates from the group
+  def observe(obj)
+    obj.add_observer self
+  end
+  
+  # Choose the appropriate filename for this layer, based on the input
+  def update(value)
+    @selected_filename =  case value.to_s
+                          when /^(true|false)$/
+                            select_boolean value
+                          when /^(\w|\s)+$/
+                            select_string value
+                          when /^-?(\d+\.)?\d+$/
+                            select_numeric value
+                          when /(\d\d):(\d\d):\d\d/
+                            select_time "#{$1}#{$2}"
+                          else
+                            select_default
+                          end
+    # Finally, try to use 'default' if we're still blank
+    @selected_filename ||= select_default
+  end
+
+  # Returns the full path to the selected image, or a blank string
+  def path
+    unless @selected_filename.nil? || @selected_filename.empty?
+      return File.join(@base_dir, @name, @selected_filename)
+    end
+    ''
+  end
+
+private
+
+  # Match "true.png" or "false.png"
+  def select_boolean(value)
+    file_exists_or_blank value.to_s
+  end
+
+  # Match -5 to _5.png
+  def select_numeric(value)
+    file_exists_or_blank value.to_s.gsub('-', '_')
+  end
+  
+  def select_time(value)
+    times = @filenames.map { |filename| filename.gsub('.png', '') }
+    times.each_with_index do |time, index|
+      if (time > value) && (index > 0)
+        return "#{times[index - 1]}.png"
+      end
+    end
+    return "#{times.last}.png"
+  end
+  
+  # Match "partly cloudy" to "partly_cloudy.png"
+  def select_string(value)
+    file_exists_or_blank value.to_s.gsub(' ', '_')
+  end
+  
+  def select_default
+    @filenames.include?("default.png") ? "default.png" : ''
+  end
+
+  # Returns the string "#{filename}.png", if it exists.
+  #
+  # Failing that, it returns default.png, or '' if that doesn't exist.
+  def file_exists_or_blank(filename)
+    @filenames.include?("#{filename}.png") ? "#{filename}.png" : select_default
+  end
+  
+end
diff --git a/lib/gruff-0.2.8/lib/gruff/side_bar.rb b/lib/gruff-0.2.8/lib/gruff/side_bar.rb
new file mode 100644 (file)
index 0000000..9662d41
--- /dev/null
@@ -0,0 +1,117 @@
+require File.dirname(__FILE__) + '/base'
+
+##
+# EXPERIMENTAL
+#
+# Graph with horizontal bars instead of vertical.
+#
+# TODO SideStackedBar should probably inherit from this 
+# to consolidate the line marker drawing.
+class Gruff::SideBar < Gruff::Base
+
+  # Instead of base class version, draws vertical background lines and label
+  def draw_line_markers
+
+    return if @hide_line_markers
+
+    @d = @d.stroke_antialias false
+      
+    # Draw horizontal line markers and annotate with numbers
+    @d = @d.stroke(@marker_color)
+    @d = @d.stroke_width 1
+    number_of_lines = 5
+
+    # TODO Round maximum marker value to a round number like 100, 0.1, 0.5, etc.
+    increment = significant(@maximum_value.to_f / number_of_lines)
+    (0..number_of_lines).each do |index|
+
+      line_diff    = (@graph_right - @graph_left) / number_of_lines
+      x            = @graph_right - (line_diff * index) - 1
+      @d           = @d.line(x, @graph_bottom, x, @graph_top)
+      diff         = index - number_of_lines
+      marker_label = diff.abs * increment
+
+      unless @hide_line_numbers
+        @d.fill      = @font_color
+        @d.font      = @font if @font
+        @d.stroke    = 'transparent'
+        @d.pointsize = scale_fontsize(@marker_font_size)
+        # @d.gravity = NorthGravity
+        @d           = @d.annotate_scaled( @base_image, 
+                          100, 20,
+                          x - (@marker_font_size/1.5), @graph_bottom + 40, 
+                          marker_label.to_s, @scale)
+      end # unless
+      @d = @d.stroke_antialias true
+    end
+  end
+
+  ##
+  # Draw on the Y axis instead of the X
+  
+  def draw_label(y_offset, index)
+    if !@labels[index].nil? && @labels_seen[index].nil?
+      @d.fill             = @font_color
+      @d.font             = @font if @font
+      @d.stroke           = 'transparent'
+      @d.font_weight      = NormalWeight
+      @d.pointsize        = scale_fontsize(@marker_font_size)
+      @d.gravity          = EastGravity
+      @d                  = @d.annotate_scaled(@base_image,
+                              1, 1,
+                              -@graph_left + LABEL_MARGIN * 2.0, y_offset,
+                              @labels[index], @scale)
+      @labels_seen[index] = 1
+    end
+  end
+
+  def draw
+    @has_left_labels = true
+    super
+
+    return unless @has_data
+
+    # Setup spacing.
+    #
+    # Columns sit stacked.
+    spacing_factor = 0.9
+
+    @bar_width = @graph_height / @column_count.to_f
+    @d         = @d.stroke_opacity 0.0
+    height     = Array.new(@column_count, 0)
+    length     = Array.new(@column_count, @graph_left)
+
+    @norm_data.each_with_index do |data_row, row_index|
+      @d = @d.fill data_row[DATA_COLOR_INDEX]
+
+      data_row[1].each_with_index do |data_point, point_index|
+
+         # Using the original calcs from the stacked bar chart 
+         # to get the difference between
+         # part of the bart chart we wish to stack.
+        temp1      = @graph_left + (@graph_width -
+                                data_point * @graph_width - 
+                                height[point_index]) + 1
+        temp2      = @graph_left + @graph_width - height[point_index] - 1
+        difference = temp2 - temp1
+
+        left_x                = length[point_index] #+ 1
+        left_y                = @graph_top + (@bar_width * point_index)
+        right_x               = left_x + difference
+        right_y               = left_y + @bar_width * spacing_factor
+        length[point_index] += difference
+        height[point_index] += (data_point * @graph_width - 2)
+
+        @d                    = @d.rectangle(left_x, left_y, right_x, right_y)
+
+        # Calculate center based on bar_width and current row
+        label_center          = @graph_top + (@bar_width * point_index) + (@bar_width * spacing_factor / 2.0)
+        draw_label(label_center, point_index)
+      end
+
+    end
+
+    @d.draw(@base_image)    
+  end
+
+end
diff --git a/lib/gruff-0.2.8/lib/gruff/side_stacked_bar.rb b/lib/gruff-0.2.8/lib/gruff/side_stacked_bar.rb
new file mode 100644 (file)
index 0000000..e4eff6a
--- /dev/null
@@ -0,0 +1,121 @@
+##
+# New gruff graph type added to enable sideways stacking bar charts (basically looks like a x/y
+# flip of a standard stacking bar chart)
+#
+# alun.eyre@googlemail.com 
+#
+require File.dirname(__FILE__) + '/base'
+
+class Gruff::SideStackedBar < Gruff::Base
+
+    # instead of base class version, draws vertical background lines and label
+    def draw_line_markers
+
+      return if @hide_line_markers
+
+      # Draw horizontal line markers and annotate with numbers
+      @d = @d.stroke(@marker_color)
+      @d = @d.stroke_width 1
+      number_of_lines = 5
+
+      # TODO Round maximum marker value to a round number like 100, 0.1, 0.5, etc.
+      increment = significant(@maximum_value.to_f / number_of_lines)
+      (0..number_of_lines).each do |index|
+
+        line_diff = (@graph_right - @graph_left) / number_of_lines
+        x = @graph_right - (line_diff * index) - 1
+        @d = @d.line(x, @graph_bottom, x, @graph_top)
+
+        diff = index - number_of_lines
+        marker_label = diff.abs * increment
+
+        @d.fill = @marker_color
+        @d.font = @font if @font
+        @d.stroke = 'transparent'
+        @d.pointsize = scale_fontsize(@marker_font_size)
+#        @d.gravity = NorthGravity
+        @d = @d.annotate_scaled( @base_image, 
+                          100, 20,
+                          x - (@marker_font_size/1.5), @graph_bottom + 40, 
+                          marker_label.to_s, @scale)
+
+      end
+    end
+
+    # instead of base class version, modified to enable us to draw on the Y axis instead of X
+    def draw_label(y_offset, index)
+      if !@labels[index].nil? && @labels_seen[index].nil?
+        @d.fill = @marker_color
+        @d.font = @font if @font
+        @d.stroke = 'transparent'
+        @d.font_weight = NormalWeight
+        @d.pointsize = scale_fontsize(@marker_font_size)
+        @d.gravity = CenterGravity
+        @d = @d.annotate_scaled(@base_image,
+                                1, 1,
+                                @graph_left / 2, y_offset,
+                                @labels[index], @scale)
+        @labels_seen[index] = 1
+      end
+    end
+
+    def draw
+      @has_left_labels = true
+      get_maximum_by_stack
+      super
+
+      return unless @has_data
+
+      # Setup spacing.
+      #
+      # Columns sit stacked.
+      spacing_factor = 0.9
+
+      @bar_width = @graph_height / @column_count.to_f
+      @d = @d.stroke_opacity 0.0
+      height = Array.new(@column_count, 0)
+      length = Array.new(@column_count, @graph_left)
+
+      @norm_data.each_with_index do |data_row, row_index|
+        @d = @d.fill data_row[DATA_COLOR_INDEX]
+
+        data_row[1].each_with_index do |data_point, point_index|
+
+         ## using the original calcs from the stacked bar chart to get the difference between
+         ## part of the bart chart we wish to stack.
+         temp1 = @graph_left + (@graph_width -
+                                      data_point * @graph_width - 
+                                      height[point_index]) + 1
+         temp2 = @graph_left + @graph_width - height[point_index] - 1
+         difference = temp2 - temp1
+
+         left_x = length[point_index] #+ 1
+                left_y = @graph_top + (@bar_width * point_index)
+         right_x = left_x + difference
+                right_y = left_y + @bar_width * spacing_factor
+         length[point_index] += difference
+          height[point_index] += (data_point * @graph_width - 2)
+
+          @d = @d.rectangle(left_x, left_y, right_x, right_y)
+
+          # Calculate center based on bar_width and current row
+          label_center = @graph_top + (@bar_width * point_index) + (@bar_width * spacing_factor / 2.0)
+          draw_label(label_center, point_index)
+        end
+
+      end
+
+      @d.draw(@base_image)    
+    end
+
+    protected
+
+    def larger_than_max?(data_point, index=0)
+      max(data_point, index) > @maximum_value
+    end
+
+    def max(data_point, index)
+      @data.inject(0) {|sum, item| sum + item[1][index]}
+    end
+
+end
diff --git a/lib/gruff-0.2.8/lib/gruff/spider.rb b/lib/gruff-0.2.8/lib/gruff/spider.rb
new file mode 100644 (file)
index 0000000..7710426
--- /dev/null
@@ -0,0 +1,130 @@
+
+require File.dirname(__FILE__) + '/base'
+
+# Experimental!!! See also the Net graph.
+#
+# Submitted by Kevin Clark http://glu.ttono.us/
+class Gruff::Spider < Gruff::Base
+  
+  # Hide all text
+  attr_reader :hide_text
+  attr_accessor :hide_axes
+  attr_reader :transparent_background
+  
+  def transparent_background=(value)
+    @transparent_background = value
+    @base_image = render_transparent_background if value
+  end
+
+  def hide_text=(value)
+    @hide_title = @hide_text = value
+  end
+  
+  def initialize(max_value, target_width = 800)
+    super(target_width)
+    @max_value = max_value
+    @hide_legend = true;
+  end
+  
+  def draw
+    @hide_line_markers = true
+    
+    super
+
+    return unless @has_data
+
+    # Setup basic positioning
+    diameter = @graph_height
+    radius = @graph_height / 2.0
+    top_x = @graph_left + (@graph_width - diameter) / 2.0
+    center_x = @graph_left + (@graph_width / 2.0)
+    center_y = @graph_top + (@graph_height / 2.0) - 25 # Move graph up a bit
+    
+    @unit_length = radius / @max_value
+    
+        
+    total_sum = sums_for_spider
+    prev_degrees = 0.0
+    additive_angle = (2 * Math::PI)/ @data.size
+    
+    current_angle = 0.0
+
+    # Draw axes
+    draw_axes(center_x, center_y, radius, additive_angle) unless hide_axes    
+    
+    # Draw polygon
+    draw_polygon(center_x, center_y, additive_angle)
+    
+     
+    @d.draw(@base_image)
+  end
+
+private
+  
+  def normalize_points(value)
+    value * @unit_length
+  end
+  
+  def draw_label(center_x, center_y, angle, radius, amount)
+    r_offset = 50      # The distance out from the center of the pie to get point
+    x_offset = center_x      # The label points need to be tweaked slightly
+    y_offset = center_y + 0  # This one doesn't though
+    x = x_offset + ((radius + r_offset) * Math.cos(angle))
+    y = y_offset + ((radius + r_offset) * Math.sin(angle))
+    
+    # Draw label
+    @d.fill = @marker_color
+    @d.font = @font if @font
+    @d.pointsize = scale_fontsize(legend_font_size)
+    @d.stroke = 'transparent'
+    @d.font_weight = BoldWeight
+    @d.gravity = CenterGravity
+    @d.annotate_scaled( @base_image, 
+                      0, 0,
+                      x, y, 
+                      amount, @scale)
+  end
+  
+  def draw_axes(center_x, center_y, radius, additive_angle, line_color = nil)
+    return if hide_axes
+    
+    current_angle = 0.0
+    
+    @data.each do |data_row|
+      @d.stroke(line_color || data_row[DATA_COLOR_INDEX])
+      @d.stroke_width 5.0
+    
+      x_offset = radius * Math.cos(current_angle)
+      y_offset = radius * Math.sin(current_angle)
+
+      @d.line(center_x, center_y,
+              center_x + x_offset,
+              center_y + y_offset)
+            
+      draw_label(center_x, center_y, current_angle, radius, data_row[0].to_s) unless hide_text
+            
+      current_angle += additive_angle
+    end
+  end
+  
+  def draw_polygon(center_x, center_y, additive_angle, color = nil)
+    points = []
+    current_angle = 0.0
+    @data.each do |data_row|
+      points << center_x + normalize_points(data_row[1][0]) * Math.cos(current_angle)
+      points << center_y + normalize_points(data_row[1][0]) * Math.sin(current_angle)
+      current_angle += additive_angle
+    end
+    
+    @d.stroke_width 1.0
+    @d.stroke(color || @marker_color)
+    @d.fill(color || @marker_color)
+    @d.fill_opacity 0.4
+    @d.polygon(*points)
+  end
+  
+  def sums_for_spider
+    @data.inject(0.0) {|sum, data_row| sum += data_row[1][0]}
+  end
+
+end
diff --git a/lib/gruff-0.2.8/lib/gruff/stacked_bar.rb b/lib/gruff-0.2.8/lib/gruff/stacked_bar.rb
new file mode 100644 (file)
index 0000000..bf28df7
--- /dev/null
@@ -0,0 +1,49 @@
+
+require File.dirname(__FILE__) + '/base'
+
+class Gruff::StackedBar < Gruff::Base
+
+    # Draws a bar graph, but multiple sets are stacked on top of each other.
+    def draw
+      get_maximum_by_stack
+      super
+      return unless @has_data
+
+      # Setup spacing.
+      #
+      # Columns sit stacked.
+      spacing_factor = 0.9
+      @bar_width = @graph_width / @column_count.to_f
+    
+      @d = @d.stroke_opacity 0.0
+      
+      height = Array.new(@column_count, 0)
+    
+      @norm_data.each_with_index do |data_row, row_index|
+        @d = @d.fill data_row[DATA_COLOR_INDEX]
+      
+        data_row[1].each_with_index do |data_point, point_index|
+          # Use incremented x and scaled y
+          left_x = @graph_left + (@bar_width * point_index)
+          left_y = @graph_top + (@graph_height -
+                                 data_point * @graph_height - 
+                                 height[point_index]) + 1
+          right_x = left_x + @bar_width * spacing_factor
+          right_y = @graph_top + @graph_height - height[point_index] - 1
+          
+          # update the total height of the current stacked bar
+          height[point_index] += (data_point * @graph_height - 2)
+          
+          @d = @d.rectangle(left_x, left_y, right_x, right_y)
+          
+          # Calculate center based on bar_width and current row
+          label_center = @graph_left + (@bar_width * point_index) + (@bar_width * spacing_factor / 2.0)
+          draw_label(label_center, point_index)
+        end
+
+      end
+    
+      @d.draw(@base_image)    
+    end
+
+end
diff --git a/lib/gruff-0.2.8/test/gruff_test_case.rb b/lib/gruff-0.2.8/test/gruff_test_case.rb
new file mode 100644 (file)
index 0000000..1327df2
--- /dev/null
@@ -0,0 +1,118 @@
+$:.unshift(File.dirname(__FILE__) + "/../lib/")
+
+require 'test/unit'
+require 'gruff'
+# require 'test_timer'
+
+class GruffTestCase < Test::Unit::TestCase
+
+  def setup
+    @datasets = [
+      [:Jimmy, [25, 36, 86, 39, 25, 31, 79, 88]],
+      [:Charles, [80, 54, 67, 54, 68, 70, 90, 95]],
+      [:Julie, [22, 29, 35, 38, 36, 40, 46, 57]],
+      [:Jane, [95, 95, 95, 90, 85, 80, 88, 100]],
+      [:Philip, [90, 34, 23, 12, 78, 89, 98, 88]],
+      ["Arthur", [5, 10, 13, 11, 6, 16, 22, 32]],
+      ]
+
+    @labels = {
+        0 => '5/6', 
+        1 => '5/15', 
+        2 => '5/24', 
+        3 => '5/30', 
+        4 => '6/4', 
+        5 => '6/12', 
+        6 => '6/21', 
+        7 => '6/28', 
+      }      
+  end
+
+  def setup_single_dataset
+    @datasets = [
+      [:Jimmy, [25, 36, 86]]
+      ]
+
+    @labels = {
+        0 => 'You', 
+        1 => 'Average', 
+        2 => 'Lifetime' 
+      }      
+  end
+
+  def setup_wide_dataset
+    @datasets = [
+      ["Auto", 25],
+      ["Food", 5],
+      ["Entertainment", 15]
+      ]
+
+    @labels = { 0 => 'This Month' }
+  end
+
+  def test_dummy
+    assert true
+  end
+
+protected
+
+  # Generate graphs at several sizes.
+  #
+  # Also writes the graph to disk.
+  #
+  #   graph_sized 'bar_basic' do |g|
+  #     g.data('students', [1, 2, 3, 4])
+  #   end
+  #
+  def graph_sized(filename, sizes=['', 400])
+    class_name = self.class.name.gsub(/^TestGruff/, '')
+    Array(sizes).each do |size|
+      g = instance_eval("Gruff::#{class_name}.new #{size}")
+      g.title = "#{class_name} Graph"
+      yield g      
+      write_test_file g, "#{filename}_#{size}.png"
+    end
+  end
+
+  def write_test_file(graph, filename)
+    graph.write(File.dirname(__FILE__) + "/output/#{filename}")
+  end
+
+  ##
+  # Example:
+  #
+  #   setup_basic_graph Gruff::Pie, 400
+  #
+  def setup_basic_graph(*args)
+    klass, size = Gruff::Bar, 400
+    # Allow args to be klass, size or just klass or just size.
+    #
+    # TODO Refactor
+    case args.length
+    when 1
+      case args[0]
+      when Fixnum
+        size = args[0]
+        klass = eval("Gruff::#{self.class.name.gsub(/^TestGruff/, '')}") 
+      when String
+        size = args[0]
+        klass = eval("Gruff::#{self.class.name.gsub(/^TestGruff/, '')}") 
+      else
+        klass = args[0]
+      end
+    when 2
+      klass, size = args[0], args[1]
+    end
+    
+    g = klass.new(size)
+    g.title = "My Bar Graph"
+    g.labels = @labels
+
+
+    @datasets.each do |data|
+      g.data(data[0], data[1])
+    end
+    g
+  end
+
+end
diff --git a/lib/gruff-0.2.8/test/test_accumulator_bar.rb b/lib/gruff-0.2.8/test/test_accumulator_bar.rb
new file mode 100644 (file)
index 0000000..1872c7a
--- /dev/null
@@ -0,0 +1,50 @@
+require File.dirname(__FILE__) + "/gruff_test_case"
+
+class TestGruffAccumulatorBar < GruffTestCase
+
+  # TODO Delete old output files once when starting tests
+
+  def setup
+    @datasets = [
+      (1..20).to_a.map { rand(10) }
+    ]
+  end
+
+  def test_accumulator
+    g = Gruff::AccumulatorBar.new 500
+    g.title = "Your Savings"
+    g.hide_legend = true
+
+    # g.font = File.expand_path(File.dirname(__FILE__) + "/../assets/fonts/ATMA____.TTF")
+
+    g.marker_font_size = 18
+    
+    g.theme = {
+      :colors => ['#aedaa9', '#12a702'], # 3077a9 blue, aedaa9 light green
+      :marker_color => '#dddddd',
+      :font_color => 'black',      
+      :background_colors => "white"
+      # :background_image => File.expand_path(File.dirname(__FILE__) + "/../assets/backgrounds/43things.png")
+    }
+
+    # Attempt at negative numbers
+    # g.data 'Savings', (1..20).to_a.map { rand(10) * (rand(2) > 0 ? 1 : -1) }
+    g.data 'Savings', (1..12).to_a.map { rand(100) }
+    g.labels = (0..11).to_a.inject({}) {|memo, index| {index => '12-26'}.merge(memo)}
+
+    g.maximum_value = 1000
+    g.minimum_value = 0
+
+    g.write("test/output/accum_bar.png")
+  end
+
+  def test_too_many_args
+    assert_raise(Gruff::IncorrectNumberOfDatasetsException) {
+      g = Gruff::AccumulatorBar.new
+      g.data 'First', [1,1,1]
+      g.data 'Too Many', [1,1,1]
+      g.write("test/output/_SHOULD_NOT_ACTUALLY_BE_WRITTEN.png")
+    }
+  end
+
+end
diff --git a/lib/gruff-0.2.8/test/test_area.rb b/lib/gruff-0.2.8/test/test_area.rb
new file mode 100644 (file)
index 0000000..357711c
--- /dev/null
@@ -0,0 +1,134 @@
+#!/usr/bin/ruby
+
+require File.dirname(__FILE__) + "/gruff_test_case"
+
+class TestGruffArea < GruffTestCase
+
+  def setup
+    @datasets = [
+      [:Jimmy, [25, 36, 86, 39, 25, 31, 79, 88]],
+      [:Charles, [80, 54, 67, 54, 68, 70, 90, 95]],
+      [:Julie, [22, 29, 35, 38, 36, 40, 46, 57]],
+      [:Jane, [95, 95, 95, 90, 85, 80, 88, 100]],
+      [:Philip, [90, 34, 23, 12, 78, 89, 98, 88]],
+      ["Arthur", [5, 10, 13, 11, 6, 16, 22, 32]],
+      ]
+    @sample_labels = {
+        0 => '5/6', 
+        1 => '5/15', 
+        2 => '5/24', 
+        3 => '5/30', 
+        4 => '6/4', 
+        5 => '6/12', 
+        6 => '6/21', 
+        7 => '6/28', 
+      }      
+
+  end
+  
+  def test_area_graph
+    g = Gruff::Area.new
+    g.title = "Visual Multi-Area Graph Test"
+    g.labels = {
+      0 => '5/6', 
+      2 => '5/15', 
+      4 => '5/24', 
+      6 => '5/30', 
+    }
+    @datasets.each do |data|
+      g.data(data[0], data[1])
+    end
+
+    # Default theme
+    g.write("test/output/area_keynote.png")
+  end
+  
+  def test_resize
+    g = Gruff::Area.new(400)
+    g.title = "Small Size Multi-Area Graph Test"
+    g.labels = {
+      0 => '5/6', 
+      2 => '5/15', 
+      4 => '5/24', 
+      6 => '5/30', 
+    }
+    @datasets.each do |data|
+      g.data(data[0], data[1])
+    end
+
+    # Default theme
+    g.write("test/output/area_keynote_small.png")
+  end
+  
+  def test_many_datapoints
+    g = Gruff::Area.new
+    g.title = "Many Multi-Area Graph Test"
+    g.labels = {
+      0 => 'June', 
+      10 => 'July', 
+      30 => 'August', 
+      50 => 'September', 
+    }
+    g.data('many points', (0..50).collect {|i| rand(100) })
+
+    # Default theme
+    g.write("test/output/area_many.png")
+  end
+
+  def test_many_areas_graph_small
+    g = Gruff::Area.new(400)
+    g.title = "Many Values Area Test 400px"
+    g.labels = {
+      0 => '5/6', 
+      10 => '5/15', 
+      20 => '5/24', 
+      30 => '5/30', 
+      40 => '6/4', 
+      50 => '6/16'
+    }
+    %w{jimmy jane philip arthur julie bert}.each do |student_name|
+      g.data(student_name, (0..50).collect { |i| rand 100 })
+    end
+
+    # Default theme
+    g.write("test/output/area_many_areas_small.png")
+  end
+
+  def test_area_graph_tiny
+    g = Gruff::Area.new(300)
+    g.title = "Area Test 300px"
+    g.labels = {
+      0 => '5/6', 
+      10 => '5/15', 
+      20 => '5/24', 
+      30 => '5/30', 
+      40 => '6/4', 
+      50 => '6/16'
+    }
+    %w{jimmy jane philip arthur julie bert}.each do |student_name|
+      g.data(student_name, (0..50).collect { |i| rand 100 })
+    end
+
+    # Default theme
+    g.write("test/output/area_tiny.png")
+  end
+
+  def test_wide
+    g = setup_basic_graph('800x400')
+    g.title = "Area Wide"
+    g.write("test/output/area_wide.png")
+  end
+
+protected
+  
+  def setup_basic_graph(size=800)
+    g = Gruff::Area.new(size)
+    g.title = "My Graph Title"
+    g.labels = @sample_labels
+    @datasets.each do |data|
+      g.data(data[0], data[1])
+    end
+    return g
+  end
+  
+end
diff --git a/lib/gruff-0.2.8/test/test_bar.rb b/lib/gruff-0.2.8/test/test_bar.rb
new file mode 100644 (file)
index 0000000..3971889
--- /dev/null
@@ -0,0 +1,284 @@
+#!/usr/bin/ruby
+
+require File.dirname(__FILE__) + "/gruff_test_case"
+
+class TestGruffBar < GruffTestCase
+
+  # TODO Delete old output files once when starting tests
+
+  def setup
+    @datasets = [
+      [:Jimmy, [25, 36, 86, 39]],
+      [:Charles, [80, 54, 67, 54]],
+      [:Julie, [22, 29, 35, 38]],
+      #[:Jane, [95, 95, 95, 90, 85, 80, 88, 100]],
+      #[:Philip, [90, 34, 23, 12, 78, 89, 98, 88]],
+      #["Arthur", [5, 10, 13, 11, 6, 16, 22, 32]],
+      ]
+  end
+
+  def test_bar_graph
+    g = setup_basic_graph
+    g.title = "Bar Graph Test"
+    g.write("test/output/bar_keynote.png")
+        
+    g = setup_basic_graph
+    g.title = "Visual Multi-Line Bar Graph Test"
+    g.theme_rails_keynote
+    g.write("test/output/bar_rails_keynote.png")
+    
+    g = setup_basic_graph
+    g.title = "Visual Multi-Line Bar Graph Test"
+    g.theme_odeo
+    g.write("test/output/bar_odeo.png")
+  end
+
+  def test_bar_graph_set_colors
+    g = Gruff::Bar.new
+    g.title = "Bar Graph With Manual Colors"
+    g.labels = {
+      0 => '5/6', 
+      1 => '5/15', 
+      2 => '5/24', 
+      3 => '5/30', 
+    }
+    g.data(:Art, [0, 5, 8, 15], '#990000')
+    g.data(:Philosophy, [10, 3, 2, 8], '#009900')
+    g.data(:Science, [2, 15, 8, 11], '#990099')
+
+    g.minimum_value = 0
+        
+    g.write("test/output/bar_manual_colors.png")
+  end
+
+  def test_bar_graph_small
+    g = Gruff::Bar.new(400)
+    g.title = "Visual Multi-Line Bar Graph Test"
+    g.labels = {
+      0 => '5/6', 
+      1 => '5/15', 
+      2 => '5/24', 
+      3 => '5/30', 
+    }
+    @datasets.each do |data|
+      g.data(data[0], data[1])
+    end
+
+    g.write("test/output/bar_keynote_small.png")
+  end
+
+  # Somewhat worthless test. Should an error be thrown?
+  # def test_nil_font
+  #   g = setup_basic_graph 400
+  #   g.title = "Nil Font"
+  #   g.font = nil
+  #   g.write "test/output/bar_nil_font.png"
+  # end
+
+
+  def test_no_line_markers
+    g = setup_basic_graph(400)
+    g.title = "No Line Markers"
+    g.hide_line_markers = true
+    g.write("test/output/bar_no_line_markers.png")    
+  end
+
+  def test_no_legend
+    g = setup_basic_graph(400)
+    g.title = "No Legend"
+    g.hide_legend = true
+    g.write("test/output/bar_no_legend.png")    
+  end
+
+  def test_no_title
+    g = setup_basic_graph(400)
+    g.title = "No Title"
+    g.hide_title = true
+    g.write("test/output/bar_no_title.png")    
+  end
+  
+  def test_no_title_or_legend
+    g = setup_basic_graph(400)
+    g.title = "No Title or Legend"
+    g.hide_legend = true
+    g.hide_title = true
+    g.write("test/output/bar_no_title_or_legend.png")    
+  end
+
+  def test_set_marker_count
+    g = setup_basic_graph(400)
+    g.title = "Set marker"
+    g.marker_count = 10
+    g.write("test/output/bar_set_marker.png")
+  end
+
+  def test_set_legend_box_size
+    g = setup_basic_graph(400)
+    g.title = "Set Legend Box Size"
+    g.legend_box_size = 10.0
+    g.write("test/output/bar_set_legend_box_size_sm.png")
+    
+    g = setup_basic_graph(400)
+    g.title = "Set Legend Box Size"
+    g.legend_box_size = 50.0
+    g.write("test/output/bar_set_legend_box_size_lg.png")
+  end
+
+  def test_x_y_labels
+    g = setup_basic_graph(400)
+    g.title = "X Y Labels"
+    g.x_axis_label = 'Score (%)'
+    g.y_axis_label = "Students"
+    g.write("test/output/bar_x_y_labels.png")    
+  end
+
+  def test_wide_graph
+    g = setup_basic_graph('800x400')
+    g.title = "Wide Graph"
+    g.write("test/output/bar_wide_graph.png")    
+
+    g = setup_basic_graph('400x200')
+    g.title = "Wide Graph Small"
+    g.write("test/output/bar_wide_graph_small.png")
+  end
+
+
+  def test_tall_graph
+    g = setup_basic_graph('400x600')
+    g.title = "Tall Graph"
+    g.write("test/output/bar_tall_graph.png")
+
+    g = setup_basic_graph('200x400')
+    g.title = "Tall Graph Small"
+    g.write("test/output/bar_tall_graph_small.png")
+  end
+
+
+  def test_one_value
+    g = Gruff::Bar.new
+    g.title = "One Value Graph Test"
+    g.labels = {
+      0 => '1', 
+      1 => '2'
+    }
+    g.data('one', [1,1])
+
+    g.write("test/output/bar_one_value.png")
+  end
+
+
+  def test_negative
+    g = Gruff::Bar.new
+    g.title = "Pos/Neg Bar Graph Test"
+    g.labels = {
+      0 => '5/6', 
+      1 => '5/15', 
+      2 => '5/24', 
+      3 => '5/30', 
+    }
+    g.data(:apples, [-1, 0, 4, -4])
+    g.data(:peaches, [10, 8, 6, 3])
+
+    g.write("test/output/bar_pos_neg.png")
+  end
+
+
+  def test_nearly_zero
+    g = Gruff::Bar.new
+    g.title = "Nearly Zero Graph"
+    g.labels = {
+      0 => '5/6', 
+      1 => '5/15', 
+      2 => '5/24', 
+      3 => '5/30', 
+    }
+    g.data(:apples, [1, 2, 3, 4])
+    g.data(:peaches, [4, 3, 2, 1])
+    g.minimum_value = 0
+    g.maximum_value = 10
+    g.write("test/output/bar_nearly_zero_max_10.png")
+  end
+
+  def test_y_axis_increment
+    generate_with_y_axis_increment 2.0
+    generate_with_y_axis_increment 1
+    generate_with_y_axis_increment 5
+    generate_with_y_axis_increment 20
+  end
+
+  def generate_with_y_axis_increment(increment)
+    g = Gruff::Bar.new
+    g.title = "Y Axis Set to #{increment}"
+    g.labels = {
+      0 => '5/6', 
+      1 => '5/15', 
+      2 => '5/24', 
+      3 => '5/30', 
+    }
+    g.y_axis_increment = increment
+    g.data(:apples, [1, 0.2, 0.5, 0.7])
+    g.data(:peaches, [2.5, 2.3, 2, 6.1])
+    g.write("test/output/bar_y_increment_#{increment}.png")
+  end
+
+
+  def test_custom_theme
+    g = Gruff::Bar.new
+    g.title = "Custom Theme"
+    g.font = File.expand_path('CREABBRG.TTF', ENV['MAGICK_FONT_PATH'])
+    g.title_font_size = 60
+    g.legend_font_size = 32
+    g.marker_font_size = 32
+    g.theme = {
+      :colors => %w(#efd250 #666699 #e5573f #9595e2),
+      :marker_color => 'white',
+      :font_color => 'blue',
+      :background_image => "assets/pc306715.jpg"
+    }
+    g.labels = {
+      0 => '5/6', 
+      1 => '5/15', 
+      2 => '5/24', 
+      3 => '5/30', 
+    }
+    g.data(:vancouver, [1, 2, 3, 4])
+    g.data(:seattle, [2, 4, 6, 8])
+    g.data(:portland, [3, 1, 7, 3])
+    g.data(:victoria, [4, 3, 5, 7])
+    g.minimum_value = 0
+    g.write("test/output/bar_themed.png")
+  end
+
+  def test_july_enhancements
+    g = Gruff::Bar.new(600)
+    g.hide_legend = true
+    g.title = "Full speed ahead"
+    g.labels = (0..10).inject({}) { |memo, i| memo.merge({ i => (i*10).to_s}) }
+    g.data(:apples, (0..9).map { rand(20)/10.0 })
+    g.y_axis_increment = 1.0
+    g.x_axis_label = 'Score (%)'
+    g.y_axis_label = 'Students'
+    write_test_file g, 'enhancements.png'
+  end
+
+
+protected
+
+  def setup_basic_graph(size=800)
+    g = Gruff::Bar.new(size)
+    g.title = "My Bar Graph"
+    g.labels = {
+      0 => '5/6', 
+      1 => '5/15', 
+      2 => '5/24', 
+      3 => '5/30', 
+    }
+    @datasets.each do |data|
+      g.data(data[0], data[1])
+    end
+    g
+  end
+
+  
+end
+
diff --git a/lib/gruff-0.2.8/test/test_base.rb b/lib/gruff-0.2.8/test/test_base.rb
new file mode 100644 (file)
index 0000000..d68be92
--- /dev/null
@@ -0,0 +1,8 @@
+#!/usr/bin/ruby
+
+require File.dirname(__FILE__) + "/gruff_test_case"
+
+class TestGruffBase < GruffTestCase
+
+
+end
\ No newline at end of file
diff --git a/lib/gruff-0.2.8/test/test_legend.rb b/lib/gruff-0.2.8/test/test_legend.rb
new file mode 100644 (file)
index 0000000..9e6d6d2
--- /dev/null
@@ -0,0 +1,71 @@
+require File.dirname(__FILE__) + "/gruff_test_case"
+
+class TestGruffLegend < GruffTestCase
+
+  def setup
+    @datasets = [
+      [:Jimmy, [25, 36, 86, 39, 25, 31, 79, 88]],
+      [:Charles, [80, 54, 67, 54, 68, 70, 90, 95]],
+      [:Julie, [22, 29, 35, 38, 36, 40, 46, 57]],
+      [:Jane, [95, 95, 95, 90, 85, 80, 88, 100]],
+      [:Philip, [90, 34, 23, 12, 78, 89, 98, 88]],
+      ["Arthur", [5, 10, 13, 11, 6, 16, 22, 32]],
+      ["Vincent", [5, 10, 13, 11, 6, 16, 22, 32]],
+      ["Jake", [5, 10, 13, 11, 6, 16, 22, 32]],
+      ["Stephen", [5, 10, 13, 11, 6, 16, 22, 32]],
+      ]
+
+    @sample_labels = {
+        0 => '5/6', 
+        1 => '5/15', 
+        2 => '5/24', 
+        3 => '5/30', 
+        4 => '6/4', 
+        5 => '6/12', 
+        6 => '6/21', 
+        7 => '6/28', 
+      }      
+  end
+
+  ## TODO Fix implementation
+
+  # def test_bar_legend_wrap
+  #   [800, 400].each do |width|
+  #     [nil, 4, 16, 30].each do |font_size|
+  #       g = Gruff::Bar.new(width)
+  #       g.title = "Wrapped Legend Bar Test #{font_size}pts #{width}px"
+  #       g.labels = @sample_labels
+  #       0xEFD250.step(0xFF0000, 60) do |num|
+  #         g.colors << "#%x" % num
+  #       end
+  # 
+  #       @datasets.each do |data|
+  #         g.data(data[0], data[1])
+  #       end
+  # 
+  #       g.legend_font_size = font_size unless font_size.nil?
+  #       g.write("test/output/bar_wrapped_legend_#{font_size}_#{width}.png")
+  #     end
+  #   end
+  # end
+  # 
+  # def test_pie_legend_wrap    
+  #   [800, 400].each do |width|
+  #     [nil, 4, 16, 30].each do |font_size|
+  #       g = Gruff::Pie.new(width)
+  #       g.title = "Wrapped Legend Pie Test #{font_size}pts #{width}px"
+  #       g.labels = @sample_labels
+  #       0xEFD250.step(0xFF0000, 60) do |num|
+  #         g.colors << "#%x" % num
+  #       end
+  # 
+  #       @datasets.each do |data|
+  #         g.data(data[0], data[1])
+  #       end
+  # 
+  #       g.legend_font_size = font_size unless font_size.nil?
+  #       g.write("test/output/pie_wrapped_legend_#{font_size}_#{width}.png")
+  #     end
+  #   end
+  # end
+end
diff --git a/lib/gruff-0.2.8/test/test_line.rb b/lib/gruff-0.2.8/test/test_line.rb
new file mode 100644 (file)
index 0000000..99a3683
--- /dev/null
@@ -0,0 +1,493 @@
+#!/usr/bin/ruby
+
+require File.dirname(__FILE__) + "/gruff_test_case"
+
+class TestGruffLine < GruffTestCase
+
+  # TODO Delete old output files once when starting tests
+  
+  def test_line_graph_with_themes
+    line_graph_with_themes()
+    line_graph_with_themes(400)
+  end
+  
+  def test_one_value
+    g = Gruff::Line.new
+    g.title = "One Value"
+    g.labels = {
+      0 => '1', 
+      1 => '2'
+    }
+    g.data('one', 1)
+
+    g.write("test/output/line_one_value.png")
+  end
+
+  def test_one_value_array
+    g = Gruff::Line.new
+    g.title = "One Value in an Array"
+    g.labels = {
+      0 => '1', 
+      1 => '2'
+    }
+    g.data('one', [1])
+
+    g.write("test/output/line_one_value_array.png")
+  end
+
+
+  def test_should_not_hang_with_0_0_100
+    g = Gruff::Line.new(320)
+    g.title = "Hang Value Graph Test"
+    g.data('test', [0,0,100])
+
+    g.write("test/output/line_hang_value.png")
+  end
+
+  # TODO
+  # def test_fix_crash
+  #   g = Gruff::Line.new(370)
+  #   g.title = "Crash Test"
+  #   g.data "ichi", [5]
+  #   g.data "ni", [0]
+  #   g.data "san", [0]
+  #   g.data "shi", [0]
+  #   g.write("test/output/line_crash_fix_test.png")
+  # end
+
+  
+  def test_line_small_values    
+    @datasets = [
+      [:small, [0.1, 0.14356, 0.0, 0.5674839, 0.456]],
+      [:small2, [0.2, 0.3, 0.1, 0.05, 0.9]]
+      ]
+
+    g = Gruff::Line.new
+    g.title = "Small Values Line Graph Test"
+    @datasets.each do |data|
+      g.data(data[0], data[1])
+    end
+    g.write("test/output/line_small.png")
+
+    g = Gruff::Line.new(400)
+    g.title = "Small Values Line Graph Test 400px"
+    @datasets.each do |data|
+      g.data(data[0], data[1])
+    end
+    g.write("test/output/line_small_small.png")
+  end
+
+  def test_line_starts_with_zero
+    @datasets = [
+      [:first0, [0, 5, 10, 8, 18]],
+      [:normal, [1, 2, 3, 4, 5]]
+      ]
+
+    g = Gruff::Line.new
+    g.title = "Small Values Line Graph Test"
+    @datasets.each do |data|
+      g.data(data[0], data[1])
+    end
+    g.write("test/output/line_small_zero.png")
+
+    g = Gruff::Line.new(400)
+    g.title = "Small Values Line Graph Test 400px"
+    @datasets.each do |data|
+      g.data(data[0], data[1])
+    end
+    g.write("test/output/line_small_small_zero.png")
+  end
+
+    
+  def test_line_large_values    
+    @datasets = [
+      [:large, [100_005, 35_000, 28_000, 27_000]],
+      [:large2, [35_000, 28_000, 27_000, 100_005]],
+      [:large3, [28_000, 27_000, 100_005, 35_000]],
+      [:large4, [1_238, 39_092, 27_938, 48_876]]
+      ]
+
+    g = Gruff::Line.new
+    g.title = "Very Large Values Line Graph Test"
+    g.baseline_value = 50_000
+    g.baseline_color = 'green'
+    @datasets.each do |data|
+      g.data(data[0], data[1])
+    end
+
+    g.write("test/output/line_large.png")
+  end
+    
+  def test_long_title
+    
+  end
+  
+  def test_add_colors
+    
+  end
+
+  def test_request_too_many_colors
+    
+  end
+
+  def test_add_data
+    
+  end
+
+  def test_many_datapoints
+    g = Gruff::Line.new
+    g.title = "Many Multi-Line Graph Test"
+    g.labels = {
+      0 => 'June', 
+      10 => 'July', 
+      30 => 'August', 
+      50 => 'September', 
+    }
+    g.data('many points', (0..50).collect {|i| rand(100) })
+    g.x_axis_label = "Months"
+
+    # Default theme
+    g.write("test/output/line_many.png")
+  end
+
+
+  def test_similar_high_end_values
+    @dataset = %w(29.43 29.459 29.498 29.53 29.548 29.589 29.619 29.66 29.689 29.849 29.878 29.74 29.769 29.79 29.808 29.828).collect {|i| i.to_f}
+
+    g = Gruff::Line.new
+    g.title = "Similar High End Values Test"
+    g.data('similar points', @dataset )
+    g.write("test/output/line_similar_high_end_values.png")
+    
+    g = Gruff::Line.new
+    g.title = "Similar High End Values With Floor"
+    g.data('similar points', @dataset )
+    g.minimum_value = 0
+    g.y_axis_label = "Barometric Pressure"
+    g.write("test/output/line_similar_high_end_values_with_floor.png")
+  end
+
+  def test_many_lines_graph_small
+    g = Gruff::Line.new(400)
+    g.title = "Many Values Line Test 400px"
+    g.labels = {
+      0 => '5/6', 
+      10 => '5/15', 
+      20 => '5/24', 
+      30 => '5/30', 
+      40 => '6/4', 
+      50 => '6/16'
+    }
+    %w{jimmy jane philip arthur julie bert}.each do |student_name|
+      g.data(student_name, (0..50).collect { |i| rand 100 })
+    end
+
+    # Default theme
+    g.write("test/output/line_many_lines_small.png")
+  end
+
+  def test_graph_tiny
+    g = Gruff::Line.new(300)
+    g.title = "Tiny Test 300px"
+    g.labels = {
+      0 => '5/6', 
+      10 => '5/15', 
+      20 => '5/24', 
+      30 => '5/30', 
+      40 => '6/4', 
+      50 => '6/16'
+    }
+    %w{jimmy jane philip arthur julie bert}.each do |student_name|
+      g.data(student_name, (0..50).collect { |i| rand 100 })
+    end
+
+    # Default theme
+    g.write("test/output/line_tiny.png")
+  end
+
+  def test_no_data
+    g = Gruff::Line.new(400)
+    g.title = "No Data"
+    # Default theme
+    g.write("test/output/line_no_data.png")
+    
+    g = Gruff::Line.new(400)
+    g.title = "No Data Title"
+    g.no_data_message = 'There is no data'
+    g.write("test/output/line_no_data_msg.png")
+  end
+
+
+  def test_all_zeros
+    g = Gruff::Line.new(400)
+    g.title = "All Zeros"
+
+    g.data(:gus, [0,0,0,0])
+
+    # Default theme
+    g.write("test/output/line_no_data_other.png")    
+  end
+
+
+  def test_some_nil_points
+    g = Gruff::Line.new
+    g.title = "Some Nil Points"
+
+    @datasets = [
+      [:data1, [1, 2, 3, nil, 3, 5, 6]],
+      [:data2, [5, nil, nil, nil, nil, nil, 5]],
+      [:data3, [4, nil, 2, 1, 0]],
+      [:data4, [nil, nil, 3, 1, 2]]
+      ]
+
+    @datasets.each do |data|
+      g.data(*data)
+    end
+
+    # Default theme
+    g.write("test/output/line_some_nil_points.png")    
+  end
+
+  def test_no_title
+    g = Gruff::Line.new(400)
+    g.labels = @labels
+    @datasets.each do |data|
+      g.data(data[0], data[1])
+    end
+
+    g.write("test/output/line_no_title.png")
+  end
+
+  def test_no_line_markers
+    g = setup_basic_graph(400)
+    g.title = "No Line Markers"
+    g.hide_line_markers = true
+    g.write("test/output/line_no_line_markers.png")    
+  end
+
+  def test_no_legend
+    g = setup_basic_graph(400)
+    g.title = "No Legend"
+    g.hide_legend = true
+    g.write("test/output/line_no_legend.png")
+  end
+
+  def test_nothing_but_the_graph
+    g = setup_basic_graph(400)
+    g.title = "THIS TITLE SHOULD NOT DISPLAY!!!"
+    g.hide_line_markers = true
+    g.hide_legend = true
+    g.hide_title = true
+    g.write("test/output/line_nothing_but_the_graph.png")    
+  end
+
+  def test_baseline_larger_than_data
+    g = setup_basic_graph(400)
+    g.title = "Baseline Larger Than Data"
+    g.baseline_value = 150
+    g.write("test/output/line_large_baseline.png")    
+  end
+
+
+  def test_hide_dots
+    g = setup_basic_graph(400)
+    g.title = "Hide Dots"
+    g.hide_dots = true
+    g.write("test/output/line_hide_dots.png")    
+  end
+
+  def test_hide_lines
+    g = setup_basic_graph(400)
+    g.title = "Hide Lines"
+    g.hide_lines = true
+    g.write("test/output/line_hide_lines.png")    
+  end
+
+  def test_wide_graph
+    g = setup_basic_graph('800x400')
+    g.title = "Wide Graph"
+    g.write("test/output/line_wide_graph.png")    
+
+    g = setup_basic_graph('400x200')
+    g.title = "Wide Graph Small"
+    g.write("test/output/line_wide_graph_small.png")
+  end
+
+  def test_negative
+    g = setup_pos_neg(800)
+    g.write("test/output/line_pos_neg.png")
+    
+    g = setup_pos_neg(400)
+    g.title = 'Pos/Neg Line Test Small'
+    g.write("test/output/line_pos_neg_400.png")
+  end
+
+  def test_all_negative
+    g = setup_all_neg(800)
+    g.write("test/output/line_all_neg.png")
+    
+    g = setup_all_neg(400)
+    g.title = 'All Neg Line Test Small'
+    g.write("test/output/line_all_neg_400.png")
+  end
+
+  def test_many_numbers    
+    g = Gruff::Line.new('400x170')
+    g.title = "Line Test, Many Numbers"
+
+    data = [
+      { :date => '01',
+        :wpm => 0,
+        :errors => 0,
+        :accuracy => 0 },
+      { :date => '02',
+        :wpm => 10,
+        :errors => 2,
+        :accuracy => 80 },
+      { :date => '03',
+        :wpm => 15,
+        :errors => 0,
+        :accuracy => 100 },
+      { :date => '04',
+        :wpm => 16,
+        :errors => 2,
+        :accuracy => 87 },
+      { :date => '05',
+          :wpm => nil,
+          :errors => nil,
+          :accuracy => nil },
+      { :date => '06',
+        :wpm => 18,
+        :errors => 1,
+        :accuracy => 94 },
+      { :date => '07'},
+      { :date => '08' },
+      { :date => '09',
+         :wpm => 21,
+         :errors => 1,
+         :accuracy => 95 },
+      { :date => '10'},
+      { :date => '11'},
+      { :date => '12'},
+      { :date => '13'},
+      { :date => '14'},
+      { :date => '15'},
+      { :date => '16'},
+      { :date => '17'},
+      { :date => '18'},
+      { :date => '19',
+         :wpm => 28,
+         :errors => 5,
+         :accuracy => 82 },
+      { :date => '20'},
+      { :date => '21'},
+      { :date => '22'},
+      { :date => '23'},
+      { :date => '24'},
+      { :date => '25'},
+      { :date => '26'},     
+      { :date => '27',
+         :wpm => 37,
+         :errors => 3,
+         :accuracy => 92 },
+    ]
+
+    [:wpm, :errors, :accuracy].each do |field|
+      g.data(field.to_s, data.collect {|d| d[field] })
+    end
+
+    labels = Hash.new
+    data.each_with_index do |d, i|
+      labels[i] = d[:date]
+    end
+    g.labels = labels
+
+    g.write('test/output/line_many_numbers.png')
+  end
+
+  def test_no_hide_line_no_labels
+    g = Gruff::Line.new
+    g.title = "No Hide Line No Labels"
+    @datasets.each do |data|
+      g.data(data[0], data[1])
+    end
+    g.hide_line_markers = false
+    g.write('test/output/line_no_hide.png')
+  end
+
+protected
+
+  # TODO Reset data after each theme
+  def line_graph_with_themes(size=nil)
+    g = Gruff::Line.new(size)
+    g.title = "Multi-Line Graph Test #{size}"
+    g.labels = @labels
+    g.baseline_value = 90
+    @datasets.each do |data|
+      g.data(data[0], data[1])
+    end
+    # Default theme
+    g.write("test/output/line_theme_keynote_#{size}.png")
+  
+    g = Gruff::Line.new(size)
+    g.title = "Multi-Line Graph Test #{size}"
+    g.labels = @labels
+    g.baseline_value = 90
+    g.theme_37signals
+    @datasets.each do |data|
+      g.data(data[0], data[1])
+    end
+    g.write("test/output/line_theme_37signals_#{size}.png")
+  
+
+    g = Gruff::Line.new(size)
+    g.title = "Multi-Line Graph Test #{size}"
+    g.labels = @labels
+    g.baseline_value = 90
+    g.theme_rails_keynote
+    @datasets.each do |data|
+      g.data(data[0], data[1])
+    end
+    g.write("test/output/line_theme_rails_keynote_#{size}.png")
+  
+    g = Gruff::Line.new(size)
+    g.title = "Multi-Line Graph Test #{size}"
+    g.labels = @labels
+    g.baseline_value = 90
+    g.theme_odeo
+    @datasets.each do |data|
+      g.data(data[0], data[1])
+    end
+    g.write("test/output/line_theme_odeo_#{size}.png")
+  end
+
+  def setup_pos_neg(size=800)
+    g = Gruff::Line.new(size)
+    g.title = "Pos/Neg Line Graph Test"
+    g.labels = {
+      0 => '5/6', 
+      1 => '5/15', 
+      2 => '5/24', 
+      3 => '5/30', 
+    }
+    g.data(:apples, [-1, 0, 4, -4])
+    g.data(:peaches, [10, 8, 6, 3])
+    return g
+  end
+
+
+  def setup_all_neg(size=800)
+    g = Gruff::Line.new(size)
+    g.title = "All Neg Line Graph Test"
+    g.labels = {
+      0 => '5/6', 
+      1 => '5/15', 
+      2 => '5/24', 
+      3 => '5/30', 
+    }
+    g.data(:apples, [-1, -5, -20, -4])
+    g.data(:peaches, [-10, -8, -6, -3])
+    g
+  end
+  
+end
diff --git a/lib/gruff-0.2.8/test/test_mini_bar.rb b/lib/gruff-0.2.8/test/test_mini_bar.rb
new file mode 100644 (file)
index 0000000..1850725
--- /dev/null
@@ -0,0 +1,32 @@
+
+require File.dirname(__FILE__) + "/gruff_test_case"
+
+class TestMiniBar < GruffTestCase
+  
+  def test_simple_bar
+    setup_single_dataset
+    g = setup_basic_graph(Gruff::Mini::Bar, 200)
+    write_test_file g, 'mini_bar.png'
+  end
+
+  # def test_simple_bar_wide_dataset
+  #   setup_wide_dataset
+  #   g = setup_basic_graph(Gruff::Mini::Bar, 200)
+  #   write_test_file g, 'mini_bar_wide_data.png'
+  # end
+  # 
+  # def test_code_sample
+  #   g = Gruff::Mini::Bar.new(200)
+  #   g.data "Jim", [200, 500, 400]
+  #   g.labels = { 0 => 'This Month', 1 => 'Average', 2 => 'Overall'}
+  #   g.write "mini_bar_one_color.png"
+  #   
+  #   g = Gruff::Mini::Bar.new(200)
+  #   g.data "Car", 200
+  #   g.data "Food", 500
+  #   g.data "Art", 1000
+  #   g.data "Music", 16
+  #   g.write "mini_bar_many_colors.png"
+  # end
+
+end
diff --git a/lib/gruff-0.2.8/test/test_mini_pie.rb b/lib/gruff-0.2.8/test/test_mini_pie.rb
new file mode 100644 (file)
index 0000000..87ff58a
--- /dev/null
@@ -0,0 +1,20 @@
+
+require File.dirname(__FILE__) + "/gruff_test_case"
+
+class TestMiniPie < GruffTestCase
+  
+  def test_simple_pie
+    g = setup_basic_graph(Gruff::Mini::Pie, 200)
+    write_test_file g, 'mini_pie.png'
+  end
+
+  # def test_code_sample    
+  #   g = Gruff::Mini::Pie.new(200)
+  #   g.data "Car", 200
+  #   g.data "Food", 500
+  #   g.data "Art", 1000
+  #   g.data "Music", 16
+  #   g.write "mini_pie.png"    
+  # end
+
+end
diff --git a/lib/gruff-0.2.8/test/test_mini_side_bar.rb b/lib/gruff-0.2.8/test/test_mini_side_bar.rb
new file mode 100644 (file)
index 0000000..4145380
--- /dev/null
@@ -0,0 +1,37 @@
+
+require File.dirname(__FILE__) + "/gruff_test_case"
+
+class TestMiniSideBar < GruffTestCase
+    
+  def test_one_color
+    # Use a single data set
+    @datasets = [
+        [:Jimmy, [25, 36, 86, 39]]
+      ]
+    @labels = {
+        0 => 'Auto', 
+        1 => 'Entertainment', 
+        2 => 'Food',
+        3 => 'Bus'
+      }
+
+    g = setup_basic_graph(Gruff::Mini::SideBar, 200)
+    write_test_file g, 'mini_side_bar.png'    
+  end
+
+  def test_multi_color
+    # @datasets = [
+    #     [:Jimmy, [25, 36, 86, 39]]
+    #   ]
+    # @labels = {
+    #     0 => 'Auto', 
+    #     1 => 'Entertainment', 
+    #     2 => 'Food',
+    #     3 => 'Bus'
+    #   }
+
+    g = setup_basic_graph(Gruff::Mini::SideBar, 200)
+    write_test_file g, 'mini_side_bar_multi_color.png'    
+  end
+
+end
diff --git a/lib/gruff-0.2.8/test/test_net.rb b/lib/gruff-0.2.8/test/test_net.rb
new file mode 100644 (file)
index 0000000..869149d
--- /dev/null
@@ -0,0 +1,230 @@
+#!/usr/bin/ruby
+
+require File.dirname(__FILE__) + "/gruff_test_case"
+
+class TestGruffNet < GruffTestCase
+
+  def setup
+    @datasets = [
+      [:Jimmy, [25, 36, 86, 39, 25, 31, 79, 88]],
+      [:Charles, [80, 54, 67, 54, 68, 70, 90, 95]],
+      [:Julie, [22, 29, 35, 38, 36, 40, 46, 57]],
+      [:Jane, [95, 95, 95, 90, 85, 80, 88, 100]],
+      [:Philip, [90, 34, 23, 12, 78, 89, 98, 88]],
+      ["Arthur", [5, 10, 13, 11, 6, 16, 22, 32]],
+      ]
+
+    @sample_labels = {
+        0 => '5/6', 
+        1 => '5/15', 
+        2 => '5/24', 
+        3 => '5/30', 
+        4 => '6/4', 
+        5 => '6/12', 
+        6 => '6/21', 
+        7 => '6/28', 
+      }      
+  end
+    
+  def test_net_small_values    
+    @datasets = [
+      [:small, [0.1, 0.14356, 0.0, 0.5674839, 0.456]],
+      [:small2, [0.2, 0.3, 0.1, 0.05, 0.9]]
+      ]
+
+    g = Gruff::Net.new
+    g.title = "Small Values Net Graph Test"
+    @datasets.each do |data|
+      g.data(data[0], data[1])
+    end
+    g.write("test/output/net_small.png")
+
+    g = Gruff::Net.new(400)
+    g.title = "Small Values Net Graph Test 400px"
+    @datasets.each do |data|
+      g.data(data[0], data[1])
+    end
+    g.write("test/output/net_small_small.png")
+  end
+
+  def test_net_starts_with_zero
+    @datasets = [
+      [:first0, [0, 5, 10, 8, 18]],
+      [:normal, [1, 2, 3, 4, 5]]
+      ]
+
+    g = Gruff::Net.new
+    g.title = "Small Values Net Graph Test"
+    @datasets.each do |data|
+      g.data(data[0], data[1])
+    end
+    g.write("test/output/net_small_zero.png")
+
+    g = Gruff::Net.new(400)
+    g.title = "Small Values Net Graph Test 400px"
+    @datasets.each do |data|
+      g.data(data[0], data[1])
+    end
+    g.write("test/output/net_small_small_zero.png")
+  end
+
+    
+  def test_net_large_values    
+    @datasets = [
+      [:large, [100_005, 35_000, 28_000, 27_000]],
+      [:large2, [35_000, 28_000, 27_000, 100_005]],
+      [:large3, [28_000, 27_000, 100_005, 35_000]],
+      [:large4, [1_238, 39_092, 27_938, 48_876]]
+      ]
+
+    g = Gruff::Net.new
+    g.title = "Very Large Values Net Graph Test"
+    @datasets.each do |data|
+      g.data(data[0], data[1])
+    end
+
+    g.write("test/output/net_large.png")
+  end
+    
+  def test_many_datapoints
+    g = Gruff::Net.new
+    g.title = "Many Multi-Net Graph Test"
+    g.labels = {
+      0 => 'June', 
+      10 => 'July', 
+      30 => 'August', 
+      50 => 'September', 
+    }
+    g.data('many points', (0..50).collect {|i| rand(100) })
+
+    # Default theme
+    g.write("test/output/net_many.png")
+  end
+
+
+  def test_similar_high_end_values
+    g = Gruff::Net.new
+    g.title = "Similar High End Values Test"
+    g.data('similar points', %w(29.43 29.459 29.498 29.53 29.548 29.589 29.619 29.66 29.689 29.849 29.878 29.74 29.769 29.79 29.808 29.828).collect {|i| i.to_f} )
+
+    # Default theme
+    g.write("test/output/net_similar_high_end_values.png")    
+  end
+
+  def test_many_nets_graph_small
+    g = Gruff::Net.new(400)
+    g.title = "Many Values Net Test 400px"
+    g.labels = {
+      0 => '5/6', 
+      10 => '5/15', 
+      20 => '5/24', 
+      30 => '5/30', 
+      40 => '6/4', 
+      50 => '6/16'
+    }
+    %w{jimmy jane philip arthur julie bert}.each do |student_name|
+      g.data(student_name, (0..50).collect { |i| rand 100 })
+    end
+
+    # Default theme
+    g.write("test/output/net_many_nets_small.png")
+  end
+
+  def test_dots_graph_tiny
+    g = Gruff::Net.new(300)
+    g.title = "Dots Test 300px"
+    g.labels = {
+      0 => '5/6', 
+      10 => '5/15', 
+      20 => '5/24', 
+      30 => '5/30', 
+      40 => '6/4', 
+      50 => '6/16'
+    }
+    %w{jimmy jane philip arthur julie bert}.each do |student_name|
+      g.data(student_name, (0..50).collect { |i| rand 100 })
+    end
+
+    # Default theme
+    g.write("test/output/net_dots_tiny.png")
+  end
+
+  def test_no_data
+    g = Gruff::Net.new(400)
+    g.title = "No Data"
+    # Default theme
+    g.write("test/output/net_no_data.png")
+    
+    g = Gruff::Net.new(400)
+    g.title = "No Data Title"
+    g.no_data_message = 'There is no data'
+    g.write("test/output/net_no_data_msg.png")
+  end
+
+
+  def test_all_zeros
+    g = Gruff::Net.new(400)
+    g.title = "All Zeros"
+
+    g.data(:gus, [0,0,0,0])
+
+    # Default theme
+    g.write("test/output/net_no_data_other.png")    
+  end
+
+  def test_no_title
+    g = Gruff::Net.new(400)
+    g.labels = @sample_labels
+    @datasets.each do |data|
+      g.data(data[0], data[1])
+    end
+
+    g.write("test/output/net_no_title.png")
+  end
+
+  def test_no_net_markers
+    g = setup_basic_graph(400)
+    g.title = "No Net Markers"
+    g.hide_line_markers = true
+    g.write("test/output/net_no_net_markers.png")    
+  end
+
+  def test_no_legend
+    g = setup_basic_graph(400)
+    g.title = "No Legend"
+    g.hide_legend = true
+    g.write("test/output/net_no_legend.png")
+  end
+
+  def test_nothing_but_the_graph
+    g = setup_basic_graph(400)
+    g.title = "THIS TITLE SHOULD NOT DISPLAY!!!"
+    g.hide_line_markers = true
+    g.hide_legend = true
+    g.hide_title = true
+    g.write("test/output/net_nothing_but_the_graph.png")    
+  end
+
+  def test_wide_graph
+    g = setup_basic_graph('800x400')
+    g.title = "Wide Graph"
+    g.write("test/output/net_wide_graph.png")    
+
+    g = setup_basic_graph('400x200')
+    g.title = "Wide Graph Small"
+    g.write("test/output/net_wide_graph_small.png")
+  end
+
+protected
+
+  def setup_basic_graph(size=800)
+    g = Gruff::Net.new(size)
+    g.title = "My Graph Title"
+    g.labels = @sample_labels
+    @datasets.each do |data|
+      g.data(data[0], data[1])
+    end
+    return g
+  end
+  
+end
diff --git a/lib/gruff-0.2.8/test/test_photo.rb b/lib/gruff-0.2.8/test/test_photo.rb
new file mode 100644 (file)
index 0000000..32415aa
--- /dev/null
@@ -0,0 +1,41 @@
+#!/usr/bin/ruby
+
+require File.dirname(__FILE__) + "/gruff_test_case"
+
+class TestGruffPhotoBar < GruffTestCase
+
+#   def setup
+#     @datasets = [
+#       [:Jimmy, [25, 36, 86, 39]],
+#       [:Charles, [80, 54, 67, 54]],
+# #      [:Charity, [0, nil, 100, 90]],
+#       ]
+#   end
+# 
+#   def test_bar_graph
+#     bar_graph_sized
+#     bar_graph_sized(400)
+#   end
+# 
+# 
+# protected
+# 
+#   def bar_graph_sized(size=800)
+#     g = Gruff::PhotoBar.new(size)
+#     g.title = "Photo Bar Graph Test #{size}px"
+#     g.labels = {
+#       0 => '5/6', 
+#       1 => '5/15', 
+#       2 => '5/24', 
+#       3 => '5/30', 
+#     }
+#     @datasets.each do |data|
+#       g.data(*data)
+#     end
+# 
+#     g.theme = 'plastik'
+# 
+#     g.write("test/output/photo_plastik_#{size}.png")    
+#   end
+
+end
diff --git a/lib/gruff-0.2.8/test/test_pie.rb b/lib/gruff-0.2.8/test/test_pie.rb
new file mode 100644 (file)
index 0000000..0ff8a85
--- /dev/null
@@ -0,0 +1,129 @@
+#!/usr/bin/ruby
+
+require File.dirname(__FILE__) + "/gruff_test_case"
+
+class TestGruffPie < GruffTestCase
+
+  def setup
+    @datasets = [
+      [:Darren, [25]],
+      [:Chris, [80]],
+      [:Egbert, [22]],
+      [:Adam, [95]],
+      [:Bill, [90]],
+      ["Frank", [5]],
+      ["Zero", [0]],
+      ]
+  end
+  
+  def test_pie_graph
+    g = Gruff::Pie.new
+    g.title = "Visual Pie Graph Test"
+    @datasets.each do |data|
+      g.data(data[0], data[1])
+    end
+
+    # Default theme
+    g.write("test/output/pie_keynote.png")
+  end
+
+  def test_pie_graph_small
+    g = Gruff::Pie.new(400)
+    g.title = "Visual Pie Graph Test Small"
+    @datasets.each do |data|
+      g.data(data[0], data[1])
+    end
+
+    # Default theme
+    g.write("test/output/pie_keynote_small.png")
+  end
+
+  def test_pie_graph_nearly_equal
+    g = Gruff::Pie.new
+    g.title = "Pie Graph Nearly Equal"
+    
+    g.data(:Blake, [41])
+    g.data(:Aaron, [42])
+#    g.data(:Grouch, [40])
+#    g.data(:Snuffleupagus, [43])
+
+    g.write("test/output/pie_nearly_equal.png")
+  end
+
+  def test_pie_graph_equal
+    g = Gruff::Pie.new
+    g.title = "Pie Graph Equal"
+        
+    g.data(:Bert, [41])
+    g.data(:Adam, [41])
+
+    g.write("test/output/pie_equal.png")
+  end
+
+  def test_pie_graph_zero
+    g = Gruff::Pie.new
+    g.title = "Pie Graph One Zero"
+        
+    g.data(:Bert, [0])
+    g.data(:Adam, [1])
+
+    g.write("test/output/pie_zero.png")
+  end
+
+
+  def test_pie_graph_one_val
+    g = Gruff::Pie.new
+    g.title = "Pie Graph One Val"
+        
+    g.data(:Bert, 53)
+    g.data(:Adam, 29)
+
+    g.write("test/output/pie_one_val.png")
+  end
+
+
+  def test_wide
+    g = setup_basic_graph('800x400')
+    g.title = "Wide Pie"
+    g.write("test/output/pie_wide.png")
+  end
+
+  def test_label_size
+    g = setup_basic_graph()
+    g.title = "Pie With Small Legend"
+    g.legend_font_size = 10
+    g.write("test/output/pie_legend.png")
+    
+    g = setup_basic_graph(400)
+    g.title = "Small Pie With Small Legend"
+    g.legend_font_size = 10
+    g.write("test/output/pie_legend_small.png")
+  end
+
+  
+  def test_tiny_simple_pie
+    @datasets = (1..5).map {|n| ['Auto', [rand(100)]]}
+
+    g = setup_basic_graph 200
+    g.hide_legend = true
+    g.hide_title = true
+    g.hide_line_numbers = true
+  
+    g.marker_font_size = 40.0
+    g.minimum_value = 0.0
+    
+    write_test_file g, "pie_simple.png"
+  end
+  
+protected
+
+  def setup_basic_graph(size=800)
+    g = Gruff::Pie.new(size)
+    g.title = "My Graph Title"
+    @datasets.each do |data|
+      g.data(data[0], data[1])
+    end
+    return g
+  end
+  
+end
diff --git a/lib/gruff-0.2.8/test/test_scene.rb b/lib/gruff-0.2.8/test/test_scene.rb
new file mode 100644 (file)
index 0000000..7603d29
--- /dev/null
@@ -0,0 +1,100 @@
+#!/usr/bin/ruby
+
+require File.dirname(__FILE__) + "/gruff_test_case"
+require 'yaml'
+
+class LayerStub < Gruff::Layer; attr_reader :base_dir, :filenames, :selected_filename; end
+
+class TestGruffScene < GruffTestCase
+
+  def test_hazy
+    g = setup_scene
+    g.weather = "cloudy"
+    g.haze = true
+    g.time = Time.mktime(2006, 7, 4, 4, 35)
+    g.write "test/output/scene_hazy_night.png"
+  end
+
+  def test_stormy_night
+    g = setup_scene
+    g.weather = "stormy"
+    g.time = Time.mktime(2006, 7, 4, 0, 0)
+    g.write "test/output/scene_stormy_night.png"
+  end
+
+
+  def test_not_hazy
+    g = setup_scene
+    g.weather = "cloudy"
+    g.haze = false
+    g.time = Time.mktime(2006, 7, 4, 6, 00)
+    g.write "test/output/scene_not_hazy_day.png"
+  end
+
+  def test_partly_cloudy
+    g = setup_scene
+    g.weather = "partly cloudy"
+    g.haze = false
+    g.time = Time.mktime(2006, 7, 4, 13, 00)
+    g.write "test/output/scene_partly_cloudy_day.png"
+  end
+
+
+  def test_stormy_day
+    g = setup_scene
+    g.weather = "stormy"
+    g.haze = false
+    g.time = Time.mktime(2006, 7, 4, 8, 00)
+    g.write "test/output/scene_stormy_day.png"
+  end
+
+
+  def test_layer
+    l = LayerStub.new(File.expand_path("../assets/city_scene", File.dirname(__FILE__)), "clouds")
+    assert_equal %w(cloudy.png partly_cloudy.png stormy.png), l.filenames
+    
+    l = LayerStub.new(File.expand_path("../assets/city_scene", File.dirname(__FILE__)), "grass")
+    assert_equal 'default.png', l.selected_filename
+    
+    l = LayerStub.new(File.expand_path("../assets/city_scene", File.dirname(__FILE__)), "sky")
+    l.update Time.mktime(2006, 7, 4, 12, 35) # 12:35, July 4, 2006
+    assert_equal '1200.png', l.selected_filename
+
+    l = LayerStub.new(File.expand_path("../assets/city_scene", File.dirname(__FILE__)), "sky")
+    l.update Time.mktime(2006, 7, 4, 0, 0) # 00:00, July 4, 2006
+    assert_equal '0000.png', l.selected_filename
+
+    l = LayerStub.new(File.expand_path("../assets/city_scene", File.dirname(__FILE__)), "sky")
+    l.update Time.mktime(2006, 7, 4, 23, 35) # 23:35, July 4, 2006
+    assert_equal '2000.png', l.selected_filename
+
+    l = LayerStub.new(File.expand_path("../assets/city_scene", File.dirname(__FILE__)), "sky")
+    l.update Time.mktime(2006, 7, 4, 0, 1) # 00:01, July 4, 2006
+    assert_equal '0000.png', l.selected_filename
+
+    l = LayerStub.new(File.expand_path("../assets/city_scene", File.dirname(__FILE__)), "sky")
+    l.update Time.mktime(2006, 7, 4, 2, 0) # 02:00, July 4, 2006
+    assert_equal '0200.png', l.selected_filename
+
+    l = LayerStub.new(File.expand_path("../assets/city_scene", File.dirname(__FILE__)), "sky")
+    l.update Time.mktime(2006, 7, 4, 4, 00) # 04:00, July 4, 2006
+    assert_equal '0400.png', l.selected_filename
+    
+    # TODO Need number_sample folder
+    # l = LayerStub.new(File.expand_path("../assets/city_scene", File.dirname(__FILE__)), "number_sample")
+    # assert_equal %w(1.png 2.png default.png), l.filenames
+    # l.update 3
+    # assert_equal 'default.png', l.selected_filename
+  end
+
+private
+
+  def setup_scene
+    g = Gruff::Scene.new("500x100", File.expand_path("../assets/city_scene", File.dirname(__FILE__)) )
+    g.layers = %w(background haze sky clouds)
+    g.weather_group = %w(clouds)
+    g.time_group = %w(background sky)
+    g    
+  end
+
+end
diff --git a/lib/gruff-0.2.8/test/test_side_bar.rb b/lib/gruff-0.2.8/test/test_side_bar.rb
new file mode 100644 (file)
index 0000000..7d2ea3d
--- /dev/null
@@ -0,0 +1,12 @@
+
+require File.dirname(__FILE__) + "/gruff_test_case"
+
+class TestGruffSideBar < GruffTestCase
+
+  def test_bar_graph
+    g = setup_basic_graph(Gruff::SideBar, 800)
+    write_test_file g, 'side_bar.png'    
+  end
+
+end
+
diff --git a/lib/gruff-0.2.8/test/test_sidestacked_bar.rb b/lib/gruff-0.2.8/test/test_sidestacked_bar.rb
new file mode 100644 (file)
index 0000000..5bcc670
--- /dev/null
@@ -0,0 +1,89 @@
+#!/usr/bin/ruby
+
+require File.dirname(__FILE__) + "/gruff_test_case"
+
+class TestGruffSideStackedBar < GruffTestCase
+
+  def setup
+    @datasets = [
+      [:Jimmy, [25, 36, 86, 39]],
+      [:Charles, [80, 54, 67, 54]],
+      [:Julie, [22, 29, 35, 38]],
+      #[:Jane, [95, 95, 95, 90, 85, 80, 88, 100]],
+      #[:Philip, [90, 34, 23, 12, 78, 89, 98, 88]],
+      #["Arthur", [5, 10, 13, 11, 6, 16, 22, 32]],
+      ]
+    @sample_labels = {
+        0 => '5/6', 
+        1 => '5/15', 
+        2 => '5/24'
+      }      
+
+  end
+
+  def test_bar_graph
+    g = Gruff::SideStackedBar.new
+    g.title = "Visual Stacked Bar Graph Test"
+    g.labels = {
+      0 => '5/6', 
+      1 => '5/15', 
+      2 => '5/24', 
+      3 => '5/30', 
+    }
+    @datasets.each do |data|
+      g.data(data[0], data[1])
+    end
+    g.write "test/output/side_stacked_bar_keynote.png"
+  end
+
+
+  def test_bar_graph_small
+    g = Gruff::SideStackedBar.new(400)
+    g.title = "Visual Stacked Bar Graph Test"
+    g.labels = {
+      0 => '5/6', 
+      1 => '5/15', 
+      2 => '5/24', 
+      3 => '5/30', 
+    }
+    @datasets.each do |data|
+      g.data(data[0], data[1])
+    end
+    g.write "test/output/side_stacked_bar_keynote_small.png"
+  end
+
+  def test_wide
+    g = setup_basic_graph('800x400')
+    g.title = "Wide SSBar"
+    g.write "test/output/side_stacked_bar_wide.png"
+  end
+
+  def test_should_space_long_left_labels_appropriately
+    g = Gruff::SideStackedBar.new
+    g.title = "Stacked Bar Long Label"
+    g.labels = {
+      0 => 'September', 
+      1 => 'Oct', 
+      2 => 'Nov', 
+      3 => 'Dec', 
+    }
+    @datasets.each do |data|
+      g.data(data[0], data[1])
+    end
+    g.write "test/output/side_stacked_bar_long_label.png"
+  end
+
+protected
+
+  def setup_basic_graph(size=800)
+    g = Gruff::SideStackedBar.new(size)
+    g.title = "My Graph Title"
+    g.labels = @sample_labels
+    @datasets.each do |data|
+      g.data(data[0], data[1])
+    end
+    return g
+  end
+
+end
+
diff --git a/lib/gruff-0.2.8/test/test_spider.rb b/lib/gruff-0.2.8/test/test_spider.rb
new file mode 100644 (file)
index 0000000..99dbb2d
--- /dev/null
@@ -0,0 +1,216 @@
+#!/usr/bin/ruby
+
+require File.dirname(__FILE__) + "/gruff_test_case"
+
+class TestGruffSpider < GruffTestCase
+
+  def setup
+    @datasets = [
+      [:Strength, [10]],
+      [:Dexterity, [16]],
+      [:Constitution, [12]],
+      [:Intelligence, [12]],
+      [:Wisdom, [10]],
+      ["Charisma", [16]],
+      ]
+
+#     @datasets = [
+#       [:Darren, [25]],
+#       [:Chris, [80]],
+#       [:Egbert, [22]],
+#       [:Adam, [95]],
+#       [:Bill, [90]],
+#       ["Frank", [5]],
+#       ["Zero", [0]],
+#       ]
+  end
+  
+  def test_spider_graph
+    g = Gruff::Spider.new(20)
+    g.title = "Spider Graph Test"
+    @datasets.each do |data|
+      g.data(data[0], data[1])
+    end
+
+    # Default theme
+    g.write("test/output/spider_keynote.png")
+  end
+
+  def test_pie_graph_small
+    g = Gruff::Spider.new(20, 400)
+    g.title = "Visual Spider Graph Test Small"
+    @datasets.each do |data|
+      g.data(data[0], data[1])
+    end
+
+    # Default theme
+    g.write("test/output/spider_small.png")
+  end
+
+  def test_spider_graph_nearly_equal
+    g = Gruff::Spider.new(50)
+    g.title = "Spider Graph Nearly Equal"
+    
+    g.data(:Blake, [41])
+    g.data(:Aaron, [42])
+    g.data(:Grouch, [40])
+#    g.data(:Snuffleupagus, [43])
+
+    g.write("test/output/spider_nearly_equal.png")
+  end
+
+  def test_pie_graph_equal
+    g = Gruff::Spider.new(50)
+    g.title = "Spider Graph Equal"
+        
+    g.data(:Bert, [41])
+    g.data(:Adam, [41])
+    g.data(:Joe, [41])
+
+    g.write("test/output/spider_equal.png")
+  end
+
+  def test_pie_graph_zero
+    g = Gruff::Spider.new(2)
+    g.title = "Pie Graph Two One Zero"
+        
+    g.data(:Bert, [0])
+    g.data(:Adam, [1])
+    g.data(:Sam,  [2])
+
+    g.write("test/output/spider_zero.png")
+  end
+
+  def test_wide
+    g = setup_basic_graph('800x400')
+    g.title = "Wide spider"
+    g.write("test/output/spider_wide.png")
+  end
+
+  def test_label_size
+    g = setup_basic_graph()
+    g.title = "Spider With Small Legend"
+    g.legend_font_size = 10
+    g.write("test/output/spider_legend.png")
+    
+    g = setup_basic_graph(400)
+    g.title = "Small spider With Small Legend"
+    g.legend_font_size = 10
+    g.write("test/output/spider_legend_small.png")
+  end
+  def test_theme_37signals
+    g = Gruff::Spider.new(20)
+    g.title = "Spider Graph Test"
+    @datasets.each do |data|
+     g.data(data[0], data[1])
+    end
+
+    g.theme_37signals
+
+    # Default theme
+    g.write("test/output/spider_37signals.png")
+  end
+  def test_no_axes
+    g = Gruff::Spider.new(20)
+    g.title = "Look ma, no axes"
+    g.hide_axes = true
+    @datasets.each do |data|
+     g.data(data[0], data[1])
+    end
+    g.write("test/output/spider_no_axes.png")
+
+  end
+  def test_no_print
+    g = Gruff::Spider.new(20)
+    g.title = "Should not print"
+    g.hide_text = true
+    @datasets.each do |data|
+      g.data(data[0], data[1])
+    end
+    g.write("test/output/spider_no_print.png")
+  end
+  def test_transparency
+    g = Gruff::Spider.new(20)
+    g.title = "Transparent background"
+    g.hide_text = true
+    g.transparent_background = true
+    g.hide_axes = true
+    @datasets.each do |data|
+      g.data(data[0], data[1])
+    end
+    g.write("test/output/spider_no_background.png")
+  end
+  
+  def test_overlay
+    g = Gruff::Spider.new(20)
+    g.title = "George (blue) vs Sarah (white)"
+    @datasets.each do |data|
+      g.data(data[0], data[1])
+    end
+    g.write("test/output/spider_overlay_1.png")
+    
+    g = Gruff::Spider.new(20)
+    g.title = "Transparent background"
+    g.hide_text = true
+    g.hide_axes = true
+    g.transparent_background = true
+    @datasets = [
+      [:Strength, [18]],
+      [:Dexterity, [10]],
+      [:Constitution, [18]],
+      [:Intelligence, [8]],
+      [:Wisdom, [14]],
+      ["Charisma", [4]],
+      ]   
+    @datasets.each do |data|
+      g.data(data[0], data[1])
+    end
+    g.marker_color = "#4F6EFF"
+    g.write("test/output/spider_overlay_2.png")
+  end  
+  def test_lots_of_data
+    g = Gruff::Spider.new(10)
+    @datasets = [[:a, [1]], [:b, [5]], [:c, [3]], [:d, [9]], [:e, [4]],
+                 [:f, [7]], [:g, [0]], [:h, [4]], [:i, [6]], [:j, [0]],
+                 [:k, [4]], [:l, [8]]]
+   
+    @datasets.each do |data|
+      g.data(data[0], data[1])
+    end
+                 
+    g.title = "Sample Data"
+    g.write("test/output/spider_lots_of_data.png")
+  end
+  
+  def test_lots_of_data_with_large_names
+    g = Gruff::Spider.new(10)
+    @datasets = [[:anteaters, [1]], [:bulls, [5]], [:cats, [3]], [:dogs, [9]], [:elephants, [4]],
+                 [:frogs, [7]], [:giraffes, [0]], [:hamsters, [4]], [:iguanas, [6]], 
+                 [:jaguar, [0]], [:kangaroo, [4]], [:locust, [8]]]
+   
+    @datasets.each do |data|
+      g.data(data[0], data[1])
+    end
+                 
+    g.title = "Zoo Inventory"
+    g.write("test/output/spider_lots_of_data_normal_names.png")
+  end
+  
+
+protected
+
+  def setup_basic_graph(size=800, max = 20)
+    g = Gruff::Spider.new(max, size)
+    g.title = "My Graph Title"
+    @datasets.each do |data|
+      g.data(data[0], data[1])
+    end
+    return g
+  end
+  
+end
diff --git a/lib/gruff-0.2.8/test/test_stacked_bar.rb b/lib/gruff-0.2.8/test/test_stacked_bar.rb
new file mode 100644 (file)
index 0000000..ab943d6
--- /dev/null
@@ -0,0 +1,52 @@
+#!/usr/bin/ruby
+
+require File.dirname(__FILE__) + "/gruff_test_case"
+
+class TestGruffStackedBar < GruffTestCase
+
+  def setup
+    @datasets = [
+      [:Jimmy, [25, 36, 86, 39]],
+      [:Charles, [80, 54, 67, 54]],
+      [:Julie, [22, 29, 35, 38]],
+      ]
+    @sample_labels = {
+        0 => '5/6', 
+        1 => '5/15', 
+        2 => '5/24'
+      }      
+
+  end
+
+  def test_bar_graph
+    g = Gruff::StackedBar.new
+    g.title = "Visual Stacked Bar Graph Test"
+    g.labels = {
+      0 => '5/6', 
+      1 => '5/15', 
+      2 => '5/24', 
+      3 => '5/30', 
+    }
+    @datasets.each do |data|
+      g.data(data[0], data[1])
+    end
+    g.write "test/output/stacked_bar_keynote.png"
+  end
+
+
+  def test_bar_graph_small
+    g = Gruff::StackedBar.new(400)
+    g.title = "Visual Stacked Bar Graph Test"
+    g.labels = {
+      0 => '5/6', 
+      1 => '5/15', 
+      2 => '5/24', 
+      3 => '5/30', 
+    }
+    @datasets.each do |data|
+      g.data(data[0], data[1])
+    end
+    g.write "test/output/stacked_bar_keynote_small.png"
+  end
+  
+end

Benjamin Mako Hill || Want to submit a patch?