scruffy 0.0.9
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES +11 -0
- data/MIT-LICENSE +20 -0
- data/README +9 -0
- data/Rakefile +103 -0
- data/lib/scruffy.rb +23 -0
- data/lib/scruffy/graph.rb +182 -0
- data/lib/scruffy/graph_layers/all_smiles.rb +107 -0
- data/lib/scruffy/graph_layers/area.rb +32 -0
- data/lib/scruffy/graph_layers/average.rb +52 -0
- data/lib/scruffy/graph_layers/bar.rb +28 -0
- data/lib/scruffy/graph_layers/base.rb +120 -0
- data/lib/scruffy/graph_layers/blue_screen.rb +7 -0
- data/lib/scruffy/graph_layers/line.rb +11 -0
- data/lib/scruffy/renderers/standard.rb +132 -0
- data/lib/scruffy/resolver.rb +14 -0
- data/lib/scruffy/themes.rb +26 -0
- data/lib/scruffy/transformer.rb +73 -0
- data/lib/scruffy/version.rb +3 -0
- data/spec/graph_spec.rb +56 -0
- metadata +64 -0
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,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,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
|
data/spec/graph_spec.rb
ADDED
@@ -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
|
+
|