scruffy 0.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+