scruffy 0.0.9

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGES ADDED
@@ -0,0 +1,11 @@
1
+ = Scruffy Changelog
2
+
3
+ == Version 0.0.9
4
+ (August 10th, 2006)
5
+
6
+ * Initial release.
7
+ * Standard renderer.
8
+ * Marker transformers: currency, percentages.
9
+ * Basic Graphs: Area, Bar, Line.
10
+ * Advanced Graphs: Average, AllSmiles.
11
+ * Initial documentation.
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2006 Brasten Sager (brasten@nagilum.com)
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,9 @@
1
+ ==Scruffy Graphs
2
+
3
+ Author:: Brasten Sager (brasten@nagilum.com)
4
+ Date:: August 5th, 2006
5
+
6
+ Scruffy is a Ruby library for generating high quality, good looking graphs. It is designed
7
+ to be easy to use and highly customizable.
8
+
9
+ For basic usage instructions, refer to the documentation for Scruffy::Graph.
data/Rakefile ADDED
@@ -0,0 +1,103 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rake/testtask'
4
+ require 'rake/rdoctask'
5
+ require 'rake/packagetask'
6
+ require 'rake/gempackagetask'
7
+ require 'rake/contrib/rubyforgepublisher'
8
+ require 'lib/scruffy'
9
+ require 'spec'
10
+ require 'diff/lcs'
11
+ require 'spec/rake/spectask'
12
+
13
+ PKG_NAME = "scruffy"
14
+ PKG_VERSION = Scruffy::VERSION
15
+ PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
16
+ PKG_FILES = FileList[
17
+ '[A-Z]*',
18
+ 'lib/**/*.rb',
19
+ 'spec/**/*.rb'
20
+ ]
21
+
22
+ desc 'Runs all RSpec specs - translated with test2spec from our own tests'
23
+ Spec::Rake::SpecTask.new do |t|
24
+ t.spec_files = FileList['spec/**/*_spec.rb']
25
+ end
26
+
27
+ desc "Deploy docs to RubyForge"
28
+ task :rdoc_deploy => [:rdoc] do
29
+ dirs = %w{doc}
30
+ onserver = "brasten@rubyforge.org:/var/www/gforge-projects/scruffy"
31
+ dirs.each do | dir|
32
+ `scp -r "#{`pwd`.chomp}/#{dir}" "#{onserver}"`
33
+ end
34
+ end
35
+
36
+ # Genereate the RDoc documentation
37
+ Rake::RDocTask.new { |rdoc|
38
+ rdoc.rdoc_dir = 'doc'
39
+ rdoc.title = "Scruffy - Graphing Library for Ruby"
40
+ # rdoc.options << '--line-numbers --inline-source --main README --accessor adv_attr_accessor=M'
41
+ # rdoc.template = "#{ENV['template']}.rb" if ENV['template']
42
+ rdoc.rdoc_files.include('README', 'CHANGELOG')
43
+ rdoc.rdoc_files.include('lib/scruffy.rb')
44
+ rdoc.rdoc_files.include('lib/scruffy/*.rb')
45
+ rdoc.rdoc_files.include('lib/scruffy/graph_layers/*.rb')
46
+ rdoc.rdoc_files.include('lib/scruffy/renderers/*.rb')
47
+ }
48
+
49
+ spec = Gem::Specification.new do |s|
50
+ s.name = PKG_NAME
51
+ s.version = PKG_VERSION
52
+ s.author = "Brasten Sager"
53
+ s.email = "brasten@nagilum.com"
54
+ s.homepage = "http://scruffy.rubyforge.org"
55
+ s.summary = "A powerful, clean graphing library for Ruby."
56
+ s.description = <<-EOF
57
+ Scruffy is a Ruby library for generating powerful graphs. It is based on
58
+ SVG, allowing for powerful, clean code, as well as a good foundation for
59
+ future features.
60
+ EOF
61
+
62
+ s.files = PKG_FILES.to_a
63
+ s.require_path = 'lib'
64
+
65
+ s.has_rdoc = true
66
+
67
+ #s.test_files = Dir.glob('test/*_test.rb')
68
+ s.require_path = 'lib'
69
+ s.autorequire = 'scruffy'
70
+ s.rubyforge_project = "scruffy"
71
+ end
72
+
73
+ Rake::GemPackageTask.new(spec) do |pkg|
74
+ pkg.need_zip = true
75
+ pkg.need_tar = true
76
+ end
77
+
78
+ task :verify_user do
79
+ raise "RUBYFORGE_USER environment variable not set!" unless ENV['RUBYFORGE_USER']
80
+ end
81
+
82
+ task :verify_password do
83
+ raise "RUBYFORGE_PASSWORD environment variable not set!" unless ENV['RUBYFORGE_PASSWORD']
84
+ end
85
+
86
+ desc "Publish gem+tgz+zip on RubyForge. You must make sure lib/version.rb is aligned with the CHANGELOG file"
87
+ task :publish_packages => [:verify_user, :verify_password, :package] do
88
+ require 'meta_project'
89
+ require 'rake/contrib/xforge'
90
+ release_files = FileList[
91
+ "pkg/#{PKG_FILE_NAME}.gem",
92
+ "pkg/#{PKG_FILE_NAME}.tgz",
93
+ "pkg/#{PKG_FILE_NAME}.zip"
94
+ ]
95
+
96
+ Rake::XForge::Release.new(MetaProject::Project::XForge::RubyForge.new(PKG_NAME)) do |xf|
97
+ # Never hardcode user name and password in the Rakefile!
98
+ xf.user_name = ENV['RUBYFORGE_USER']
99
+ xf.password = ENV['RUBYFORGE_PASSWORD']
100
+ xf.files = release_files.to_a
101
+ xf.release_name = "Scruffy #{PKG_VERSION}"
102
+ end
103
+ end
data/lib/scruffy.rb ADDED
@@ -0,0 +1,23 @@
1
+ $:.unshift(File.dirname(__FILE__)) unless
2
+ $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
3
+
4
+ require 'rubygems'
5
+ require_gem 'builder', '>= 2.0'
6
+
7
+ # Base files
8
+ require 'scruffy/graph'
9
+ require 'scruffy/themes'
10
+ require 'scruffy/version'
11
+ require 'scruffy/transformer'
12
+
13
+ # Layer files
14
+ require 'scruffy/graph_layers/base'
15
+ require 'scruffy/graph_layers/area'
16
+ require 'scruffy/graph_layers/all_smiles'
17
+ require 'scruffy/graph_layers/bar'
18
+ require 'scruffy/graph_layers/blue_screen'
19
+ require 'scruffy/graph_layers/line'
20
+ require 'scruffy/graph_layers/average'
21
+
22
+ # Renderers
23
+ require 'scruffy/renderers/standard'
@@ -0,0 +1,182 @@
1
+ # ===Scruffy Graphing Library for Ruby
2
+ #
3
+ # Author:: Brasten Sager
4
+ # Date:: August 5th, 2006
5
+ #
6
+ # For information on generating graphs using Scruffy, see the
7
+ # documentation in Scruffy::Graph.
8
+ #
9
+ # For information on creating your own graph types, see the
10
+ # documentation in Scruffy::GraphLayer.
11
+ module Scruffy
12
+
13
+ # ==Scruffy Graphs
14
+ #
15
+ # Author:: Brasten Sager
16
+ # Date:: August 5th, 2006
17
+ #
18
+ #
19
+ # ====Graphs vs. Layers (Graph Types)
20
+ #
21
+ # Scruffy::Graph is the primary class you will use to generate your graphs. A Graph does not
22
+ # define a graph type nor does it directly hold any data. Instead, a Graph object can be thought
23
+ # of as a canvas on which other graphs are draw. (The actual graphs themselves are subclasses of Scruffy::GraphLayer)
24
+ # Despite the technical distinction, we will refer to Scruffy::Graph objects as 'graphs' and Scruffy::GraphLayers as
25
+ # 'layers' or 'graph types.'
26
+ #
27
+ #
28
+ # ==== Creating a Graph
29
+ #
30
+ # You can begin building a graph by instantiating a Graph object and optionally passing a hash
31
+ # of properties.
32
+ #
33
+ # graph = Scruffy::Graph.new
34
+ #
35
+ # OR
36
+ #
37
+ # graph = Scruffy::Graph.new(:title => "Monthly Profits", :theme => Scruffy::Theme::RUBY_BLOG)
38
+ #
39
+ # Once you have a Graph object, you can set any Graph-level properties (title, theme, etc), or begin adding
40
+ # graph layers. You can add a graph layer to a graph by using the Graph#add or Graph#<< methods. The two
41
+ # methods are identical and used to accommodate syntax preferences.
42
+ #
43
+ # graph.add(:line, 'John', [100, -20, 30, 60])
44
+ # graph.add(:line, 'Sara', [120, 50, -80, 20])
45
+ #
46
+ # OR
47
+ #
48
+ # graph << Scruffy::LineLayer.new(:title => 'John', :points => [100, -20, 30, 60])
49
+ # graph << Scruffy::LineLayer.new(:title => 'Sara', :points => [120, 50, -80, 20])
50
+ #
51
+ # Now that we've created our graph and added a layer to it, we're ready to render! You can render the graph
52
+ # directly to SVG with the Graph#render method:
53
+ #
54
+ # graph.render # Renders a 600x400 SVG graph
55
+ #
56
+ # OR
57
+ #
58
+ # graph.render(:size => [1500, 1000])
59
+ #
60
+ # And that's your basic Scruffy graph! Please check the documentation for the various methods and
61
+ # classes you'll be using, as there are a bunch of options not demonstrated here.
62
+ #
63
+ # A couple final things worth noting:
64
+ # * You can call Graph#render as often as you wish with different rendering options. In
65
+ # fact, you can modify the graph any way you wish between renders.
66
+ #
67
+ #
68
+ # * There are no restrictions to the combination of graph layers you can add. It is perfectly
69
+ # valid to do something like:
70
+ # graph.add(:line, [100, 200, 300])
71
+ # graph.add(:bar, [200, 150, 150])
72
+ #
73
+ # Of course, while you may be able to combine some things such as pie charts and line graphs, that
74
+ # doesn't necessarily mean they will make any logical sense together. We leave those decisions up to you. :)
75
+ class Graph
76
+ attr_accessor :title
77
+ attr_accessor :theme
78
+ attr_accessor :layers
79
+ attr_accessor :default_type
80
+ attr_accessor :point_markers
81
+ attr_accessor :renderer
82
+ attr_accessor :marker_transformer
83
+
84
+ # Returns a new Graph. You can optionally pass in a default graph type and an options hash.
85
+ #
86
+ # Graph.new # New graph
87
+ # Graph.new(:line) # New graph with default graph type of LineLayer
88
+ # Graph.new({...}) # New graph with options.
89
+ #
90
+ # Options:
91
+ #
92
+ # title:: Graph's title
93
+ # theme:: Theme to use when rendering graph
94
+ # renderer:: Sets the renderer to use when rendering graph
95
+ # marker_transformer:: Sets a transformer used to modify marker values prior to rendering
96
+ # point_markers:: Sets the x-axis marker values
97
+ def initialize(*args)
98
+ @default_type = args.shift if args.first.is_a?(Symbol)
99
+ options = args.shift if args.first.is_a?(Hash)
100
+ options ||= {}
101
+
102
+ @title = options[:title]
103
+ @theme = options[:theme] || Scruffy::Theme::KEYNOTE
104
+ @layers = []
105
+ @renderer = options[:renderer] || Scruffy::StandardRenderer.new
106
+ @marker_transformer = options[:marker_transformer]
107
+ @point_markers = options[:point_markers]
108
+ end
109
+
110
+ # Renders the graph in it's current state to an SVG object.
111
+ #
112
+ # Options:
113
+ # size:: An array indicating the size you wish to render the graph. ( [x, y] )
114
+ # width:: The width of the rendered graph. A height is calculated at 60% of the width.
115
+ # theme:: Theme used to render graph for this render only.
116
+ # min_value:: Overrides the calculated minimum value used for the graph.
117
+ # max_value:: Overrides the calculated maximum value used for the graph.
118
+ #
119
+ def render(options = {})
120
+ size = options.delete(:size) || (options[:width] ? [options[:width], (options.delete(:width) * 0.6).to_i] : [600, 400])
121
+ options[:theme] ||= @theme
122
+ options[:marker_transformer] ||= @marker_transformer
123
+ options[:point_markers] ||= @point_markers
124
+
125
+ @renderer.render( options.merge({:size => size, :title => title,
126
+ :layers => layers, :min_value => (options[:min_value] || bottom_value), :max_value => (options[:max_value] || top_value) } ) )
127
+ end
128
+
129
+ # Adds a GraphLayer to the Graph. Accepts either a list of arguments used to build a new layer, or
130
+ # a GraphLayer object. When passing a list of arguments, all arguments are optional, but the arguments
131
+ # specified must be provided in a particular order: type (Symbol), title (String), points (Array),
132
+ # options (Hash).
133
+ #
134
+ # Both #add and #<< can be used.
135
+ #
136
+ # graph.add(:line, [100, 200, 150]) # Create and add an untitled line graph
137
+ #
138
+ # graph << (:line, "John's Sales", [150, 100]) # Create and add a titled line graph
139
+ #
140
+ # graph << BarLayer.new({...}) # Adds BarLayer object to graph
141
+ #
142
+ def <<(*args)
143
+ if args[0].kind_of?(Scruffy::GraphLayer)
144
+ layers << args[0]
145
+ else
146
+ type = args.first.is_a?(Symbol) ? args.shift : default_type
147
+ title = args.shift if args.first.is_a?(String)
148
+ points = args.first.is_a?(Array) ? args.shift : []
149
+ options = args.first.is_a?(Hash) ? args.shift : {}
150
+
151
+ title ||= ''
152
+
153
+ raise ArgumentError,
154
+ 'You must specify a graph type (:area, :bar, :line, etc) if you do not have a default type specified.' if type.nil?
155
+
156
+ layer = Kernel::module_eval("Scruffy::#{to_camelcase(type.to_s)}Layer").new(options.merge({:points => points, :title => title}))
157
+ layers << layer
158
+ end
159
+ layer
160
+ end
161
+
162
+ alias :add :<<
163
+
164
+ protected
165
+ def to_camelcase(type) # :nodoc:
166
+ type.split('_').map { |e| e.capitalize }.join('')
167
+ end
168
+
169
+ def top_value # :nodoc:
170
+ layers.inject(0) { |max, layer| (max = ((max < layer.highest_point) ? layer.highest_point : max)) unless layer.highest_point.nil?; max }
171
+ end
172
+
173
+ def bottom_value # :nodoc:
174
+ botval = layers.inject(top_value) { |min, layer| (min = ((min > layer.lowest_point) ? layer.lowest_point : min)) unless layer.lowest_point.nil?; min }
175
+ above_zero = (botval > 0)
176
+
177
+ botval = (botval - ((top_value - botval) * 0.15))
178
+ botval = 0 if (above_zero && botval < 0)
179
+ botval
180
+ end
181
+ end
182
+ end
@@ -0,0 +1,107 @@
1
+ module Scruffy
2
+ class AllSmilesLayer < GraphLayer
3
+ attr_accessor :standalone
4
+
5
+ def initialize(options = {})
6
+ super
7
+ @standalone = options[:standalone] || false
8
+ end
9
+
10
+ def draw(svg, coords, options={})
11
+
12
+ hero_smiley = nil
13
+ coords.each { |c| hero_smiley = c.last if (hero_smiley.nil? || c.last < hero_smiley) }
14
+
15
+ svg.defs {
16
+ svg.radialGradient(:id => 'SmileyGradient', :cx => '50%',
17
+ :cy => '50%', :r => '50%', :fx => '30%', :fy => '30%') {
18
+
19
+ svg.stop(:offset => '0%', 'stop-color' => '#FFF')
20
+ svg.stop(:offset => '20%', 'stop-color' => '#FFC')
21
+ svg.stop(:offset => '45%', 'stop-color' => '#FF3')
22
+ svg.stop(:offset => '60%', 'stop-color' => '#FF0')
23
+ svg.stop(:offset => '90%', 'stop-color' => '#990')
24
+ svg.stop(:offset => '100%', 'stop-color' => '#220')
25
+ }
26
+ svg.radialGradient(:id => 'HeroGradient', :cx => '50%',
27
+ :cy => '50%', :r => '50%', :fx => '30%', :fy => '30%') {
28
+
29
+ svg.stop(:offset => '0%', 'stop-color' => '#FEE')
30
+ svg.stop(:offset => '20%', 'stop-color' => '#F0E0C0')
31
+ svg.stop(:offset => '45%', 'stop-color' => '#8A2A1A')
32
+ svg.stop(:offset => '60%', 'stop-color' => '#821')
33
+ svg.stop(:offset => '90%', 'stop-color' => '#210')
34
+ }
35
+ svg.radialGradient(:id => 'StarGradient', :cx => '50%',
36
+ :cy => '50%', :r => '50%', :fx => '30%', :fy => '30%') {
37
+
38
+ svg.stop(:offset => '0%', 'stop-color' => '#FFF')
39
+ svg.stop(:offset => '20%', 'stop-color' => '#EFEFEF')
40
+ svg.stop(:offset => '45%', 'stop-color' => '#DDD')
41
+ svg.stop(:offset => '60%', 'stop-color' => '#BBB')
42
+ svg.stop(:offset => '90%', 'stop-color' => '#888')
43
+ }
44
+ }
45
+
46
+ unless standalone
47
+ svg.polyline( :points => stringify_coords(coords).join(' '), :fill => 'none',
48
+ :stroke => '#660', 'stroke-width' => scaled(10), 'stroke-dasharray' => "#{scaled(10)}, #{scaled(10)}" )
49
+ end
50
+
51
+ coords.each do |coord|
52
+ if standalone
53
+ svg.line( :x1 => coord.first, :y1 => coord.last, :x2 => coord.first, :y2 => height, :fill => 'none',
54
+ :stroke => '#660', 'stroke-width' => scaled(10), 'stroke-dasharray' => "#{scaled(10)}, #{scaled(10)}" )
55
+ end
56
+ svg.circle( :cx => coord.first + scaled(2), :cy => coord.last + scaled(2), :r => scaled(15),
57
+ :fill => 'black', :stroke => 'none', :opacity => 0.4)
58
+ svg.circle( :cx => coord.first, :cy => coord.last, :r => scaled(15),
59
+ :fill => (complexity == :minimal ? 'yellow' : 'url(#SmileyGradient)'), :stroke => 'black', 'stroke-width' => scaled(1) )
60
+ svg.line( :x1 => (coord.first - scaled(3)),
61
+ :x2 => (coord.first - scaled(3)),
62
+ :y1 => (coord.last),
63
+ :y2 => (coord.last - scaled(7)), :stroke => 'black', 'stroke-width' => scaled(1.4) )
64
+ svg.line( :x1 => (coord.first + scaled(3)),
65
+ :x2 => (coord.first + scaled(3)),
66
+ :y1 => (coord.last),
67
+ :y2 => (coord.last - scaled(7)), :stroke => 'black', 'stroke-width' => scaled(1.4) )
68
+
69
+
70
+ percent = 1.0 - (coord.last.to_f / height.to_f)
71
+
72
+ corners = scaled(8 - (5 * percent))
73
+ anchor = scaled((20 * percent) - 5)
74
+ svg.path( :d => "M#{coord.first - scaled(9)} #{coord.last + corners} Q#{coord.first} #{coord.last + anchor} #{coord.first + scaled(9)} #{coord.last + corners}",
75
+ :stroke => 'black', 'stroke-width' => scaled(1.4), :fill => 'none' )
76
+
77
+
78
+ # top hat
79
+ if coord.last == hero_smiley
80
+ svg.ellipse(:cx => coord.first, :cy => (coord.last - scaled(13)),
81
+ :rx => scaled(17), :ry => scaled(6.5), :fill => (complexity == :minimal ? 'purple' : 'url(#HeroGradient)'), :stroke => 'black', 'stroke-width' => scaled(1.4) )
82
+
83
+ svg.path(:d => "M#{coord.first} #{coord.last - scaled(60)} " +
84
+ "L#{coord.first + scaled(10)} #{coord.last - scaled(14)} " +
85
+ "C#{coord.first + scaled(10)},#{coord.last - scaled(9)} #{coord.first - scaled(10)},#{coord.last - scaled(9)} #{coord.first - scaled(10)},#{coord.last - scaled(14)}" +
86
+ "L#{coord.first} #{coord.last - scaled(60)}",
87
+ :stroke => 'black', 'stroke-width' => scaled(1.4), :fill => (complexity == :minimal ? 'purple' : 'url(#HeroGradient)'))
88
+
89
+ svg.path(:d => "M#{coord.first - scaled(4)} #{coord.last - scaled(23)}" +
90
+ "l-#{scaled(2.5)} #{scaled(10)} l#{scaled(7.5)} -#{scaled(5)} l-#{scaled(10)} 0 l#{scaled(7.5)} #{scaled(5)} l-#{scaled(2.5)} -#{scaled(10)}", :stroke => 'none', :fill => (complexity == :minimal ? 'white': 'url(#StarGradient)') )
91
+ svg.path(:d => "M#{coord.first + scaled(2)} #{coord.last - scaled(30)}" +
92
+ "l-#{scaled(2.5)} #{scaled(10)} l#{scaled(7.5)} -#{scaled(5)} l-#{scaled(10)} 0 l#{scaled(7.5)} #{scaled(5)} l-#{scaled(2.5)} -#{scaled(10)}", :stroke => 'none', :fill => (complexity == :minimal ? 'white': 'url(#StarGradient)') )
93
+ svg.path(:d => "M#{coord.first - scaled(2)} #{coord.last - scaled(33)}" +
94
+ "l-#{scaled(1.25)} #{scaled(5)} l#{scaled(3.75)} -#{scaled(2.5)} l-#{scaled(5)} 0 l#{scaled(3.75)} #{scaled(2.5)} l-#{scaled(1.25)} -#{scaled(5)}", :stroke => 'none', :fill => 'white' )
95
+ svg.path(:d => "M#{coord.first - scaled(2.2)} #{coord.last - scaled(32.7)}" +
96
+ "l-#{scaled(1.25)} #{scaled(5)} l#{scaled(3.75)} -#{scaled(2.5)} l-#{scaled(5)} 0 l#{scaled(3.75)} #{scaled(2.5)} l-#{scaled(1.25)} -#{scaled(5)}", :stroke => 'none', :fill => (complexity == :minimal ? 'white': 'url(#StarGradient)') )
97
+ svg.path(:d => "M#{coord.first + scaled(4.5)} #{coord.last - scaled(20)}" +
98
+ "l-#{scaled(1.25)} #{scaled(5)} l#{scaled(3.75)} -#{scaled(2.5)} l-#{scaled(5)} 0 l#{scaled(3.75)} #{scaled(2.5)} l-#{scaled(1.25)} -#{scaled(5)}", :stroke => 'none', :fill => (complexity == :minimal ? 'white': 'url(#StarGradient)') )
99
+ svg.path(:d => "M#{coord.first} #{coord.last - scaled(40)}" +
100
+ "l-#{scaled(1.25)} #{scaled(5)} l#{scaled(3.75)} -#{scaled(2.5)} l-#{scaled(5)} 0 l#{scaled(3.75)} #{scaled(2.5)} l-#{scaled(1.25)} -#{scaled(5)}", :stroke => 'none', :fill => (complexity == :minimal ? 'white': 'url(#StarGradient)') )
101
+
102
+ end
103
+
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,32 @@
1
+ module Scruffy
2
+ class AreaLayer < GraphLayer
3
+ def draw(svg, coords, options={})
4
+ points_value = "0,#{height} #{stringify_coords(coords).join(' ')} #{width},#{height}"
5
+
6
+ # Experimental, for later user.
7
+ #
8
+ # svg.defs {
9
+ # svg.filter(:id => 'MyFilter', :filterUnits => 'userSpaceOnUse', :x => 0, :y => 0, :width => 200, :height => '120') {
10
+ # svg.feGaussianBlur(:in => 'SourceAlpha', :stdDeviation => 4, :result => 'blur')
11
+ # svg.feOffset(:in => 'blur', :dx => 4, :dy => 4, :result => 'offsetBlur')
12
+ # svg.feSpecularLighting( :in => 'blur', :surfaceScale => 5, :specularConstant => '.75',
13
+ # :specularExponent => 20, 'lighting-color' => '#bbbbbb',
14
+ # :result => 'specOut') {
15
+ # svg.fePointLight(:x => '-5000', :y => '-10000', :z => '20000')
16
+ # }
17
+ #
18
+ # svg.feComposite(:in => 'specOut', :in2 => 'SourceAlpha', :operator => 'in', :result => 'specOut')
19
+ # svg.feComposite(:in => 'sourceGraphic', :in2 => 'specOut', :operator => 'arithmetic',
20
+ # :k1 => 0, :k2 => 1, :k3 => 1, :k4 => 0, :result => 'litPaint')
21
+ #
22
+ # svg.feMerge {
23
+ # svg.feMergeNode(:in => 'offsetBlur')
24
+ # svg.feMergeNode(:in => 'litPaint')
25
+ # }
26
+ # }
27
+ # }
28
+
29
+ svg.polygon(:points => points_value, :fill => color.to_s, :stroke => color.to_s, :opacity => options[:opacity])
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,52 @@
1
+ module Scruffy
2
+ class AverageLayer < GraphLayer
3
+ def initialize(options = {})
4
+ super
5
+
6
+ @relevant_data = false
7
+ end
8
+ def draw(svg, coords, options = {})
9
+ svg.polyline( :points => coords.join(' '), :fill => 'none', :stroke => 'black',
10
+ 'stroke-width' => scaled(25), :opacity => '0.4')
11
+ end
12
+
13
+ def highest_point
14
+ nil
15
+ end
16
+
17
+ def lowest_point
18
+ nil
19
+ end
20
+
21
+ protected
22
+ def generate_coordinates(options = {})
23
+ key_layer = points.find { |layer| layer.relevant_data? }
24
+
25
+ options[:point_distance] = width / (key_layer.points.size - 1).to_f
26
+
27
+ coords = []
28
+
29
+ key_layer.points.each_with_index do |layer, idx|
30
+ sum, objects = points.inject([0, 0]) do |arr, elem|
31
+ if elem.relevant_data?
32
+ arr[0] += elem.points[idx]
33
+ arr[1] += 1
34
+ end
35
+ arr
36
+ end
37
+
38
+ average = sum / objects.to_f
39
+
40
+ x_coord = options[:point_distance] * idx
41
+
42
+ relative_percent = ((average == min_value) ? 0 : ((average - min_value) / (max_value - min_value).to_f))
43
+ y_coord = (height - (height * relative_percent))
44
+
45
+ coords << [x_coord, y_coord].join(',')
46
+ end
47
+
48
+ return coords
49
+ end
50
+
51
+ end
52
+ end
@@ -0,0 +1,28 @@
1
+ module Scruffy
2
+ class BarLayer < GraphLayer
3
+
4
+ def draw(svg, coords, options = {})
5
+ coords.each do |coord|
6
+ x, y, bar_height = (coord.first-(@bar_width * 0.5)), coord.last, (height - coord.last)
7
+
8
+ svg.rect( :x => x, :y => y, :width => @bar_width, :height => bar_height,
9
+ :fill => color.to_s, :stroke => color.to_s, :opacity => opacity )
10
+ end
11
+ end
12
+
13
+ protected
14
+ def generate_coordinates(options = {})
15
+ @bar_width = (width / points.size) * 0.9
16
+ options[:point_distance] = (width - (width / points.size)) / (points.size - 1).to_f
17
+
18
+ coords = (0...points.size).map do |idx|
19
+ x_coord = (options[:point_distance] * idx) + (width / points.size * 0.5)
20
+
21
+ relative_percent = ((points[idx] == min_value) ? 0 : ((points[idx] - min_value) / (max_value - min_value).to_f))
22
+ y_coord = (height - (height * relative_percent))
23
+ [x_coord, y_coord]
24
+ end
25
+ coords
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,120 @@
1
+ module Scruffy
2
+ # ==GraphLayer
3
+ #
4
+ # Author:: Brasten Sager
5
+ # Date:: August 5th, 2006
6
+ #
7
+ # GraphLayer contains the basic functionality needed by the various types of graphs. The GraphLayer
8
+ # class is responsible holding layer information such as the title and data points.
9
+ #
10
+ # When the graph is rendered, the graph renderer calls GraphLayer#render. GraphLayer#render sets up
11
+ # some standard information, and calculates the x,y coordinates of each data point. The draw() method,
12
+ # which should have been overridden by the current instance, is then called. The actual rendering of
13
+ # the graph takes place there.
14
+ #
15
+ # ====Create New Graph Types
16
+ #
17
+ # Assuming the information generated by GraphLayer is sufficient, you can create a new graph type
18
+ # simply by overriding the draw() method. See GraphLayer#draw for arguments.
19
+ #
20
+ class GraphLayer
21
+ attr_accessor :title
22
+ attr_accessor :points
23
+ attr_accessor :height, :width
24
+ attr_accessor :min_value, :max_value
25
+ attr_accessor :preferred_color, :color
26
+ attr_accessor :opacity
27
+ attr_accessor :resolver
28
+ attr_accessor :complexity
29
+ attr_accessor :relevant_data
30
+
31
+ # Returns a new GraphLayer.
32
+ #
33
+ # Options:
34
+ # title::
35
+ # points:: Array of data points
36
+ # color:: Color used to render this graph, overrides theme color.
37
+ # relevant_data:: Rarely used - indicates the data on this graph should not
38
+ # included in any graph data aggregations, such as averaging data points.
39
+ def initialize(options = {})
40
+ @title = options[:title] || ''
41
+ @points = options[:points] || []
42
+ @preferred_color = options[:color]
43
+ @relevant_data = options[:relevant_data] || true
44
+ end
45
+
46
+ # Builds SVG code for this graph using the provided Builder object.
47
+ # This method actually generates data needed by this graph, then passes the
48
+ # rendering responsibilities to GraphLayer#draw.
49
+ #
50
+ # svg:: a Builder object used to create SVG code.
51
+ def render(svg, options = {})
52
+ setup_variables(options)
53
+ coords = generate_coordinates(options)
54
+
55
+ draw(svg, coords, options)
56
+ end
57
+
58
+ # The method called by GraphLayer#draw to render the graph.
59
+ #
60
+ # svg:: a Builder object to use for creating SVG code.
61
+ # coords:: An array of coordinates relating to the graph's data points. ie: [[100, 120], [200, 140], [300, 40]]
62
+ # options:: Optional arguments.
63
+ def draw(svg, coords, options={})
64
+ # This is what the various graphs need to implement.
65
+ end
66
+
67
+ # Returns the value of relevant_data
68
+ def relevant_data?
69
+ @relevant_data
70
+ end
71
+
72
+ # The highest data point on this GraphLayer
73
+ def highest_point
74
+ points.sort.last
75
+ end
76
+
77
+ # The lowest data point on this GraphLayer
78
+ def lowest_point
79
+ points.sort.first
80
+ end
81
+
82
+ protected
83
+ def setup_variables(options = {}) # :nodoc:
84
+ @color = (preferred_color || options.delete(:color))
85
+ @resolver = options.delete(:resolver)
86
+ @width, @height = options.delete(:size)
87
+ @min_value, @max_value = options[:min_value], options[:max_value]
88
+ @opacity = options[:opacity]
89
+ @complexity = options[:complexity]
90
+ end
91
+
92
+ def generate_coordinates(options = {}) # :nodoc:
93
+ options[:point_distance] = width / (points.size - 1).to_f
94
+
95
+ coords = (0...points.size).map do |idx|
96
+ x_coord = options[:point_distance] * idx
97
+
98
+ relative_percent = ((points[idx] == min_value) ? 0 : ((points[idx] - min_value) / (max_value - min_value).to_f))
99
+ y_coord = (height - (height * relative_percent))
100
+
101
+ [x_coord, y_coord]
102
+ end
103
+
104
+ coords
105
+ end
106
+
107
+ # Returns a 'scaled' value of the given integer.
108
+ #
109
+ # Scaled adjusts the given point depending on the height of the graph, where 400px is considered 1:1.
110
+ #
111
+ # ie: On a graph that is 800px high, scaled(1) => 2. For 200px high, scaled(1) => 0.5, and so on...
112
+ def scaled(point)
113
+ resolver.to_point(point)
114
+ end
115
+
116
+ def stringify_coords(coords) # :nodoc:
117
+ coords.map { |c| c.join(',') }
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,7 @@
1
+ module Scruffy
2
+ class BlueScreenLayer < GraphLayer
3
+ def draw(svg, coords, options={})
4
+ svg.image('xlink:href' => 'http://www.nagilum.com/images/logo-large.png', :width => width, :height => height)
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,11 @@
1
+ module Scruffy
2
+ class LineLayer < GraphLayer
3
+ def draw(svg, coords, options={})
4
+ svg.polyline( :points => stringify_coords(coords).join(' '), :fill => 'none',
5
+ :stroke => color.to_s, 'stroke-width' => scaled(4) )
6
+
7
+ coords.each { |coord| svg.circle( :cx => coord.first, :cy => coord.last, :r => scaled(5),
8
+ :fill => color.to_s ) }
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,132 @@
1
+ module Scruffy
2
+ class StandardRenderer
3
+ GRAPH_VIEWPORT = { :x => 0.20, :y => 0.30, :width => 0.70, :height => 0.55 }
4
+ MARKER_ALIGNMENT = { :x => 0.17, :y => 0.31}
5
+ MARKERS = 5
6
+
7
+ def render(options = {})
8
+ title = options[:title]
9
+ theme = options[:theme]
10
+ layers = options[:layers]
11
+ size = options[:size]
12
+ graph_id = options[:graph_id] || 'scruffy_graph'
13
+
14
+
15
+ options[:complexity] ||= (global_complexity || :normal)
16
+ options[:resolver] ||= Scruffy::Resolver.new(options[:size].last)
17
+
18
+ svg = Builder::XmlMarkup.new(:indent => 2)
19
+ svg.instruct!
20
+ svg.instruct! 'DOCTYPE', 'svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd" type'
21
+ svg.svg(:xmlns => "http://www.w3.org/2000/svg", 'xmlns:xlink' => "http://www.w3.org/1999/xlink", :width => size[0], :height => size[1]) {
22
+ svg.g(:id => graph_id) {
23
+ render_background(svg, options)
24
+ render_markers(svg, options)
25
+ render_title(svg, options)
26
+ render_layers(svg, options)
27
+
28
+ }
29
+ }
30
+ svg.target!
31
+ end
32
+
33
+ protected
34
+ def render_background(svg, options)
35
+ fill = "#EEEEEE"
36
+ case options[:theme][:background]
37
+ when Symbol, String
38
+ fill = options[:theme][:background].to_s
39
+ when Array
40
+ fill = "url(#BackgroundGradient)"
41
+ svg.defs {
42
+ svg.linearGradient(:id=>'BackgroundGradient', :x1 => '0%', :y1 => '0%', :x2 => '0%', :y2 => '100%') {
43
+ svg.stop(:offset => '5%', 'stop-color' => options[:theme][:background][0])
44
+ svg.stop(:offset => '95%', 'stop-color' => options[:theme][:background][1])
45
+ }
46
+ }
47
+ when Proc
48
+ theme[:background].call(svg, options)
49
+ fill = nil
50
+ end
51
+
52
+ # Render background (maybe)
53
+ svg.rect(:width => "100%", :height => "100%", :x => "0", :y => "0", :fill => fill) unless fill.nil?
54
+ end
55
+
56
+ def render_layers(svg, options)
57
+ svg.g(:id => 'graphs', :transform => "translate(#{GRAPH_VIEWPORT[:x] * options[:size][0]},#{GRAPH_VIEWPORT[:y] * options[:size][1]})") {
58
+ options[:layers].each_with_index do |layer, idx|
59
+
60
+ layer_options = {}
61
+ layer_options[:index] = idx
62
+ layer_options[:color] = options[:theme][:colors].values[ (idx % options[:theme][:colors].size) ]
63
+ layer_options[:size] = [ GRAPH_VIEWPORT[:width] * options[:size][0], GRAPH_VIEWPORT[:height] * options[:size][1] ]
64
+ layer_options[:min_value] = options[:min_value]
65
+ layer_options[:max_value] = options[:max_value]
66
+ layer_options[:opacity] = (idx > 0) ? 0.6 : 1.0
67
+ layer_options[:resolver] = options[:resolver]
68
+ layer_options[:complexity] = options[:complexity]
69
+ layer_options[:theme] = options[:theme]
70
+
71
+ # puts options[:theme][:colors].values
72
+ # puts layer_options[:color]
73
+ # puts "#{idx} : #{options[:layers].size} == #{idx % options[:layers].size}"
74
+
75
+ svg.g {
76
+ layer.render(svg, layer_options)
77
+ }
78
+ end
79
+ }
80
+ end
81
+
82
+ def render_markers(svg, options)
83
+ svg.g(:id => 'markers', :transform => "translate(#{GRAPH_VIEWPORT[:x] * options[:size][0]},#{GRAPH_VIEWPORT[:y] * options[:size][1]})") {
84
+ (0...MARKERS).each do |idx|
85
+ marker = ((1 / (MARKERS - 1).to_f) * idx) * (options[:size].last * GRAPH_VIEWPORT[:height])
86
+ svg.line(:x1 => 0, :y1 => marker, :x2 => (GRAPH_VIEWPORT[:width] * options[:size].first), :y2 => marker, :style => "stroke: #{options[:theme][:marker].to_s}; stroke-width: #{options[:resolver].to_point(2)};")
87
+ end
88
+ }
89
+
90
+ svg.g(:id => 'marker-values', :transform => "translate(#{MARKER_ALIGNMENT[:x] * options[:size][0]},#{MARKER_ALIGNMENT[:y] * options[:size][1]})") {
91
+ (0...MARKERS).each do |idx|
92
+ marker = ((GRAPH_VIEWPORT[:height]) * options[:size].last) - ((1 / (MARKERS - 1).to_f) * idx) * (options[:size].last * GRAPH_VIEWPORT[:height])
93
+ marker_value = (options[:max_value] - options[:min_value]) * ((1 / (MARKERS - 1).to_f) * idx) + options[:min_value]
94
+ marker_value = options[:marker_transformer].route_transform(marker_value, idx, options) if options[:marker_transformer]
95
+
96
+ svg.text(marker_value.to_s, :x => 0, :y => marker, :style => "font-size: #{options[:resolver].to_point(18)}px; fill: #{((options.delete(:marker_color_override) || options[:theme][:marker]) || 'white').to_s}; text-anchor: end;")
97
+ end
98
+ }
99
+
100
+ unless options[:point_markers].nil?
101
+ svg.g(:id => 'point-markers', :transform => "translate(#{GRAPH_VIEWPORT[:x] * options[:size][0]},#{(GRAPH_VIEWPORT[:y] + GRAPH_VIEWPORT[:height]) * options[:size][1]})") {
102
+ point_distance = (options[:size].first * GRAPH_VIEWPORT[:width]) / (options[:point_markers].size - 1).to_f
103
+
104
+ (0...options[:point_markers].size).map do |idx|
105
+ x_coord = point_distance * idx
106
+ svg.text(options[:point_markers][idx], :x => x_coord, :y => options[:resolver].to_point(20),
107
+ 'font-size' => options[:resolver].to_point(16),
108
+ :fill => (options[:theme][:marker] || 'white').to_s,
109
+ 'text-anchor' => 'middle') unless options[:point_markers][idx].nil?
110
+ end
111
+ }
112
+ end
113
+ end
114
+
115
+ def render_title(svg, options)
116
+ svg.g(:transform => "translate(0, #{options[:size][1] * 0.09})") {
117
+ svg.text( options[:title], :x => options[:size][0] / 2, :y => 0,
118
+ 'font-size' => options[:resolver].to_point(28), 'text-anchor' => 'middle',
119
+ :fill => 'white')
120
+ }
121
+ end
122
+
123
+ private
124
+ def global_complexity
125
+ if Kernel.const_defined? "SCRUFFY_COMPLEXITY"
126
+ SCRUFFY_COMPLEXITY
127
+ else
128
+ nil
129
+ end
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,14 @@
1
+ module Scruffy
2
+ class Resolver
3
+ BASE_SIZE = 400
4
+ attr_accessor :actual_size
5
+
6
+ def initialize(actual_size)
7
+ @actual_size = actual_size
8
+ end
9
+
10
+ def to_point(size)
11
+ actual_size * (size / BASE_SIZE.to_f)
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,26 @@
1
+ module Scruffy
2
+ module Theme
3
+ KEYNOTE = {
4
+ :background => [:black, '#4A465A'],
5
+ :marker => :white,
6
+ :colors => {:blue => '#6886B4',
7
+ :green => '#72AE6E', :red => '#D1695E',
8
+ :purple => '#8A6EAF', :orange => '#EFAA43'}
9
+ }
10
+
11
+
12
+ MEPHISTO = {
13
+ :background => ['#101010', '#999977'],
14
+ :marker => :white,
15
+ :colors => {:orange => '#DD3300', :blue => '#66AABB', :green => '#225533',
16
+ :red => '#992200'}
17
+ }
18
+
19
+ RUBY_BLOG = {
20
+ :background => ['#670A0A', '#831515'],
21
+ :marker => '#DBD1C1',
22
+ :colors => {:teal => '#007777', :purple => '#444477', :pink => '#994444',
23
+ :green => '#77FFBB', :orange => '#D75A20'}
24
+ }
25
+ end
26
+ end
@@ -0,0 +1,73 @@
1
+ module Scruffy
2
+ module Transformer
3
+ class Base
4
+ def route_transform(target, idx, options = {})
5
+ args = [target, idx, options]
6
+ if respond_to?(:transform)
7
+ send :transform, *args[0...self.method(:transform).arity]
8
+ elsif respond_to?(:transform!)
9
+ send :transform!, *args[0...self.method(:transform).arity]
10
+ target
11
+ else
12
+ raise NameError, "Transformer subclass must container either a transform() method or transform!() method."
13
+ end
14
+ end
15
+
16
+ protected
17
+ def number_with_precision(number, precision=3)
18
+ sprintf("%01.#{precision}f", number)
19
+ end
20
+ end
21
+
22
+ class Currency < Base
23
+ def initialize(options = {})
24
+ @precision = options[:precision] || 2
25
+ @unit = options[:unit] || '$'
26
+ @separator = options[:separator] || '.'
27
+ @delimiter = options[:delimiter] || ','
28
+ @negative_color = options[:negative_color]
29
+ @special_negatives = options[:special_negatives] || false
30
+ end
31
+
32
+ def transform(target, idx, options)
33
+ @separator = "" unless @precision > 0
34
+ begin
35
+ parts = number_with_precision(target, @precision).split('.')
36
+ if @special_negatives && (target.to_f < 0)
37
+ number = "(" + @unit + parts[0].to_i.abs.to_s.gsub(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{@delimiter}") + @separator + parts[1].to_s + ")"
38
+ else
39
+ number = @unit + parts[0].to_s.gsub(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{@delimiter}") + @separator + parts[1].to_s
40
+ end
41
+ if (target.to_f < 0) && @negative_color
42
+ options[:marker_color_override] = @negative_color
43
+ end
44
+ number
45
+ rescue
46
+ target
47
+ end
48
+ end
49
+ end
50
+
51
+ class Percentage < Base
52
+ def initialize(options = {})
53
+ @precision = options[:precision] || 3
54
+ @separator = options[:separator] || '.'
55
+ end
56
+
57
+ def transform(target)
58
+ begin
59
+ number = number_with_precision(target, @precision)
60
+ parts = number.split('.')
61
+ if parts.at(1).nil?
62
+ parts[0] + "%"
63
+ else
64
+ parts[0] + @separator + parts[1].to_s + "%"
65
+ end
66
+ rescue
67
+ target
68
+ end
69
+ end
70
+ end
71
+
72
+ end
73
+ end
@@ -0,0 +1,3 @@
1
+ module Scruffy
2
+ VERSION = '0.0.9'
3
+ end
@@ -0,0 +1,56 @@
1
+ require 'lib/scruffy'
2
+
3
+ context "A new Scruffy::Graph" do
4
+ setup do
5
+ @graph = Scruffy::Graph.new
6
+ end
7
+
8
+ specify "should have a blank title" do
9
+ @graph.title.should_be.nil
10
+ end
11
+
12
+ specify "should be set to the keynote theme" do
13
+ @graph.theme.should_equal Scruffy::Theme::KEYNOTE
14
+ end
15
+
16
+ specify "should have zero layers" do
17
+ @graph.should_have(0).layers
18
+ end
19
+
20
+ specify "should not have a default layer type" do
21
+ @graph.default_type.should_be.nil
22
+ end
23
+
24
+ specify "should not have any point markers (x-axis values)" do
25
+ @graph.point_markers.should_be.nil
26
+ end
27
+
28
+ specify "should not have a marker transformer" do
29
+ @graph.marker_transformer.should_be.nil
30
+ end
31
+
32
+ specify "should use a StandardRenderer" do
33
+ @graph.renderer.should_be_instance_of Scruffy::StandardRenderer
34
+ end
35
+
36
+ specify "should accept a new title" do
37
+ @graph.title = "New Title"
38
+ @graph.title.should_equal "New Title"
39
+ end
40
+
41
+ specify "should accept a new theme" do
42
+ @graph.theme = Scruffy::Theme::MEPHISTO
43
+ @graph.theme.should_equal Scruffy::Theme::MEPHISTO
44
+ end
45
+
46
+ specify "should accept a new default type" do
47
+ @graph.default_type = :line
48
+ @graph.default_type.should_equal :line
49
+ end
50
+
51
+ specify "should accept new point markers" do
52
+ markers = ['Jan', 'Feb', 'Mar']
53
+ @graph.point_markers = markers
54
+ @graph.point_markers.should_equal markers
55
+ end
56
+ end
metadata ADDED
@@ -0,0 +1,64 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.9.0
3
+ specification_version: 1
4
+ name: scruffy
5
+ version: !ruby/object:Gem::Version
6
+ version: 0.0.9
7
+ date: 2006-08-10 00:00:00 -07:00
8
+ summary: A powerful, clean graphing library for Ruby.
9
+ require_paths:
10
+ - lib
11
+ email: brasten@nagilum.com
12
+ homepage: http://scruffy.rubyforge.org
13
+ rubyforge_project: scruffy
14
+ description: Scruffy is a Ruby library for generating powerful graphs. It is based on SVG, allowing for powerful, clean code, as well as a good foundation for future features.
15
+ autorequire: scruffy
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: true
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ - - ">"
22
+ - !ruby/object:Gem::Version
23
+ version: 0.0.0
24
+ version:
25
+ platform: ruby
26
+ signing_key:
27
+ cert_chain:
28
+ post_install_message:
29
+ authors:
30
+ - Brasten Sager
31
+ files:
32
+ - CHANGES
33
+ - MIT-LICENSE
34
+ - Rakefile
35
+ - README
36
+ - lib/scruffy.rb
37
+ - lib/scruffy/graph.rb
38
+ - lib/scruffy/resolver.rb
39
+ - lib/scruffy/themes.rb
40
+ - lib/scruffy/transformer.rb
41
+ - lib/scruffy/version.rb
42
+ - lib/scruffy/graph_layers/all_smiles.rb
43
+ - lib/scruffy/graph_layers/area.rb
44
+ - lib/scruffy/graph_layers/average.rb
45
+ - lib/scruffy/graph_layers/bar.rb
46
+ - lib/scruffy/graph_layers/base.rb
47
+ - lib/scruffy/graph_layers/blue_screen.rb
48
+ - lib/scruffy/graph_layers/line.rb
49
+ - lib/scruffy/renderers/standard.rb
50
+ - spec/graph_spec.rb
51
+ test_files: []
52
+
53
+ rdoc_options: []
54
+
55
+ extra_rdoc_files: []
56
+
57
+ executables: []
58
+
59
+ extensions: []
60
+
61
+ requirements: []
62
+
63
+ dependencies: []
64
+