svg-graph 1.0.0

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.
@@ -0,0 +1,15 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'rubygems'
4
+ require 'hoe'
5
+ $:.unshift("./lib")
6
+ require 'svggraph'
7
+ Hoe.spec 'svg-graph' do |p|
8
+ p.version=SVG::Graph::VERSION
9
+ p.developer('Sean Russell', 'ser_AT_germane-software.com')
10
+ p.developer('Claudio Bustos', 'clbustos_AT_gmail.com')
11
+ p.rubyforge_name = 'ruby-statsample' # if different than 'svg_graph'
12
+ p.remote_rdoc_dir = 'svg-graph'
13
+ end
14
+
15
+ # vim: syntax=ruby
@@ -0,0 +1,148 @@
1
+ require 'rexml/document'
2
+ require 'SVG/Graph/Graph'
3
+ require 'SVG/Graph/BarBase'
4
+
5
+ module SVG
6
+ module Graph
7
+ # === Create presentation quality SVG bar graphs easily
8
+ #
9
+ # = Synopsis
10
+ #
11
+ # require 'SVG/Graph/Bar'
12
+ #
13
+ # fields = %w(Jan Feb Mar);
14
+ # data_sales_02 = [12, 45, 21]
15
+ #
16
+ # graph = SVG::Graph::Bar.new(
17
+ # :height => 500,
18
+ # :width => 300,
19
+ # :fields => fields
20
+ # )
21
+ #
22
+ # graph.add_data(
23
+ # :data => data_sales_02,
24
+ # :title => 'Sales 2002'
25
+ # )
26
+ #
27
+ # print "Content-type: image/svg+xml\r\n\r\n"
28
+ # print graph.burn
29
+ #
30
+ # = Description
31
+ #
32
+ # This object aims to allow you to easily create high quality
33
+ # SVG[http://www.w3c.org/tr/svg bar graphs. You can either use the default
34
+ # style sheet or supply your own. Either way there are many options which
35
+ # can be configured to give you control over how the graph is generated -
36
+ # with or without a key, data elements at each point, title, subtitle etc.
37
+ #
38
+ # = Notes
39
+ #
40
+ # The default stylesheet handles upto 12 data sets, if you
41
+ # use more you must create your own stylesheet and add the
42
+ # additional settings for the extra data sets. You will know
43
+ # if you go over 12 data sets as they will have no style and
44
+ # be in black.
45
+ #
46
+ # = Examples
47
+ #
48
+ # * http://germane-software.com/repositories/public/SVG/test/test.rb
49
+ #
50
+ # = See also
51
+ #
52
+ # * SVG::Graph::Graph
53
+ # * SVG::Graph::BarHorizontal
54
+ # * SVG::Graph::Line
55
+ # * SVG::Graph::Pie
56
+ # * SVG::Graph::Plot
57
+ # * SVG::Graph::TimeSeries
58
+ class Bar < BarBase
59
+ include REXML
60
+
61
+ # See Graph::initialize and BarBase::set_defaults
62
+ def set_defaults
63
+ super
64
+ self.top_align = self.top_font = 1
65
+ end
66
+
67
+ protected
68
+
69
+ def get_x_labels
70
+ @config[:fields]
71
+ end
72
+
73
+ def get_y_labels
74
+ maxvalue = max_value
75
+ minvalue = min_value
76
+ range = maxvalue - minvalue
77
+
78
+ top_pad = range == 0 ? 10 : range / 20.0
79
+ scale_range = (maxvalue + top_pad) - minvalue
80
+
81
+ scale_division = scale_divisions || (scale_range / 10.0)
82
+
83
+ if scale_integers
84
+ scale_division = scale_division < 1 ? 1 : scale_division.round
85
+ end
86
+
87
+ rv = []
88
+ maxvalue = maxvalue%scale_division == 0 ?
89
+ maxvalue : maxvalue + scale_division
90
+ minvalue.step( maxvalue, scale_division ) {|v| rv << v}
91
+ return rv
92
+ end
93
+
94
+ def x_label_offset( width )
95
+ width / 2.0
96
+ end
97
+
98
+ def draw_data
99
+ minvalue = min_value
100
+ fieldwidth = field_width
101
+
102
+ unit_size = (@graph_height.to_f - font_size*2*top_font) /
103
+ (get_y_labels.max - get_y_labels.min)
104
+ bargap = bar_gap ? (fieldwidth < 10 ? fieldwidth / 2 : 10) : 0
105
+
106
+ bar_width = fieldwidth - bargap
107
+ bar_width /= @data.length if stack == :side
108
+ x_mod = (@graph_width-bargap)/2 - (stack==:side ? bar_width/2 : 0)
109
+
110
+ bottom = @graph_height
111
+
112
+ field_count = 0
113
+ @config[:fields].each_index { |i|
114
+ dataset_count = 0
115
+ for dataset in @data
116
+
117
+ # cases (assume 0 = +ve):
118
+ # value min length
119
+ # +ve +ve value - min
120
+ # +ve -ve value - 0
121
+ # -ve -ve value.abs - 0
122
+
123
+ value = dataset[:data][i]
124
+
125
+ left = (fieldwidth * field_count)
126
+
127
+ length = (value.abs - (minvalue > 0 ? minvalue : 0)) * unit_size
128
+ # top is 0 if value is negative
129
+ top = bottom - (((value < 0 ? 0 : value) - minvalue) * unit_size)
130
+ left += bar_width * dataset_count if stack == :side
131
+
132
+ @graph.add_element( "rect", {
133
+ "x" => left.to_s,
134
+ "y" => top.to_s,
135
+ "width" => bar_width.to_s,
136
+ "height" => length.to_s,
137
+ "class" => "fill#{dataset_count+1}"
138
+ })
139
+
140
+ make_datapoint_text(left + bar_width/2.0, top - 6, value.to_s)
141
+ dataset_count += 1
142
+ end
143
+ field_count += 1
144
+ }
145
+ end
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,139 @@
1
+ require 'rexml/document'
2
+ require 'SVG/Graph/Graph'
3
+
4
+ module SVG
5
+ module Graph
6
+ # = Synopsis
7
+ #
8
+ # A superclass for bar-style graphs. Do not attempt to instantiate
9
+ # directly; use one of the subclasses instead.
10
+ #
11
+ # = Author
12
+ #
13
+ # Sean E. Russell <serATgermaneHYPHENsoftwareDOTcom>
14
+ #
15
+ # Copyright 2004 Sean E. Russell
16
+ # This software is available under the Ruby license[LICENSE.txt]
17
+ #
18
+ class BarBase < SVG::Graph::Graph
19
+ # Ensures that :fields are provided in the configuration.
20
+ def initialize config
21
+ raise "fields was not supplied or is empty" unless config[:fields] &&
22
+ config[:fields].kind_of?(Array) &&
23
+ config[:fields].length > 0
24
+ super
25
+ end
26
+
27
+ # In addition to the defaults set in Graph::initialize, sets
28
+ # [bar_gap] true
29
+ # [stack] :overlap
30
+ def set_defaults
31
+ init_with( :bar_gap => true, :stack => :overlap )
32
+ end
33
+
34
+ # Whether to have a gap between the bars or not, default
35
+ # is true, set to false if you don't want gaps.
36
+ attr_accessor :bar_gap
37
+ # How to stack data sets. :overlap overlaps bars with
38
+ # transparent colors, :top stacks bars on top of one another,
39
+ # :side stacks the bars side-by-side. Defaults to :overlap.
40
+ attr_accessor :stack
41
+
42
+
43
+ protected
44
+
45
+ def max_value
46
+ @data.collect{|x| x[:data].max}.max
47
+ end
48
+
49
+ def min_value
50
+ min = 0
51
+ if min_scale_value.nil?
52
+ min = @data.collect{|x| x[:data].min}.min
53
+ min = min > 0 ? 0 : min
54
+ else
55
+ min = min_scale_value
56
+ end
57
+ return min
58
+ end
59
+
60
+ def get_css
61
+ return <<EOL
62
+ /* default fill styles for multiple datasets (probably only use a single dataset on this graph though) */
63
+ .key1,.fill1{
64
+ fill: #ff0000;
65
+ fill-opacity: 0.5;
66
+ stroke: none;
67
+ stroke-width: 0.5px;
68
+ }
69
+ .key2,.fill2{
70
+ fill: #0000ff;
71
+ fill-opacity: 0.5;
72
+ stroke: none;
73
+ stroke-width: 1px;
74
+ }
75
+ .key3,.fill3{
76
+ fill: #00ff00;
77
+ fill-opacity: 0.5;
78
+ stroke: none;
79
+ stroke-width: 1px;
80
+ }
81
+ .key4,.fill4{
82
+ fill: #ffcc00;
83
+ fill-opacity: 0.5;
84
+ stroke: none;
85
+ stroke-width: 1px;
86
+ }
87
+ .key5,.fill5{
88
+ fill: #00ccff;
89
+ fill-opacity: 0.5;
90
+ stroke: none;
91
+ stroke-width: 1px;
92
+ }
93
+ .key6,.fill6{
94
+ fill: #ff00ff;
95
+ fill-opacity: 0.5;
96
+ stroke: none;
97
+ stroke-width: 1px;
98
+ }
99
+ .key7,.fill7{
100
+ fill: #00ffff;
101
+ fill-opacity: 0.5;
102
+ stroke: none;
103
+ stroke-width: 1px;
104
+ }
105
+ .key8,.fill8{
106
+ fill: #ffff00;
107
+ fill-opacity: 0.5;
108
+ stroke: none;
109
+ stroke-width: 1px;
110
+ }
111
+ .key9,.fill9{
112
+ fill: #cc6666;
113
+ fill-opacity: 0.5;
114
+ stroke: none;
115
+ stroke-width: 1px;
116
+ }
117
+ .key10,.fill10{
118
+ fill: #663399;
119
+ fill-opacity: 0.5;
120
+ stroke: none;
121
+ stroke-width: 1px;
122
+ }
123
+ .key11,.fill11{
124
+ fill: #339900;
125
+ fill-opacity: 0.5;
126
+ stroke: none;
127
+ stroke-width: 1px;
128
+ }
129
+ .key12,.fill12{
130
+ fill: #9966FF;
131
+ fill-opacity: 0.5;
132
+ stroke: none;
133
+ stroke-width: 1px;
134
+ }
135
+ EOL
136
+ end
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,149 @@
1
+ require 'rexml/document'
2
+ require 'SVG/Graph/BarBase'
3
+
4
+ module SVG
5
+ module Graph
6
+ # === Create presentation quality SVG horitonzal bar graphs easily
7
+ #
8
+ # = Synopsis
9
+ #
10
+ # require 'SVG/Graph/BarHorizontal'
11
+ #
12
+ # fields = %w(Jan Feb Mar)
13
+ # data_sales_02 = [12, 45, 21]
14
+ #
15
+ # graph = SVG::Graph::BarHorizontal.new({
16
+ # :height => 500,
17
+ # :width => 300,
18
+ # :fields => fields,
19
+ # })
20
+ #
21
+ # graph.add_data({
22
+ # :data => data_sales_02,
23
+ # :title => 'Sales 2002',
24
+ # })
25
+ #
26
+ # print "Content-type: image/svg+xml\r\n\r\n"
27
+ # print graph.burn
28
+ #
29
+ # = Description
30
+ #
31
+ # This object aims to allow you to easily create high quality
32
+ # SVG horitonzal bar graphs. You can either use the default style sheet
33
+ # or supply your own. Either way there are many options which can
34
+ # be configured to give you control over how the graph is
35
+ # generated - with or without a key, data elements at each point,
36
+ # title, subtitle etc.
37
+ #
38
+ # = Examples
39
+ #
40
+ # * http://germane-software.com/repositories/public/SVG/test/test.rb
41
+ #
42
+ # = See also
43
+ #
44
+ # * SVG::Graph::Graph
45
+ # * SVG::Graph::Bar
46
+ # * SVG::Graph::Line
47
+ # * SVG::Graph::Pie
48
+ # * SVG::Graph::Plot
49
+ # * SVG::Graph::TimeSeries
50
+ #
51
+ # == Author
52
+ #
53
+ # Sean E. Russell <serATgermaneHYPHENsoftwareDOTcom>
54
+ #
55
+ # Copyright 2004 Sean E. Russell
56
+ # This software is available under the Ruby license[LICENSE.txt]
57
+ #
58
+ class BarHorizontal < BarBase
59
+ # In addition to the defaults set in BarBase::set_defaults, sets
60
+ # [rotate_y_labels] true
61
+ # [show_x_guidelines] true
62
+ # [show_y_guidelines] false
63
+ def set_defaults
64
+ super
65
+ init_with(
66
+ :rotate_y_labels => true,
67
+ :show_x_guidelines => true,
68
+ :show_y_guidelines => false
69
+ )
70
+ self.right_align = self.right_font = 1
71
+ end
72
+
73
+ protected
74
+
75
+ def get_x_labels
76
+ maxvalue = max_value
77
+ minvalue = min_value
78
+ range = maxvalue - minvalue
79
+ top_pad = range == 0 ? 10 : range / 20.0
80
+ scale_range = (maxvalue + top_pad) - minvalue
81
+
82
+ scale_division = scale_divisions || (scale_range / 10.0)
83
+
84
+ if scale_integers
85
+ scale_division = scale_division < 1 ? 1 : scale_division.round
86
+ end
87
+
88
+ rv = []
89
+ maxvalue = maxvalue%scale_division == 0 ?
90
+ maxvalue : maxvalue + scale_division
91
+ minvalue.step( maxvalue, scale_division ) {|v| rv << v}
92
+ return rv
93
+ end
94
+
95
+ def get_y_labels
96
+ @config[:fields]
97
+ end
98
+
99
+ def y_label_offset( height )
100
+ height / -2.0
101
+ end
102
+
103
+ def draw_data
104
+ minvalue = min_value
105
+ fieldheight = field_height
106
+
107
+ unit_size = (@graph_width.to_f - font_size*2*right_font ) /
108
+ (get_x_labels.max - get_x_labels.min )
109
+ bargap = bar_gap ? (fieldheight < 10 ? fieldheight / 2 : 10) : 0
110
+
111
+ bar_height = fieldheight - bargap
112
+ bar_height /= @data.length if stack == :side
113
+ y_mod = (bar_height / 2) + (font_size / 2)
114
+
115
+ field_count = 1
116
+ @config[:fields].each_index { |i|
117
+ dataset_count = 0
118
+ for dataset in @data
119
+ value = dataset[:data][i]
120
+
121
+ top = @graph_height - (fieldheight * field_count)
122
+ top += (bar_height * dataset_count) if stack == :side
123
+ # cases (assume 0 = +ve):
124
+ # value min length left
125
+ # +ve +ve value.abs - min minvalue.abs
126
+ # +ve -ve value.abs - 0 minvalue.abs
127
+ # -ve -ve value.abs - 0 minvalue.abs + value
128
+ length = (value.abs - (minvalue > 0 ? minvalue : 0)) * unit_size
129
+ left = (minvalue.abs + (value < 0 ? value : 0)) * unit_size
130
+
131
+ @graph.add_element( "rect", {
132
+ "x" => left.to_s,
133
+ "y" => top.to_s,
134
+ "width" => length.to_s,
135
+ "height" => bar_height.to_s,
136
+ "class" => "fill#{dataset_count+1}"
137
+ })
138
+
139
+ make_datapoint_text(
140
+ left+length+5, top+y_mod, value, "text-anchor: start; "
141
+ )
142
+ dataset_count += 1
143
+ end
144
+ field_count += 1
145
+ }
146
+ end
147
+ end
148
+ end
149
+ end