ramhoj-scruffy 0.2.6
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/README.txt +66 -0
- data/lib/scruffy.rb +25 -0
- data/lib/scruffy/components.rb +21 -0
- data/lib/scruffy/components/background.rb +24 -0
- data/lib/scruffy/components/base.rb +57 -0
- data/lib/scruffy/components/data_markers.rb +26 -0
- data/lib/scruffy/components/graphs.rb +48 -0
- data/lib/scruffy/components/grid.rb +19 -0
- data/lib/scruffy/components/label.rb +17 -0
- data/lib/scruffy/components/legend.rb +105 -0
- data/lib/scruffy/components/style_info.rb +22 -0
- data/lib/scruffy/components/title.rb +19 -0
- data/lib/scruffy/components/value_markers.rb +32 -0
- data/lib/scruffy/components/viewport.rb +37 -0
- data/lib/scruffy/formatters.rb +213 -0
- data/lib/scruffy/graph.rb +190 -0
- data/lib/scruffy/graph_state.rb +24 -0
- data/lib/scruffy/helpers.rb +12 -0
- data/lib/scruffy/helpers/canvas.rb +41 -0
- data/lib/scruffy/helpers/layer_container.rb +95 -0
- data/lib/scruffy/helpers/meta.rb +5 -0
- data/lib/scruffy/helpers/point_container.rb +70 -0
- data/lib/scruffy/layers.rb +24 -0
- data/lib/scruffy/layers/all_smiles.rb +137 -0
- data/lib/scruffy/layers/area.rb +46 -0
- data/lib/scruffy/layers/average.rb +67 -0
- data/lib/scruffy/layers/bar.rb +52 -0
- data/lib/scruffy/layers/base.rb +191 -0
- data/lib/scruffy/layers/line.rb +46 -0
- data/lib/scruffy/layers/pie.rb +123 -0
- data/lib/scruffy/layers/pie_slice.rb +119 -0
- data/lib/scruffy/layers/scatter.rb +21 -0
- data/lib/scruffy/layers/sparkline_bar.rb +39 -0
- data/lib/scruffy/layers/stacked.rb +87 -0
- data/lib/scruffy/rasterizers.rb +14 -0
- data/lib/scruffy/rasterizers/batik_rasterizer.rb +39 -0
- data/lib/scruffy/rasterizers/rmagick_rasterizer.rb +27 -0
- data/lib/scruffy/renderers.rb +22 -0
- data/lib/scruffy/renderers/base.rb +93 -0
- data/lib/scruffy/renderers/cubed.rb +44 -0
- data/lib/scruffy/renderers/cubed3d.rb +53 -0
- data/lib/scruffy/renderers/empty.rb +22 -0
- data/lib/scruffy/renderers/pie.rb +20 -0
- data/lib/scruffy/renderers/reversed.rb +17 -0
- data/lib/scruffy/renderers/sparkline.rb +10 -0
- data/lib/scruffy/renderers/split.rb +48 -0
- data/lib/scruffy/renderers/standard.rb +36 -0
- data/lib/scruffy/themes.rb +156 -0
- data/lib/scruffy/version.rb +3 -0
- data/spec/output/array.svg +47 -0
- data/spec/output/hash.svg +47 -0
- data/spec/scruffy/graph_spec.rb +175 -0
- data/spec/scruffy/layers/base_spec.rb +30 -0
- data/spec/spec_helper.rb +8 -0
- metadata +155 -0
data/README.txt
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
= scruffy, unofficial release
|
2
|
+
|
3
|
+
This is a fork from based on the official 0.2.5 release. See below for
|
4
|
+
further a longer description.
|
5
|
+
|
6
|
+
== DESCRIPTION:
|
7
|
+
|
8
|
+
* scruffy.rubyforge.org
|
9
|
+
|
10
|
+
Author:: Brasten Sager (brasten@nagilum.com)
|
11
|
+
Date:: July 8, 2008
|
12
|
+
Release:: 0.2.5
|
13
|
+
|
14
|
+
Scruffy is a Ruby library for generating high quality, good looking graphs. It is designed
|
15
|
+
to be easy to use and highly customizable.
|
16
|
+
|
17
|
+
For basic usage instructions, refer to the documentation for Scruffy::Graph.
|
18
|
+
|
19
|
+
|
20
|
+
== FORK DESCRIPTION
|
21
|
+
|
22
|
+
* http://github.com/delano/scruffy/
|
23
|
+
|
24
|
+
Author:: Delano Mandelbaum (delano@solutious.com)
|
25
|
+
Author:: Kalin Harvey
|
26
|
+
Date:: December 12, 2008
|
27
|
+
|
28
|
+
We love scruffy. Our motivation for creating a forking is to make it useful for hi-resolution
|
29
|
+
graphs and charts. We would love to get our changes in to the official release but until
|
30
|
+
that time they will be available at the GitHub URI above.
|
31
|
+
|
32
|
+
CHANGES.txt contains everything we've been up to.
|
33
|
+
|
34
|
+
|
35
|
+
== FEATURES
|
36
|
+
|
37
|
+
* Renders to SVG or bitmap (PNG, JPG)
|
38
|
+
|
39
|
+
== PROBLEMS:
|
40
|
+
|
41
|
+
* 0.2.3 version has missing legend text when rendering to bitmap. This is strange because the text is there in the SVG before it goes to RMagick.
|
42
|
+
|
43
|
+
== SYNOPSIS:
|
44
|
+
|
45
|
+
graph = Scruffy::Graph.new
|
46
|
+
graph.title = "Sample Line Graph"
|
47
|
+
graph.renderer = Scruffy::Renderers::Standard.new
|
48
|
+
|
49
|
+
graph.add :line, 'Example', [20, 100, 70, 30, 106]
|
50
|
+
|
51
|
+
graph.render :to => "line_test.svg"
|
52
|
+
graph.render :width => 300, :height => 200,
|
53
|
+
:to => "line_test.png", :as => 'png'
|
54
|
+
|
55
|
+
== REQUIREMENTS:
|
56
|
+
|
57
|
+
* Needs RMagick and Magic installed, if you wish to render to bitmap.
|
58
|
+
|
59
|
+
== INSTALL:
|
60
|
+
|
61
|
+
* sudo gem install scruffy
|
62
|
+
|
63
|
+
|
64
|
+
== LICENSE:
|
65
|
+
|
66
|
+
See Licence.txt
|
data/lib/scruffy.rb
ADDED
@@ -0,0 +1,25 @@
|
|
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::Layers::Base.
|
11
|
+
module Scruffy
|
12
|
+
end
|
13
|
+
|
14
|
+
require 'builder'
|
15
|
+
|
16
|
+
require 'scruffy/helpers'
|
17
|
+
require 'scruffy/graph_state'
|
18
|
+
require 'scruffy/graph'
|
19
|
+
require 'scruffy/themes'
|
20
|
+
require 'scruffy/version'
|
21
|
+
require 'scruffy/formatters'
|
22
|
+
require 'scruffy/rasterizers'
|
23
|
+
require 'scruffy/layers'
|
24
|
+
require 'scruffy/components'
|
25
|
+
require 'scruffy/renderers'
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# ===Scruffy Components
|
2
|
+
#
|
3
|
+
# Author:: Brasten Sager
|
4
|
+
# Date:: August 16th, 2006
|
5
|
+
#
|
6
|
+
# Components make up the visual elements of a Scruffy graph.
|
7
|
+
#
|
8
|
+
# For examples, see Scruffy::Components::Base.
|
9
|
+
module Scruffy::Components; end
|
10
|
+
|
11
|
+
require 'scruffy/components/base'
|
12
|
+
require 'scruffy/components/title'
|
13
|
+
require 'scruffy/components/background'
|
14
|
+
require 'scruffy/components/graphs'
|
15
|
+
require 'scruffy/components/grid'
|
16
|
+
require 'scruffy/components/value_markers'
|
17
|
+
require 'scruffy/components/data_markers'
|
18
|
+
require 'scruffy/components/legend'
|
19
|
+
require 'scruffy/components/style_info'
|
20
|
+
require 'scruffy/components/viewport'
|
21
|
+
require 'scruffy/components/label'
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Scruffy
|
2
|
+
module Components
|
3
|
+
class Background < Base
|
4
|
+
def draw(svg, bounds, options={})
|
5
|
+
fill = "#EEEEEE"
|
6
|
+
case options[:theme].background
|
7
|
+
when Symbol, String
|
8
|
+
fill = options[:theme].background.to_s
|
9
|
+
when Array
|
10
|
+
fill = "url(#BackgroundGradient)"
|
11
|
+
svg.defs {
|
12
|
+
svg.linearGradient(:id=>'BackgroundGradient', :x1 => '0%', :y1 => '0%', :x2 => '0%', :y2 => '100%') {
|
13
|
+
svg.stop(:offset => '5%', 'stop-color' => options[:theme].background[0])
|
14
|
+
svg.stop(:offset => '95%', 'stop-color' => options[:theme].background[1])
|
15
|
+
}
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
# Render background (maybe)
|
20
|
+
svg.rect(:width => bounds[:width], :height => bounds[:height], :x => "0", :y => "0", :fill => fill) unless fill.nil?
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Scruffy
|
2
|
+
module Components
|
3
|
+
# ===Scruffy::Components::Base
|
4
|
+
#
|
5
|
+
# Common attributes for all components, and a standard render method
|
6
|
+
# that calls draw after setting up the drawing transformations.
|
7
|
+
class Base
|
8
|
+
attr_reader :id
|
9
|
+
|
10
|
+
# In terms of percentages: [10, 10] == 10% by 10%
|
11
|
+
attr_accessor :position
|
12
|
+
attr_accessor :size
|
13
|
+
attr_accessor :options
|
14
|
+
attr_accessor :visible
|
15
|
+
|
16
|
+
# Options:
|
17
|
+
# stroke_width:: numeric value for width of line (0.1 - 10, default: 1)
|
18
|
+
def initialize(id, options = {})
|
19
|
+
@id = id.to_sym
|
20
|
+
@position = options[:position] || [0, 0]
|
21
|
+
@size = options[:size] || [100, 100]
|
22
|
+
@visible = options[:visible] || true
|
23
|
+
|
24
|
+
@options = options
|
25
|
+
end
|
26
|
+
|
27
|
+
def render(svg, bounds, options={})
|
28
|
+
if @visible
|
29
|
+
unless bounds.nil?
|
30
|
+
@render_height = bounds[:height]
|
31
|
+
|
32
|
+
svg.g(:id => id.to_s,
|
33
|
+
:transform => "translate(#{bounds.delete(:x)}, #{bounds.delete(:y)})") {
|
34
|
+
|
35
|
+
draw(svg, bounds, options.merge(@options))
|
36
|
+
}
|
37
|
+
else
|
38
|
+
process(svg, options.merge(@options))
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def draw(svg, bounds, options={})
|
44
|
+
# Override this if visual component
|
45
|
+
end
|
46
|
+
|
47
|
+
def process(svg, options={})
|
48
|
+
# Override this NOT a visual component
|
49
|
+
end
|
50
|
+
|
51
|
+
protected
|
52
|
+
def relative(pct)
|
53
|
+
@render_height * ( pct / 100.to_f )
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Scruffy
|
2
|
+
module Components
|
3
|
+
|
4
|
+
class DataMarkers < Base
|
5
|
+
|
6
|
+
def draw(svg, bounds, options={})
|
7
|
+
unless options[:point_markers].nil?
|
8
|
+
point_distance = bounds[:width] / (options[:point_markers].size - 1).to_f
|
9
|
+
|
10
|
+
(0...options[:point_markers].size).map do |idx|
|
11
|
+
x_coord = point_distance * idx
|
12
|
+
svg.text(options[:point_markers][idx],
|
13
|
+
:x => x_coord,
|
14
|
+
:y => bounds[:height],
|
15
|
+
'font-size' => relative(90),
|
16
|
+
'font-family' => options[:theme].font_family,
|
17
|
+
:fill => (options[:theme].marker || 'white').to_s,
|
18
|
+
'text-anchor' => 'middle') unless options[:point_markers][idx].nil?
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end # draw
|
22
|
+
|
23
|
+
end # class
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Scruffy
|
2
|
+
module Components
|
3
|
+
|
4
|
+
# Component for displaying Graphs layers.
|
5
|
+
#
|
6
|
+
# Is passed all graph layers from the Graph object.
|
7
|
+
#
|
8
|
+
# (This may change as the capability for Graph filtering and such fills out.)
|
9
|
+
class Graphs < Base
|
10
|
+
STACKED_OPACITY = 0.85;
|
11
|
+
|
12
|
+
def draw(svg, bounds, options={})
|
13
|
+
# If Graph is limited to a category, reject layers outside of it's scope.
|
14
|
+
applicable_layers = options[:layers].reject do |l|
|
15
|
+
if @options[:only]
|
16
|
+
(l.options[:category].nil? && l.options[:categories].nil?) ||
|
17
|
+
(!l.options[:category].nil? && l.options[:category] != @options[:only]) ||
|
18
|
+
(!l.options[:categories].nil? && !l.options[:categories].include?(@options[:only]))
|
19
|
+
else
|
20
|
+
false
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
applicable_layers.each_with_index do |layer, idx|
|
25
|
+
layer_options = {}
|
26
|
+
layer_options[:index] = idx
|
27
|
+
layer_options[:min_value] = options[:min_value]
|
28
|
+
layer_options[:max_value] = options[:max_value]
|
29
|
+
layer_options[:complexity] = options[:complexity]
|
30
|
+
layer_options[:size] = [bounds[:width], bounds[:height]]
|
31
|
+
layer_options[:color] = layer.preferred_color || layer.color || options[:theme].next_color
|
32
|
+
layer_options[:opacity] = opacity_for(idx)
|
33
|
+
layer_options[:theme] = options[:theme]
|
34
|
+
|
35
|
+
svg.g(:id => "component_#{id}_graph_#{idx}", :class => 'graph_layer') {
|
36
|
+
layer.render(svg, layer_options)
|
37
|
+
}
|
38
|
+
end # applicable_layers
|
39
|
+
end # draw
|
40
|
+
|
41
|
+
protected
|
42
|
+
def opacity_for(idx)
|
43
|
+
(idx == 0) ? 1.0 : (@options[:stacked_opacity] || STACKED_OPACITY)
|
44
|
+
end
|
45
|
+
|
46
|
+
end #class
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Scruffy
|
2
|
+
module Components
|
3
|
+
class Grid < Base
|
4
|
+
attr_accessor :markers
|
5
|
+
|
6
|
+
def draw(svg, bounds, options={})
|
7
|
+
markers = (options[:markers] || self.markers) || 5
|
8
|
+
|
9
|
+
stroke_width = options[:stroke_width]
|
10
|
+
|
11
|
+
(0...markers).each do |idx|
|
12
|
+
marker = ((1 / (markers - 1).to_f) * idx) * bounds[:height]
|
13
|
+
svg.line(:x1 => 0, :y1 => marker, :x2 => bounds[:width], :y2 => marker, :style => "stroke: #{options[:theme].marker.to_s}; stroke-width: #{stroke_width};")
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Scruffy
|
2
|
+
module Components
|
3
|
+
class Label < Base
|
4
|
+
def draw(svg, bounds, options={})
|
5
|
+
svg.text(@options[:text],
|
6
|
+
:class => 'text',
|
7
|
+
:x => (bounds[:width] / 2),
|
8
|
+
:y => bounds[:height],
|
9
|
+
'font-size' => relative(100),
|
10
|
+
'font-family' => options[:theme].font_family,
|
11
|
+
:fill => options[:theme].marker,
|
12
|
+
:stroke => 'none', 'stroke-width' => '0',
|
13
|
+
'text-anchor' => (@options[:text_anchor] || 'middle'))
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
module Scruffy::Components
|
2
|
+
|
3
|
+
class Legend < Base
|
4
|
+
FONT_SIZE = 80
|
5
|
+
|
6
|
+
def draw(svg, bounds, options={})
|
7
|
+
vertical = options[:vertical_legend]
|
8
|
+
legend_info = relevant_legend_info(options[:layers])
|
9
|
+
@line_height, x, y, size = 0
|
10
|
+
if vertical
|
11
|
+
set_line_height = 0.08 * bounds[:height]
|
12
|
+
@line_height = bounds[:height] / legend_info.length
|
13
|
+
@line_height = set_line_height if @line_height >
|
14
|
+
set_line_height
|
15
|
+
else
|
16
|
+
set_line_height = 0.90 * bounds[:height]
|
17
|
+
@line_height = set_line_height
|
18
|
+
end
|
19
|
+
|
20
|
+
text_height = @line_height * FONT_SIZE / 100
|
21
|
+
# #TODO how does this related to @points?
|
22
|
+
active_width, points = layout(legend_info, vertical)
|
23
|
+
|
24
|
+
offset = (bounds[:width] - active_width) / 2 # Nudge over a bit for true centering
|
25
|
+
|
26
|
+
# Render Legend
|
27
|
+
points.each_with_index do |point, idx|
|
28
|
+
if vertical
|
29
|
+
x = 0
|
30
|
+
y = point
|
31
|
+
size = @line_height * 0.5
|
32
|
+
else
|
33
|
+
x = offset + point
|
34
|
+
y = 0
|
35
|
+
size = relative(50)
|
36
|
+
end
|
37
|
+
|
38
|
+
# "#{x} #{y} #{@line_height} #{size}"
|
39
|
+
|
40
|
+
svg.rect(:x => x,
|
41
|
+
:y => y,
|
42
|
+
:width => size,
|
43
|
+
:height => size,
|
44
|
+
:fill => legend_info[idx][:color])
|
45
|
+
|
46
|
+
svg.text(legend_info[idx][:title],
|
47
|
+
:x => x + @line_height,
|
48
|
+
:y => y + text_height * 0.75,
|
49
|
+
'font-size' => text_height,
|
50
|
+
'font-family' => options[:theme].font_family,
|
51
|
+
:style => "color: #{options[:theme].marker || 'white'}",
|
52
|
+
:fill => (options[:theme].marker || 'white'))
|
53
|
+
end
|
54
|
+
end # draw
|
55
|
+
|
56
|
+
protected
|
57
|
+
# Collects Legend Info from the provided Layers.
|
58
|
+
#
|
59
|
+
# Automatically filters by legend's categories.
|
60
|
+
def relevant_legend_info(layers, categories=(@options[:category] ? [@options[:category]] : @options[:categories]))
|
61
|
+
legend_info = layers.inject([]) do |arr, layer|
|
62
|
+
if categories.nil? ||
|
63
|
+
(categories.include?(layer.options[:category]) ||
|
64
|
+
(layer.options[:categories] && (categories & layer.options[:categories]).size > 0) )
|
65
|
+
|
66
|
+
data = layer.legend_data
|
67
|
+
arr << data if data.is_a?(Hash)
|
68
|
+
arr = arr + data if data.is_a?(Array)
|
69
|
+
end
|
70
|
+
arr
|
71
|
+
end
|
72
|
+
end # relevant_legend_info
|
73
|
+
|
74
|
+
# Returns an array consisting of the total width needed by the legend
|
75
|
+
# information, as well as an array of @x-coords for each element. If
|
76
|
+
# vertical, then these are @y-coords, and @x is 0
|
77
|
+
#
|
78
|
+
# ie: [200, [0, 50, 100, 150]]
|
79
|
+
def layout(legend_info_array, vertical = false)
|
80
|
+
if vertical
|
81
|
+
longest = 0
|
82
|
+
legend_info_array.each {|elem|
|
83
|
+
cur_length = relative(50) * elem[:title].length
|
84
|
+
longest = longest < cur_length ? cur_length : longest
|
85
|
+
}
|
86
|
+
y_positions = []
|
87
|
+
(0..legend_info_array.length - 1).each {|y|
|
88
|
+
y_positions << y * @line_height
|
89
|
+
}
|
90
|
+
[longest, y_positions]
|
91
|
+
else
|
92
|
+
legend_info_array.inject([0, []]) do |enum, elem|
|
93
|
+
enum[0] += (relative(50) * 2) if enum.first != 0 # Add spacer between elements
|
94
|
+
enum[1] << enum.first # Add location to points
|
95
|
+
enum[0] += relative(50) # Add room for color box
|
96
|
+
enum[0] += (relative(50) * elem[:title].length) # Add room for text
|
97
|
+
|
98
|
+
[enum.first, enum.last]
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
end # class Legend
|
104
|
+
|
105
|
+
end # Scruffy::Components
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Scruffy
|
2
|
+
module Components
|
3
|
+
# Component used for adding CSS styling to SVG graphs.
|
4
|
+
#
|
5
|
+
# In hindsight, ImageMagick and Mozilla SVG's handling of CSS styling is
|
6
|
+
# extremely inconsistant, so use this at your own risk.
|
7
|
+
class StyleInfo < Base
|
8
|
+
def initialize(*args)
|
9
|
+
super
|
10
|
+
|
11
|
+
@visible = false
|
12
|
+
end
|
13
|
+
def process(svg, options={})
|
14
|
+
svg.defs {
|
15
|
+
svg.style(:type => "text/css") {
|
16
|
+
svg.cdata!("\n#{options[:selector]} {\n #{options[:style]}\n}\n")
|
17
|
+
}
|
18
|
+
}
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Scruffy
|
2
|
+
module Components
|
3
|
+
class Title < Base
|
4
|
+
def draw(svg, bounds, options={})
|
5
|
+
if options[:title]
|
6
|
+
svg.text(options[:title],
|
7
|
+
:class => 'title',
|
8
|
+
:x => (bounds[:width] / 2),
|
9
|
+
:y => bounds[:height],
|
10
|
+
'font-size' => relative(100),
|
11
|
+
'font-family' => options[:theme].font_family,
|
12
|
+
:fill => options[:theme].marker,
|
13
|
+
:stroke => 'none', 'stroke-width' => '0',
|
14
|
+
'text-anchor' => (@options[:text_anchor] || 'middle'))
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|