##// END OF EJS Templates
Set proper EOL on SVG source files (#12971)....
Jean-Philippe Lang -
r11039:08826ed0b65c
parent child
Show More
@@ -1,148 +1,148
1 require 'rexml/document'
1 require 'rexml/document'
2 require 'SVG/Graph/Graph'
2 require 'SVG/Graph/Graph'
3 require 'SVG/Graph/BarBase'
3 require 'SVG/Graph/BarBase'
4
4
5 module SVG
5 module SVG
6 module Graph
6 module Graph
7 # === Create presentation quality SVG bar graphs easily
7 # === Create presentation quality SVG bar graphs easily
8 #
8 #
9 # = Synopsis
9 # = Synopsis
10 #
10 #
11 # require 'SVG/Graph/Bar'
11 # require 'SVG/Graph/Bar'
12 #
12 #
13 # fields = %w(Jan Feb Mar);
13 # fields = %w(Jan Feb Mar);
14 # data_sales_02 = [12, 45, 21]
14 # data_sales_02 = [12, 45, 21]
15 #
15 #
16 # graph = SVG::Graph::Bar.new(
16 # graph = SVG::Graph::Bar.new(
17 # :height => 500,
17 # :height => 500,
18 # :width => 300,
18 # :width => 300,
19 # :fields => fields
19 # :fields => fields
20 # )
20 # )
21 #
21 #
22 # graph.add_data(
22 # graph.add_data(
23 # :data => data_sales_02,
23 # :data => data_sales_02,
24 # :title => 'Sales 2002'
24 # :title => 'Sales 2002'
25 # )
25 # )
26 #
26 #
27 # print "Content-type: image/svg+xml\r\n\r\n"
27 # print "Content-type: image/svg+xml\r\n\r\n"
28 # print graph.burn
28 # print graph.burn
29 #
29 #
30 # = Description
30 # = Description
31 #
31 #
32 # This object aims to allow you to easily create high quality
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
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
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 -
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.
36 # with or without a key, data elements at each point, title, subtitle etc.
37 #
37 #
38 # = Notes
38 # = Notes
39 #
39 #
40 # The default stylesheet handles upto 12 data sets, if you
40 # The default stylesheet handles upto 12 data sets, if you
41 # use more you must create your own stylesheet and add the
41 # use more you must create your own stylesheet and add the
42 # additional settings for the extra data sets. You will know
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
43 # if you go over 12 data sets as they will have no style and
44 # be in black.
44 # be in black.
45 #
45 #
46 # = Examples
46 # = Examples
47 #
47 #
48 # * http://germane-software.com/repositories/public/SVG/test/test.rb
48 # * http://germane-software.com/repositories/public/SVG/test/test.rb
49 #
49 #
50 # = See also
50 # = See also
51 #
51 #
52 # * SVG::Graph::Graph
52 # * SVG::Graph::Graph
53 # * SVG::Graph::BarHorizontal
53 # * SVG::Graph::BarHorizontal
54 # * SVG::Graph::Line
54 # * SVG::Graph::Line
55 # * SVG::Graph::Pie
55 # * SVG::Graph::Pie
56 # * SVG::Graph::Plot
56 # * SVG::Graph::Plot
57 # * SVG::Graph::TimeSeries
57 # * SVG::Graph::TimeSeries
58 class Bar < BarBase
58 class Bar < BarBase
59 include REXML
59 include REXML
60
60
61 # See Graph::initialize and BarBase::set_defaults
61 # See Graph::initialize and BarBase::set_defaults
62 def set_defaults
62 def set_defaults
63 super
63 super
64 self.top_align = self.top_font = 1
64 self.top_align = self.top_font = 1
65 end
65 end
66
66
67 protected
67 protected
68
68
69 def get_x_labels
69 def get_x_labels
70 @config[:fields]
70 @config[:fields]
71 end
71 end
72
72
73 def get_y_labels
73 def get_y_labels
74 maxvalue = max_value
74 maxvalue = max_value
75 minvalue = min_value
75 minvalue = min_value
76 range = maxvalue - minvalue
76 range = maxvalue - minvalue
77
77
78 top_pad = range == 0 ? 10 : range / 20.0
78 top_pad = range == 0 ? 10 : range / 20.0
79 scale_range = (maxvalue + top_pad) - minvalue
79 scale_range = (maxvalue + top_pad) - minvalue
80
80
81 scale_division = scale_divisions || (scale_range / 10.0)
81 scale_division = scale_divisions || (scale_range / 10.0)
82
82
83 if scale_integers
83 if scale_integers
84 scale_division = scale_division < 1 ? 1 : scale_division.round
84 scale_division = scale_division < 1 ? 1 : scale_division.round
85 end
85 end
86
86
87 rv = []
87 rv = []
88 maxvalue = maxvalue%scale_division == 0 ?
88 maxvalue = maxvalue%scale_division == 0 ?
89 maxvalue : maxvalue + scale_division
89 maxvalue : maxvalue + scale_division
90 minvalue.step( maxvalue, scale_division ) {|v| rv << v}
90 minvalue.step( maxvalue, scale_division ) {|v| rv << v}
91 return rv
91 return rv
92 end
92 end
93
93
94 def x_label_offset( width )
94 def x_label_offset( width )
95 width / 2.0
95 width / 2.0
96 end
96 end
97
97
98 def draw_data
98 def draw_data
99 minvalue = min_value
99 minvalue = min_value
100 fieldwidth = field_width
100 fieldwidth = field_width
101
101
102 unit_size = (@graph_height.to_f - font_size*2*top_font) /
102 unit_size = (@graph_height.to_f - font_size*2*top_font) /
103 (get_y_labels.max - get_y_labels.min)
103 (get_y_labels.max - get_y_labels.min)
104 bargap = bar_gap ? (fieldwidth < 10 ? fieldwidth / 2 : 10) : 0
104 bargap = bar_gap ? (fieldwidth < 10 ? fieldwidth / 2 : 10) : 0
105
105
106 bar_width = fieldwidth - bargap
106 bar_width = fieldwidth - bargap
107 bar_width /= @data.length if stack == :side
107 bar_width /= @data.length if stack == :side
108 x_mod = (@graph_width-bargap)/2 - (stack==:side ? bar_width/2 : 0)
108 x_mod = (@graph_width-bargap)/2 - (stack==:side ? bar_width/2 : 0)
109
109
110 bottom = @graph_height
110 bottom = @graph_height
111
111
112 field_count = 0
112 field_count = 0
113 @config[:fields].each_index { |i|
113 @config[:fields].each_index { |i|
114 dataset_count = 0
114 dataset_count = 0
115 for dataset in @data
115 for dataset in @data
116
116
117 # cases (assume 0 = +ve):
117 # cases (assume 0 = +ve):
118 # value min length
118 # value min length
119 # +ve +ve value - min
119 # +ve +ve value - min
120 # +ve -ve value - 0
120 # +ve -ve value - 0
121 # -ve -ve value.abs - 0
121 # -ve -ve value.abs - 0
122
122
123 value = dataset[:data][i]
123 value = dataset[:data][i]
124
124
125 left = (fieldwidth * field_count)
125 left = (fieldwidth * field_count)
126
126
127 length = (value.abs - (minvalue > 0 ? minvalue : 0)) * unit_size
127 length = (value.abs - (minvalue > 0 ? minvalue : 0)) * unit_size
128 # top is 0 if value is negative
128 # top is 0 if value is negative
129 top = bottom - (((value < 0 ? 0 : value) - minvalue) * unit_size)
129 top = bottom - (((value < 0 ? 0 : value) - minvalue) * unit_size)
130 left += bar_width * dataset_count if stack == :side
130 left += bar_width * dataset_count if stack == :side
131
131
132 @graph.add_element( "rect", {
132 @graph.add_element( "rect", {
133 "x" => left.to_s,
133 "x" => left.to_s,
134 "y" => top.to_s,
134 "y" => top.to_s,
135 "width" => bar_width.to_s,
135 "width" => bar_width.to_s,
136 "height" => length.to_s,
136 "height" => length.to_s,
137 "class" => "fill#{dataset_count+1}"
137 "class" => "fill#{dataset_count+1}"
138 })
138 })
139
139
140 make_datapoint_text(left + bar_width/2.0, top - 6, value.to_s)
140 make_datapoint_text(left + bar_width/2.0, top - 6, value.to_s)
141 dataset_count += 1
141 dataset_count += 1
142 end
142 end
143 field_count += 1
143 field_count += 1
144 }
144 }
145 end
145 end
146 end
146 end
147 end
147 end
148 end
148 end
@@ -1,139 +1,139
1 require 'rexml/document'
1 require 'rexml/document'
2 require 'SVG/Graph/Graph'
2 require 'SVG/Graph/Graph'
3
3
4 module SVG
4 module SVG
5 module Graph
5 module Graph
6 # = Synopsis
6 # = Synopsis
7 #
7 #
8 # A superclass for bar-style graphs. Do not attempt to instantiate
8 # A superclass for bar-style graphs. Do not attempt to instantiate
9 # directly; use one of the subclasses instead.
9 # directly; use one of the subclasses instead.
10 #
10 #
11 # = Author
11 # = Author
12 #
12 #
13 # Sean E. Russell <serATgermaneHYPHENsoftwareDOTcom>
13 # Sean E. Russell <serATgermaneHYPHENsoftwareDOTcom>
14 #
14 #
15 # Copyright 2004 Sean E. Russell
15 # Copyright 2004 Sean E. Russell
16 # This software is available under the Ruby license[LICENSE.txt]
16 # This software is available under the Ruby license[LICENSE.txt]
17 #
17 #
18 class BarBase < SVG::Graph::Graph
18 class BarBase < SVG::Graph::Graph
19 # Ensures that :fields are provided in the configuration.
19 # Ensures that :fields are provided in the configuration.
20 def initialize config
20 def initialize config
21 raise "fields was not supplied or is empty" unless config[:fields] &&
21 raise "fields was not supplied or is empty" unless config[:fields] &&
22 config[:fields].kind_of?(Array) &&
22 config[:fields].kind_of?(Array) &&
23 config[:fields].length > 0
23 config[:fields].length > 0
24 super
24 super
25 end
25 end
26
26
27 # In addition to the defaults set in Graph::initialize, sets
27 # In addition to the defaults set in Graph::initialize, sets
28 # [bar_gap] true
28 # [bar_gap] true
29 # [stack] :overlap
29 # [stack] :overlap
30 def set_defaults
30 def set_defaults
31 init_with( :bar_gap => true, :stack => :overlap )
31 init_with( :bar_gap => true, :stack => :overlap )
32 end
32 end
33
33
34 # Whether to have a gap between the bars or not, default
34 # Whether to have a gap between the bars or not, default
35 # is true, set to false if you don't want gaps.
35 # is true, set to false if you don't want gaps.
36 attr_accessor :bar_gap
36 attr_accessor :bar_gap
37 # How to stack data sets. :overlap overlaps bars with
37 # How to stack data sets. :overlap overlaps bars with
38 # transparent colors, :top stacks bars on top of one another,
38 # transparent colors, :top stacks bars on top of one another,
39 # :side stacks the bars side-by-side. Defaults to :overlap.
39 # :side stacks the bars side-by-side. Defaults to :overlap.
40 attr_accessor :stack
40 attr_accessor :stack
41
41
42
42
43 protected
43 protected
44
44
45 def max_value
45 def max_value
46 @data.collect{|x| x[:data].max}.max
46 @data.collect{|x| x[:data].max}.max
47 end
47 end
48
48
49 def min_value
49 def min_value
50 min = 0
50 min = 0
51 if min_scale_value.nil?
51 if min_scale_value.nil?
52 min = @data.collect{|x| x[:data].min}.min
52 min = @data.collect{|x| x[:data].min}.min
53 min = min > 0 ? 0 : min
53 min = min > 0 ? 0 : min
54 else
54 else
55 min = min_scale_value
55 min = min_scale_value
56 end
56 end
57 return min
57 return min
58 end
58 end
59
59
60 def get_css
60 def get_css
61 return <<EOL
61 return <<EOL
62 /* default fill styles for multiple datasets (probably only use a single dataset on this graph though) */
62 /* default fill styles for multiple datasets (probably only use a single dataset on this graph though) */
63 .key1,.fill1{
63 .key1,.fill1{
64 fill: #ff0000;
64 fill: #ff0000;
65 fill-opacity: 0.5;
65 fill-opacity: 0.5;
66 stroke: none;
66 stroke: none;
67 stroke-width: 0.5px;
67 stroke-width: 0.5px;
68 }
68 }
69 .key2,.fill2{
69 .key2,.fill2{
70 fill: #0000ff;
70 fill: #0000ff;
71 fill-opacity: 0.5;
71 fill-opacity: 0.5;
72 stroke: none;
72 stroke: none;
73 stroke-width: 1px;
73 stroke-width: 1px;
74 }
74 }
75 .key3,.fill3{
75 .key3,.fill3{
76 fill: #00ff00;
76 fill: #00ff00;
77 fill-opacity: 0.5;
77 fill-opacity: 0.5;
78 stroke: none;
78 stroke: none;
79 stroke-width: 1px;
79 stroke-width: 1px;
80 }
80 }
81 .key4,.fill4{
81 .key4,.fill4{
82 fill: #ffcc00;
82 fill: #ffcc00;
83 fill-opacity: 0.5;
83 fill-opacity: 0.5;
84 stroke: none;
84 stroke: none;
85 stroke-width: 1px;
85 stroke-width: 1px;
86 }
86 }
87 .key5,.fill5{
87 .key5,.fill5{
88 fill: #00ccff;
88 fill: #00ccff;
89 fill-opacity: 0.5;
89 fill-opacity: 0.5;
90 stroke: none;
90 stroke: none;
91 stroke-width: 1px;
91 stroke-width: 1px;
92 }
92 }
93 .key6,.fill6{
93 .key6,.fill6{
94 fill: #ff00ff;
94 fill: #ff00ff;
95 fill-opacity: 0.5;
95 fill-opacity: 0.5;
96 stroke: none;
96 stroke: none;
97 stroke-width: 1px;
97 stroke-width: 1px;
98 }
98 }
99 .key7,.fill7{
99 .key7,.fill7{
100 fill: #00ffff;
100 fill: #00ffff;
101 fill-opacity: 0.5;
101 fill-opacity: 0.5;
102 stroke: none;
102 stroke: none;
103 stroke-width: 1px;
103 stroke-width: 1px;
104 }
104 }
105 .key8,.fill8{
105 .key8,.fill8{
106 fill: #ffff00;
106 fill: #ffff00;
107 fill-opacity: 0.5;
107 fill-opacity: 0.5;
108 stroke: none;
108 stroke: none;
109 stroke-width: 1px;
109 stroke-width: 1px;
110 }
110 }
111 .key9,.fill9{
111 .key9,.fill9{
112 fill: #cc6666;
112 fill: #cc6666;
113 fill-opacity: 0.5;
113 fill-opacity: 0.5;
114 stroke: none;
114 stroke: none;
115 stroke-width: 1px;
115 stroke-width: 1px;
116 }
116 }
117 .key10,.fill10{
117 .key10,.fill10{
118 fill: #663399;
118 fill: #663399;
119 fill-opacity: 0.5;
119 fill-opacity: 0.5;
120 stroke: none;
120 stroke: none;
121 stroke-width: 1px;
121 stroke-width: 1px;
122 }
122 }
123 .key11,.fill11{
123 .key11,.fill11{
124 fill: #339900;
124 fill: #339900;
125 fill-opacity: 0.5;
125 fill-opacity: 0.5;
126 stroke: none;
126 stroke: none;
127 stroke-width: 1px;
127 stroke-width: 1px;
128 }
128 }
129 .key12,.fill12{
129 .key12,.fill12{
130 fill: #9966FF;
130 fill: #9966FF;
131 fill-opacity: 0.5;
131 fill-opacity: 0.5;
132 stroke: none;
132 stroke: none;
133 stroke-width: 1px;
133 stroke-width: 1px;
134 }
134 }
135 EOL
135 EOL
136 end
136 end
137 end
137 end
138 end
138 end
139 end
139 end
@@ -1,149 +1,149
1 require 'rexml/document'
1 require 'rexml/document'
2 require 'SVG/Graph/BarBase'
2 require 'SVG/Graph/BarBase'
3
3
4 module SVG
4 module SVG
5 module Graph
5 module Graph
6 # === Create presentation quality SVG horitonzal bar graphs easily
6 # === Create presentation quality SVG horitonzal bar graphs easily
7 #
7 #
8 # = Synopsis
8 # = Synopsis
9 #
9 #
10 # require 'SVG/Graph/BarHorizontal'
10 # require 'SVG/Graph/BarHorizontal'
11 #
11 #
12 # fields = %w(Jan Feb Mar)
12 # fields = %w(Jan Feb Mar)
13 # data_sales_02 = [12, 45, 21]
13 # data_sales_02 = [12, 45, 21]
14 #
14 #
15 # graph = SVG::Graph::BarHorizontal.new({
15 # graph = SVG::Graph::BarHorizontal.new({
16 # :height => 500,
16 # :height => 500,
17 # :width => 300,
17 # :width => 300,
18 # :fields => fields,
18 # :fields => fields,
19 # })
19 # })
20 #
20 #
21 # graph.add_data({
21 # graph.add_data({
22 # :data => data_sales_02,
22 # :data => data_sales_02,
23 # :title => 'Sales 2002',
23 # :title => 'Sales 2002',
24 # })
24 # })
25 #
25 #
26 # print "Content-type: image/svg+xml\r\n\r\n"
26 # print "Content-type: image/svg+xml\r\n\r\n"
27 # print graph.burn
27 # print graph.burn
28 #
28 #
29 # = Description
29 # = Description
30 #
30 #
31 # This object aims to allow you to easily create high quality
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
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
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
34 # be configured to give you control over how the graph is
35 # generated - with or without a key, data elements at each point,
35 # generated - with or without a key, data elements at each point,
36 # title, subtitle etc.
36 # title, subtitle etc.
37 #
37 #
38 # = Examples
38 # = Examples
39 #
39 #
40 # * http://germane-software.com/repositories/public/SVG/test/test.rb
40 # * http://germane-software.com/repositories/public/SVG/test/test.rb
41 #
41 #
42 # = See also
42 # = See also
43 #
43 #
44 # * SVG::Graph::Graph
44 # * SVG::Graph::Graph
45 # * SVG::Graph::Bar
45 # * SVG::Graph::Bar
46 # * SVG::Graph::Line
46 # * SVG::Graph::Line
47 # * SVG::Graph::Pie
47 # * SVG::Graph::Pie
48 # * SVG::Graph::Plot
48 # * SVG::Graph::Plot
49 # * SVG::Graph::TimeSeries
49 # * SVG::Graph::TimeSeries
50 #
50 #
51 # == Author
51 # == Author
52 #
52 #
53 # Sean E. Russell <serATgermaneHYPHENsoftwareDOTcom>
53 # Sean E. Russell <serATgermaneHYPHENsoftwareDOTcom>
54 #
54 #
55 # Copyright 2004 Sean E. Russell
55 # Copyright 2004 Sean E. Russell
56 # This software is available under the Ruby license[LICENSE.txt]
56 # This software is available under the Ruby license[LICENSE.txt]
57 #
57 #
58 class BarHorizontal < BarBase
58 class BarHorizontal < BarBase
59 # In addition to the defaults set in BarBase::set_defaults, sets
59 # In addition to the defaults set in BarBase::set_defaults, sets
60 # [rotate_y_labels] true
60 # [rotate_y_labels] true
61 # [show_x_guidelines] true
61 # [show_x_guidelines] true
62 # [show_y_guidelines] false
62 # [show_y_guidelines] false
63 def set_defaults
63 def set_defaults
64 super
64 super
65 init_with(
65 init_with(
66 :rotate_y_labels => true,
66 :rotate_y_labels => true,
67 :show_x_guidelines => true,
67 :show_x_guidelines => true,
68 :show_y_guidelines => false
68 :show_y_guidelines => false
69 )
69 )
70 self.right_align = self.right_font = 1
70 self.right_align = self.right_font = 1
71 end
71 end
72
72
73 protected
73 protected
74
74
75 def get_x_labels
75 def get_x_labels
76 maxvalue = max_value
76 maxvalue = max_value
77 minvalue = min_value
77 minvalue = min_value
78 range = maxvalue - minvalue
78 range = maxvalue - minvalue
79 top_pad = range == 0 ? 10 : range / 20.0
79 top_pad = range == 0 ? 10 : range / 20.0
80 scale_range = (maxvalue + top_pad) - minvalue
80 scale_range = (maxvalue + top_pad) - minvalue
81
81
82 scale_division = scale_divisions || (scale_range / 10.0)
82 scale_division = scale_divisions || (scale_range / 10.0)
83
83
84 if scale_integers
84 if scale_integers
85 scale_division = scale_division < 1 ? 1 : scale_division.round
85 scale_division = scale_division < 1 ? 1 : scale_division.round
86 end
86 end
87
87
88 rv = []
88 rv = []
89 maxvalue = maxvalue%scale_division == 0 ?
89 maxvalue = maxvalue%scale_division == 0 ?
90 maxvalue : maxvalue + scale_division
90 maxvalue : maxvalue + scale_division
91 minvalue.step( maxvalue, scale_division ) {|v| rv << v}
91 minvalue.step( maxvalue, scale_division ) {|v| rv << v}
92 return rv
92 return rv
93 end
93 end
94
94
95 def get_y_labels
95 def get_y_labels
96 @config[:fields]
96 @config[:fields]
97 end
97 end
98
98
99 def y_label_offset( height )
99 def y_label_offset( height )
100 height / -2.0
100 height / -2.0
101 end
101 end
102
102
103 def draw_data
103 def draw_data
104 minvalue = min_value
104 minvalue = min_value
105 fieldheight = field_height
105 fieldheight = field_height
106
106
107 unit_size = (@graph_width.to_f - font_size*2*right_font ) /
107 unit_size = (@graph_width.to_f - font_size*2*right_font ) /
108 (get_x_labels.max - get_x_labels.min )
108 (get_x_labels.max - get_x_labels.min )
109 bargap = bar_gap ? (fieldheight < 10 ? fieldheight / 2 : 10) : 0
109 bargap = bar_gap ? (fieldheight < 10 ? fieldheight / 2 : 10) : 0
110
110
111 bar_height = fieldheight - bargap
111 bar_height = fieldheight - bargap
112 bar_height /= @data.length if stack == :side
112 bar_height /= @data.length if stack == :side
113 y_mod = (bar_height / 2) + (font_size / 2)
113 y_mod = (bar_height / 2) + (font_size / 2)
114
114
115 field_count = 1
115 field_count = 1
116 @config[:fields].each_index { |i|
116 @config[:fields].each_index { |i|
117 dataset_count = 0
117 dataset_count = 0
118 for dataset in @data
118 for dataset in @data
119 value = dataset[:data][i]
119 value = dataset[:data][i]
120
120
121 top = @graph_height - (fieldheight * field_count)
121 top = @graph_height - (fieldheight * field_count)
122 top += (bar_height * dataset_count) if stack == :side
122 top += (bar_height * dataset_count) if stack == :side
123 # cases (assume 0 = +ve):
123 # cases (assume 0 = +ve):
124 # value min length left
124 # value min length left
125 # +ve +ve value.abs - min minvalue.abs
125 # +ve +ve value.abs - min minvalue.abs
126 # +ve -ve value.abs - 0 minvalue.abs
126 # +ve -ve value.abs - 0 minvalue.abs
127 # -ve -ve value.abs - 0 minvalue.abs + value
127 # -ve -ve value.abs - 0 minvalue.abs + value
128 length = (value.abs - (minvalue > 0 ? minvalue : 0)) * unit_size
128 length = (value.abs - (minvalue > 0 ? minvalue : 0)) * unit_size
129 left = (minvalue.abs + (value < 0 ? value : 0)) * unit_size
129 left = (minvalue.abs + (value < 0 ? value : 0)) * unit_size
130
130
131 @graph.add_element( "rect", {
131 @graph.add_element( "rect", {
132 "x" => left.to_s,
132 "x" => left.to_s,
133 "y" => top.to_s,
133 "y" => top.to_s,
134 "width" => length.to_s,
134 "width" => length.to_s,
135 "height" => bar_height.to_s,
135 "height" => bar_height.to_s,
136 "class" => "fill#{dataset_count+1}"
136 "class" => "fill#{dataset_count+1}"
137 })
137 })
138
138
139 make_datapoint_text(
139 make_datapoint_text(
140 left+length+5, top+y_mod, value, "text-anchor: start; "
140 left+length+5, top+y_mod, value, "text-anchor: start; "
141 )
141 )
142 dataset_count += 1
142 dataset_count += 1
143 end
143 end
144 field_count += 1
144 field_count += 1
145 }
145 }
146 end
146 end
147 end
147 end
148 end
148 end
149 end
149 end
This diff has been collapsed as it changes many lines, (1956 lines changed) Show them Hide them
@@ -1,978 +1,978
1 begin
1 begin
2 require 'zlib'
2 require 'zlib'
3 @@__have_zlib = true
3 @@__have_zlib = true
4 rescue
4 rescue
5 @@__have_zlib = false
5 @@__have_zlib = false
6 end
6 end
7
7
8 require 'rexml/document'
8 require 'rexml/document'
9
9
10 module SVG
10 module SVG
11 module Graph
11 module Graph
12 VERSION = '@ANT_VERSION@'
12 VERSION = '@ANT_VERSION@'
13
13
14 # === Base object for generating SVG Graphs
14 # === Base object for generating SVG Graphs
15 #
15 #
16 # == Synopsis
16 # == Synopsis
17 #
17 #
18 # This class is only used as a superclass of specialized charts. Do not
18 # This class is only used as a superclass of specialized charts. Do not
19 # attempt to use this class directly, unless creating a new chart type.
19 # attempt to use this class directly, unless creating a new chart type.
20 #
20 #
21 # For examples of how to subclass this class, see the existing specific
21 # For examples of how to subclass this class, see the existing specific
22 # subclasses, such as SVG::Graph::Pie.
22 # subclasses, such as SVG::Graph::Pie.
23 #
23 #
24 # == Examples
24 # == Examples
25 #
25 #
26 # For examples of how to use this package, see either the test files, or
26 # For examples of how to use this package, see either the test files, or
27 # the documentation for the specific class you want to use.
27 # the documentation for the specific class you want to use.
28 #
28 #
29 # * file:test/plot.rb
29 # * file:test/plot.rb
30 # * file:test/single.rb
30 # * file:test/single.rb
31 # * file:test/test.rb
31 # * file:test/test.rb
32 # * file:test/timeseries.rb
32 # * file:test/timeseries.rb
33 #
33 #
34 # == Description
34 # == Description
35 #
35 #
36 # This package should be used as a base for creating SVG graphs.
36 # This package should be used as a base for creating SVG graphs.
37 #
37 #
38 # == Acknowledgements
38 # == Acknowledgements
39 #
39 #
40 # Leo Lapworth for creating the SVG::TT::Graph package which this Ruby
40 # Leo Lapworth for creating the SVG::TT::Graph package which this Ruby
41 # port is based on.
41 # port is based on.
42 #
42 #
43 # Stephen Morgan for creating the TT template and SVG.
43 # Stephen Morgan for creating the TT template and SVG.
44 #
44 #
45 # == See
45 # == See
46 #
46 #
47 # * SVG::Graph::BarHorizontal
47 # * SVG::Graph::BarHorizontal
48 # * SVG::Graph::Bar
48 # * SVG::Graph::Bar
49 # * SVG::Graph::Line
49 # * SVG::Graph::Line
50 # * SVG::Graph::Pie
50 # * SVG::Graph::Pie
51 # * SVG::Graph::Plot
51 # * SVG::Graph::Plot
52 # * SVG::Graph::TimeSeries
52 # * SVG::Graph::TimeSeries
53 #
53 #
54 # == Author
54 # == Author
55 #
55 #
56 # Sean E. Russell <serATgermaneHYPHENsoftwareDOTcom>
56 # Sean E. Russell <serATgermaneHYPHENsoftwareDOTcom>
57 #
57 #
58 # Copyright 2004 Sean E. Russell
58 # Copyright 2004 Sean E. Russell
59 # This software is available under the Ruby license[LICENSE.txt]
59 # This software is available under the Ruby license[LICENSE.txt]
60 #
60 #
61 class Graph
61 class Graph
62 include REXML
62 include REXML
63
63
64 # Initialize the graph object with the graph settings. You won't
64 # Initialize the graph object with the graph settings. You won't
65 # instantiate this class directly; see the subclass for options.
65 # instantiate this class directly; see the subclass for options.
66 # [width] 500
66 # [width] 500
67 # [height] 300
67 # [height] 300
68 # [show_x_guidelines] false
68 # [show_x_guidelines] false
69 # [show_y_guidelines] true
69 # [show_y_guidelines] true
70 # [show_data_values] true
70 # [show_data_values] true
71 # [min_scale_value] 0
71 # [min_scale_value] 0
72 # [show_x_labels] true
72 # [show_x_labels] true
73 # [stagger_x_labels] false
73 # [stagger_x_labels] false
74 # [rotate_x_labels] false
74 # [rotate_x_labels] false
75 # [step_x_labels] 1
75 # [step_x_labels] 1
76 # [step_include_first_x_label] true
76 # [step_include_first_x_label] true
77 # [show_y_labels] true
77 # [show_y_labels] true
78 # [rotate_y_labels] false
78 # [rotate_y_labels] false
79 # [scale_integers] false
79 # [scale_integers] false
80 # [show_x_title] false
80 # [show_x_title] false
81 # [x_title] 'X Field names'
81 # [x_title] 'X Field names'
82 # [show_y_title] false
82 # [show_y_title] false
83 # [y_title_text_direction] :bt
83 # [y_title_text_direction] :bt
84 # [y_title] 'Y Scale'
84 # [y_title] 'Y Scale'
85 # [show_graph_title] false
85 # [show_graph_title] false
86 # [graph_title] 'Graph Title'
86 # [graph_title] 'Graph Title'
87 # [show_graph_subtitle] false
87 # [show_graph_subtitle] false
88 # [graph_subtitle] 'Graph Sub Title'
88 # [graph_subtitle] 'Graph Sub Title'
89 # [key] true,
89 # [key] true,
90 # [key_position] :right, # bottom or righ
90 # [key_position] :right, # bottom or righ
91 # [font_size] 12
91 # [font_size] 12
92 # [title_font_size] 16
92 # [title_font_size] 16
93 # [subtitle_font_size] 14
93 # [subtitle_font_size] 14
94 # [x_label_font_size] 12
94 # [x_label_font_size] 12
95 # [x_title_font_size] 14
95 # [x_title_font_size] 14
96 # [y_label_font_size] 12
96 # [y_label_font_size] 12
97 # [y_title_font_size] 14
97 # [y_title_font_size] 14
98 # [key_font_size] 10
98 # [key_font_size] 10
99 # [no_css] false
99 # [no_css] false
100 # [add_popups] false
100 # [add_popups] false
101 def initialize( config )
101 def initialize( config )
102 @config = config
102 @config = config
103
103
104 self.top_align = self.top_font = self.right_align = self.right_font = 0
104 self.top_align = self.top_font = self.right_align = self.right_font = 0
105
105
106 init_with({
106 init_with({
107 :width => 500,
107 :width => 500,
108 :height => 300,
108 :height => 300,
109 :show_x_guidelines => false,
109 :show_x_guidelines => false,
110 :show_y_guidelines => true,
110 :show_y_guidelines => true,
111 :show_data_values => true,
111 :show_data_values => true,
112
112
113 # :min_scale_value => 0,
113 # :min_scale_value => 0,
114
114
115 :show_x_labels => true,
115 :show_x_labels => true,
116 :stagger_x_labels => false,
116 :stagger_x_labels => false,
117 :rotate_x_labels => false,
117 :rotate_x_labels => false,
118 :step_x_labels => 1,
118 :step_x_labels => 1,
119 :step_include_first_x_label => true,
119 :step_include_first_x_label => true,
120
120
121 :show_y_labels => true,
121 :show_y_labels => true,
122 :rotate_y_labels => false,
122 :rotate_y_labels => false,
123 :stagger_y_labels => false,
123 :stagger_y_labels => false,
124 :scale_integers => false,
124 :scale_integers => false,
125
125
126 :show_x_title => false,
126 :show_x_title => false,
127 :x_title => 'X Field names',
127 :x_title => 'X Field names',
128
128
129 :show_y_title => false,
129 :show_y_title => false,
130 :y_title_text_direction => :bt,
130 :y_title_text_direction => :bt,
131 :y_title => 'Y Scale',
131 :y_title => 'Y Scale',
132
132
133 :show_graph_title => false,
133 :show_graph_title => false,
134 :graph_title => 'Graph Title',
134 :graph_title => 'Graph Title',
135 :show_graph_subtitle => false,
135 :show_graph_subtitle => false,
136 :graph_subtitle => 'Graph Sub Title',
136 :graph_subtitle => 'Graph Sub Title',
137 :key => true,
137 :key => true,
138 :key_position => :right, # bottom or right
138 :key_position => :right, # bottom or right
139
139
140 :font_size =>12,
140 :font_size =>12,
141 :title_font_size =>16,
141 :title_font_size =>16,
142 :subtitle_font_size =>14,
142 :subtitle_font_size =>14,
143 :x_label_font_size =>12,
143 :x_label_font_size =>12,
144 :x_title_font_size =>14,
144 :x_title_font_size =>14,
145 :y_label_font_size =>12,
145 :y_label_font_size =>12,
146 :y_title_font_size =>14,
146 :y_title_font_size =>14,
147 :key_font_size =>10,
147 :key_font_size =>10,
148
148
149 :no_css =>false,
149 :no_css =>false,
150 :add_popups =>false,
150 :add_popups =>false,
151 })
151 })
152
152
153 set_defaults if respond_to? :set_defaults
153 set_defaults if respond_to? :set_defaults
154
154
155 init_with config
155 init_with config
156 end
156 end
157
157
158
158
159 # This method allows you do add data to the graph object.
159 # This method allows you do add data to the graph object.
160 # It can be called several times to add more data sets in.
160 # It can be called several times to add more data sets in.
161 #
161 #
162 # data_sales_02 = [12, 45, 21];
162 # data_sales_02 = [12, 45, 21];
163 #
163 #
164 # graph.add_data({
164 # graph.add_data({
165 # :data => data_sales_02,
165 # :data => data_sales_02,
166 # :title => 'Sales 2002'
166 # :title => 'Sales 2002'
167 # })
167 # })
168 def add_data conf
168 def add_data conf
169 @data = [] unless defined? @data
169 @data = [] unless defined? @data
170
170
171 if conf[:data] and conf[:data].kind_of? Array
171 if conf[:data] and conf[:data].kind_of? Array
172 @data << conf
172 @data << conf
173 else
173 else
174 raise "No data provided by #{conf.inspect}"
174 raise "No data provided by #{conf.inspect}"
175 end
175 end
176 end
176 end
177
177
178
178
179 # This method removes all data from the object so that you can
179 # This method removes all data from the object so that you can
180 # reuse it to create a new graph but with the same config options.
180 # reuse it to create a new graph but with the same config options.
181 #
181 #
182 # graph.clear_data
182 # graph.clear_data
183 def clear_data
183 def clear_data
184 @data = []
184 @data = []
185 end
185 end
186
186
187
187
188 # This method processes the template with the data and
188 # This method processes the template with the data and
189 # config which has been set and returns the resulting SVG.
189 # config which has been set and returns the resulting SVG.
190 #
190 #
191 # This method will croak unless at least one data set has
191 # This method will croak unless at least one data set has
192 # been added to the graph object.
192 # been added to the graph object.
193 #
193 #
194 # print graph.burn
194 # print graph.burn
195 def burn
195 def burn
196 raise "No data available" unless @data.size > 0
196 raise "No data available" unless @data.size > 0
197
197
198 calculations if respond_to? :calculations
198 calculations if respond_to? :calculations
199
199
200 start_svg
200 start_svg
201 calculate_graph_dimensions
201 calculate_graph_dimensions
202 @foreground = Element.new( "g" )
202 @foreground = Element.new( "g" )
203 draw_graph
203 draw_graph
204 draw_titles
204 draw_titles
205 draw_legend
205 draw_legend
206 draw_data
206 draw_data
207 @graph.add_element( @foreground )
207 @graph.add_element( @foreground )
208 style
208 style
209
209
210 data = ""
210 data = ""
211 @doc.write( data, 0 )
211 @doc.write( data, 0 )
212
212
213 if @config[:compress]
213 if @config[:compress]
214 if @@__have_zlib
214 if @@__have_zlib
215 inp, out = IO.pipe
215 inp, out = IO.pipe
216 gz = Zlib::GzipWriter.new( out )
216 gz = Zlib::GzipWriter.new( out )
217 gz.write data
217 gz.write data
218 gz.close
218 gz.close
219 data = inp.read
219 data = inp.read
220 else
220 else
221 data << "<!-- Ruby Zlib not available for SVGZ -->";
221 data << "<!-- Ruby Zlib not available for SVGZ -->";
222 end
222 end
223 end
223 end
224
224
225 return data
225 return data
226 end
226 end
227
227
228
228
229 # Set the height of the graph box, this is the total height
229 # Set the height of the graph box, this is the total height
230 # of the SVG box created - not the graph it self which auto
230 # of the SVG box created - not the graph it self which auto
231 # scales to fix the space.
231 # scales to fix the space.
232 attr_accessor :height
232 attr_accessor :height
233 # Set the width of the graph box, this is the total width
233 # Set the width of the graph box, this is the total width
234 # of the SVG box created - not the graph it self which auto
234 # of the SVG box created - not the graph it self which auto
235 # scales to fix the space.
235 # scales to fix the space.
236 attr_accessor :width
236 attr_accessor :width
237 # Set the path to an external stylesheet, set to '' if
237 # Set the path to an external stylesheet, set to '' if
238 # you want to revert back to using the defaut internal version.
238 # you want to revert back to using the defaut internal version.
239 #
239 #
240 # To create an external stylesheet create a graph using the
240 # To create an external stylesheet create a graph using the
241 # default internal version and copy the stylesheet section to
241 # default internal version and copy the stylesheet section to
242 # an external file and edit from there.
242 # an external file and edit from there.
243 attr_accessor :style_sheet
243 attr_accessor :style_sheet
244 # (Bool) Show the value of each element of data on the graph
244 # (Bool) Show the value of each element of data on the graph
245 attr_accessor :show_data_values
245 attr_accessor :show_data_values
246 # The point at which the Y axis starts, defaults to '0',
246 # The point at which the Y axis starts, defaults to '0',
247 # if set to nil it will default to the minimum data value.
247 # if set to nil it will default to the minimum data value.
248 attr_accessor :min_scale_value
248 attr_accessor :min_scale_value
249 # Whether to show labels on the X axis or not, defaults
249 # Whether to show labels on the X axis or not, defaults
250 # to true, set to false if you want to turn them off.
250 # to true, set to false if you want to turn them off.
251 attr_accessor :show_x_labels
251 attr_accessor :show_x_labels
252 # This puts the X labels at alternative levels so if they
252 # This puts the X labels at alternative levels so if they
253 # are long field names they will not overlap so easily.
253 # are long field names they will not overlap so easily.
254 # Default it false, to turn on set to true.
254 # Default it false, to turn on set to true.
255 attr_accessor :stagger_x_labels
255 attr_accessor :stagger_x_labels
256 # This puts the Y labels at alternative levels so if they
256 # This puts the Y labels at alternative levels so if they
257 # are long field names they will not overlap so easily.
257 # are long field names they will not overlap so easily.
258 # Default it false, to turn on set to true.
258 # Default it false, to turn on set to true.
259 attr_accessor :stagger_y_labels
259 attr_accessor :stagger_y_labels
260 # This turns the X axis labels by 90 degrees.
260 # This turns the X axis labels by 90 degrees.
261 # Default it false, to turn on set to true.
261 # Default it false, to turn on set to true.
262 attr_accessor :rotate_x_labels
262 attr_accessor :rotate_x_labels
263 # This turns the Y axis labels by 90 degrees.
263 # This turns the Y axis labels by 90 degrees.
264 # Default it false, to turn on set to true.
264 # Default it false, to turn on set to true.
265 attr_accessor :rotate_y_labels
265 attr_accessor :rotate_y_labels
266 # How many "steps" to use between displayed X axis labels,
266 # How many "steps" to use between displayed X axis labels,
267 # a step of one means display every label, a step of two results
267 # a step of one means display every label, a step of two results
268 # in every other label being displayed (label <gap> label <gap> label),
268 # in every other label being displayed (label <gap> label <gap> label),
269 # a step of three results in every third label being displayed
269 # a step of three results in every third label being displayed
270 # (label <gap> <gap> label <gap> <gap> label) and so on.
270 # (label <gap> <gap> label <gap> <gap> label) and so on.
271 attr_accessor :step_x_labels
271 attr_accessor :step_x_labels
272 # Whether to (when taking "steps" between X axis labels) step from
272 # Whether to (when taking "steps" between X axis labels) step from
273 # the first label (i.e. always include the first label) or step from
273 # the first label (i.e. always include the first label) or step from
274 # the X axis origin (i.e. start with a gap if step_x_labels is greater
274 # the X axis origin (i.e. start with a gap if step_x_labels is greater
275 # than one).
275 # than one).
276 attr_accessor :step_include_first_x_label
276 attr_accessor :step_include_first_x_label
277 # Whether to show labels on the Y axis or not, defaults
277 # Whether to show labels on the Y axis or not, defaults
278 # to true, set to false if you want to turn them off.
278 # to true, set to false if you want to turn them off.
279 attr_accessor :show_y_labels
279 attr_accessor :show_y_labels
280 # Ensures only whole numbers are used as the scale divisions.
280 # Ensures only whole numbers are used as the scale divisions.
281 # Default it false, to turn on set to true. This has no effect if
281 # Default it false, to turn on set to true. This has no effect if
282 # scale divisions are less than 1.
282 # scale divisions are less than 1.
283 attr_accessor :scale_integers
283 attr_accessor :scale_integers
284 # This defines the gap between markers on the Y axis,
284 # This defines the gap between markers on the Y axis,
285 # default is a 10th of the max_value, e.g. you will have
285 # default is a 10th of the max_value, e.g. you will have
286 # 10 markers on the Y axis. NOTE: do not set this too
286 # 10 markers on the Y axis. NOTE: do not set this too
287 # low - you are limited to 999 markers, after that the
287 # low - you are limited to 999 markers, after that the
288 # graph won't generate.
288 # graph won't generate.
289 attr_accessor :scale_divisions
289 attr_accessor :scale_divisions
290 # Whether to show the title under the X axis labels,
290 # Whether to show the title under the X axis labels,
291 # default is false, set to true to show.
291 # default is false, set to true to show.
292 attr_accessor :show_x_title
292 attr_accessor :show_x_title
293 # What the title under X axis should be, e.g. 'Months'.
293 # What the title under X axis should be, e.g. 'Months'.
294 attr_accessor :x_title
294 attr_accessor :x_title
295 # Whether to show the title under the Y axis labels,
295 # Whether to show the title under the Y axis labels,
296 # default is false, set to true to show.
296 # default is false, set to true to show.
297 attr_accessor :show_y_title
297 attr_accessor :show_y_title
298 # Aligns writing mode for Y axis label.
298 # Aligns writing mode for Y axis label.
299 # Defaults to :bt (Bottom to Top).
299 # Defaults to :bt (Bottom to Top).
300 # Change to :tb (Top to Bottom) to reverse.
300 # Change to :tb (Top to Bottom) to reverse.
301 attr_accessor :y_title_text_direction
301 attr_accessor :y_title_text_direction
302 # What the title under Y axis should be, e.g. 'Sales in thousands'.
302 # What the title under Y axis should be, e.g. 'Sales in thousands'.
303 attr_accessor :y_title
303 attr_accessor :y_title
304 # Whether to show a title on the graph, defaults
304 # Whether to show a title on the graph, defaults
305 # to false, set to true to show.
305 # to false, set to true to show.
306 attr_accessor :show_graph_title
306 attr_accessor :show_graph_title
307 # What the title on the graph should be.
307 # What the title on the graph should be.
308 attr_accessor :graph_title
308 attr_accessor :graph_title
309 # Whether to show a subtitle on the graph, defaults
309 # Whether to show a subtitle on the graph, defaults
310 # to false, set to true to show.
310 # to false, set to true to show.
311 attr_accessor :show_graph_subtitle
311 attr_accessor :show_graph_subtitle
312 # What the subtitle on the graph should be.
312 # What the subtitle on the graph should be.
313 attr_accessor :graph_subtitle
313 attr_accessor :graph_subtitle
314 # Whether to show a key, defaults to false, set to
314 # Whether to show a key, defaults to false, set to
315 # true if you want to show it.
315 # true if you want to show it.
316 attr_accessor :key
316 attr_accessor :key
317 # Where the key should be positioned, defaults to
317 # Where the key should be positioned, defaults to
318 # :right, set to :bottom if you want to move it.
318 # :right, set to :bottom if you want to move it.
319 attr_accessor :key_position
319 attr_accessor :key_position
320 # Set the font size (in points) of the data point labels
320 # Set the font size (in points) of the data point labels
321 attr_accessor :font_size
321 attr_accessor :font_size
322 # Set the font size of the X axis labels
322 # Set the font size of the X axis labels
323 attr_accessor :x_label_font_size
323 attr_accessor :x_label_font_size
324 # Set the font size of the X axis title
324 # Set the font size of the X axis title
325 attr_accessor :x_title_font_size
325 attr_accessor :x_title_font_size
326 # Set the font size of the Y axis labels
326 # Set the font size of the Y axis labels
327 attr_accessor :y_label_font_size
327 attr_accessor :y_label_font_size
328 # Set the font size of the Y axis title
328 # Set the font size of the Y axis title
329 attr_accessor :y_title_font_size
329 attr_accessor :y_title_font_size
330 # Set the title font size
330 # Set the title font size
331 attr_accessor :title_font_size
331 attr_accessor :title_font_size
332 # Set the subtitle font size
332 # Set the subtitle font size
333 attr_accessor :subtitle_font_size
333 attr_accessor :subtitle_font_size
334 # Set the key font size
334 # Set the key font size
335 attr_accessor :key_font_size
335 attr_accessor :key_font_size
336 # Show guidelines for the X axis
336 # Show guidelines for the X axis
337 attr_accessor :show_x_guidelines
337 attr_accessor :show_x_guidelines
338 # Show guidelines for the Y axis
338 # Show guidelines for the Y axis
339 attr_accessor :show_y_guidelines
339 attr_accessor :show_y_guidelines
340 # Do not use CSS if set to true. Many SVG viewers do not support CSS, but
340 # Do not use CSS if set to true. Many SVG viewers do not support CSS, but
341 # not using CSS can result in larger SVGs as well as making it impossible to
341 # not using CSS can result in larger SVGs as well as making it impossible to
342 # change colors after the chart is generated. Defaults to false.
342 # change colors after the chart is generated. Defaults to false.
343 attr_accessor :no_css
343 attr_accessor :no_css
344 # Add popups for the data points on some graphs
344 # Add popups for the data points on some graphs
345 attr_accessor :add_popups
345 attr_accessor :add_popups
346
346
347
347
348 protected
348 protected
349
349
350 def sort( *arrys )
350 def sort( *arrys )
351 sort_multiple( arrys )
351 sort_multiple( arrys )
352 end
352 end
353
353
354 # Overwrite configuration options with supplied options. Used
354 # Overwrite configuration options with supplied options. Used
355 # by subclasses.
355 # by subclasses.
356 def init_with config
356 def init_with config
357 config.each { |key, value|
357 config.each { |key, value|
358 self.send((key.to_s+"=").to_sym, value ) if respond_to? key.to_sym
358 self.send((key.to_s+"=").to_sym, value ) if respond_to? key.to_sym
359 }
359 }
360 end
360 end
361
361
362 attr_accessor :top_align, :top_font, :right_align, :right_font
362 attr_accessor :top_align, :top_font, :right_align, :right_font
363
363
364 KEY_BOX_SIZE = 12
364 KEY_BOX_SIZE = 12
365
365
366 # Override this (and call super) to change the margin to the left
366 # Override this (and call super) to change the margin to the left
367 # of the plot area. Results in @border_left being set.
367 # of the plot area. Results in @border_left being set.
368 def calculate_left_margin
368 def calculate_left_margin
369 @border_left = 7
369 @border_left = 7
370 # Check for Y labels
370 # Check for Y labels
371 max_y_label_height_px = rotate_y_labels ?
371 max_y_label_height_px = rotate_y_labels ?
372 y_label_font_size :
372 y_label_font_size :
373 get_y_labels.max{|a,b|
373 get_y_labels.max{|a,b|
374 a.to_s.length<=>b.to_s.length
374 a.to_s.length<=>b.to_s.length
375 }.to_s.length * y_label_font_size * 0.6
375 }.to_s.length * y_label_font_size * 0.6
376 @border_left += max_y_label_height_px if show_y_labels
376 @border_left += max_y_label_height_px if show_y_labels
377 @border_left += max_y_label_height_px + 10 if stagger_y_labels
377 @border_left += max_y_label_height_px + 10 if stagger_y_labels
378 @border_left += y_title_font_size + 5 if show_y_title
378 @border_left += y_title_font_size + 5 if show_y_title
379 end
379 end
380
380
381
381
382 # Calculates the width of the widest Y label. This will be the
382 # Calculates the width of the widest Y label. This will be the
383 # character height if the Y labels are rotated
383 # character height if the Y labels are rotated
384 def max_y_label_width_px
384 def max_y_label_width_px
385 return font_size if rotate_y_labels
385 return font_size if rotate_y_labels
386 end
386 end
387
387
388
388
389 # Override this (and call super) to change the margin to the right
389 # Override this (and call super) to change the margin to the right
390 # of the plot area. Results in @border_right being set.
390 # of the plot area. Results in @border_right being set.
391 def calculate_right_margin
391 def calculate_right_margin
392 @border_right = 7
392 @border_right = 7
393 if key and key_position == :right
393 if key and key_position == :right
394 val = keys.max { |a,b| a.length <=> b.length }
394 val = keys.max { |a,b| a.length <=> b.length }
395 @border_right += val.length * key_font_size * 0.6
395 @border_right += val.length * key_font_size * 0.6
396 @border_right += KEY_BOX_SIZE
396 @border_right += KEY_BOX_SIZE
397 @border_right += 10 # Some padding around the box
397 @border_right += 10 # Some padding around the box
398 end
398 end
399 end
399 end
400
400
401
401
402 # Override this (and call super) to change the margin to the top
402 # Override this (and call super) to change the margin to the top
403 # of the plot area. Results in @border_top being set.
403 # of the plot area. Results in @border_top being set.
404 def calculate_top_margin
404 def calculate_top_margin
405 @border_top = 5
405 @border_top = 5
406 @border_top += title_font_size if show_graph_title
406 @border_top += title_font_size if show_graph_title
407 @border_top += 5
407 @border_top += 5
408 @border_top += subtitle_font_size if show_graph_subtitle
408 @border_top += subtitle_font_size if show_graph_subtitle
409 end
409 end
410
410
411
411
412 # Adds pop-up point information to a graph.
412 # Adds pop-up point information to a graph.
413 def add_popup( x, y, label )
413 def add_popup( x, y, label )
414 txt_width = label.length * font_size * 0.6 + 10
414 txt_width = label.length * font_size * 0.6 + 10
415 tx = (x+txt_width > width ? x-5 : x+5)
415 tx = (x+txt_width > width ? x-5 : x+5)
416 t = @foreground.add_element( "text", {
416 t = @foreground.add_element( "text", {
417 "x" => tx.to_s,
417 "x" => tx.to_s,
418 "y" => (y - font_size).to_s,
418 "y" => (y - font_size).to_s,
419 "visibility" => "hidden",
419 "visibility" => "hidden",
420 })
420 })
421 t.attributes["style"] = "fill: #000; "+
421 t.attributes["style"] = "fill: #000; "+
422 (x+txt_width > width ? "text-anchor: end;" : "text-anchor: start;")
422 (x+txt_width > width ? "text-anchor: end;" : "text-anchor: start;")
423 t.text = label.to_s
423 t.text = label.to_s
424 t.attributes["id"] = t.object_id.to_s
424 t.attributes["id"] = t.object_id.to_s
425
425
426 @foreground.add_element( "circle", {
426 @foreground.add_element( "circle", {
427 "cx" => x.to_s,
427 "cx" => x.to_s,
428 "cy" => y.to_s,
428 "cy" => y.to_s,
429 "r" => "10",
429 "r" => "10",
430 "style" => "opacity: 0",
430 "style" => "opacity: 0",
431 "onmouseover" =>
431 "onmouseover" =>
432 "document.getElementById(#{t.object_id}).setAttribute('visibility', 'visible' )",
432 "document.getElementById(#{t.object_id}).setAttribute('visibility', 'visible' )",
433 "onmouseout" =>
433 "onmouseout" =>
434 "document.getElementById(#{t.object_id}).setAttribute('visibility', 'hidden' )",
434 "document.getElementById(#{t.object_id}).setAttribute('visibility', 'hidden' )",
435 })
435 })
436
436
437 end
437 end
438
438
439
439
440 # Override this (and call super) to change the margin to the bottom
440 # Override this (and call super) to change the margin to the bottom
441 # of the plot area. Results in @border_bottom being set.
441 # of the plot area. Results in @border_bottom being set.
442 def calculate_bottom_margin
442 def calculate_bottom_margin
443 @border_bottom = 7
443 @border_bottom = 7
444 if key and key_position == :bottom
444 if key and key_position == :bottom
445 @border_bottom += @data.size * (font_size + 5)
445 @border_bottom += @data.size * (font_size + 5)
446 @border_bottom += 10
446 @border_bottom += 10
447 end
447 end
448 if show_x_labels
448 if show_x_labels
449 max_x_label_height_px = (not rotate_x_labels) ?
449 max_x_label_height_px = (not rotate_x_labels) ?
450 x_label_font_size :
450 x_label_font_size :
451 get_x_labels.max{|a,b|
451 get_x_labels.max{|a,b|
452 a.to_s.length<=>b.to_s.length
452 a.to_s.length<=>b.to_s.length
453 }.to_s.length * x_label_font_size * 0.6
453 }.to_s.length * x_label_font_size * 0.6
454 @border_bottom += max_x_label_height_px
454 @border_bottom += max_x_label_height_px
455 @border_bottom += max_x_label_height_px + 10 if stagger_x_labels
455 @border_bottom += max_x_label_height_px + 10 if stagger_x_labels
456 end
456 end
457 @border_bottom += x_title_font_size + 5 if show_x_title
457 @border_bottom += x_title_font_size + 5 if show_x_title
458 end
458 end
459
459
460
460
461 # Draws the background, axis, and labels.
461 # Draws the background, axis, and labels.
462 def draw_graph
462 def draw_graph
463 @graph = @root.add_element( "g", {
463 @graph = @root.add_element( "g", {
464 "transform" => "translate( #@border_left #@border_top )"
464 "transform" => "translate( #@border_left #@border_top )"
465 })
465 })
466
466
467 # Background
467 # Background
468 @graph.add_element( "rect", {
468 @graph.add_element( "rect", {
469 "x" => "0",
469 "x" => "0",
470 "y" => "0",
470 "y" => "0",
471 "width" => @graph_width.to_s,
471 "width" => @graph_width.to_s,
472 "height" => @graph_height.to_s,
472 "height" => @graph_height.to_s,
473 "class" => "graphBackground"
473 "class" => "graphBackground"
474 })
474 })
475
475
476 # Axis
476 # Axis
477 @graph.add_element( "path", {
477 @graph.add_element( "path", {
478 "d" => "M 0 0 v#@graph_height",
478 "d" => "M 0 0 v#@graph_height",
479 "class" => "axis",
479 "class" => "axis",
480 "id" => "xAxis"
480 "id" => "xAxis"
481 })
481 })
482 @graph.add_element( "path", {
482 @graph.add_element( "path", {
483 "d" => "M 0 #@graph_height h#@graph_width",
483 "d" => "M 0 #@graph_height h#@graph_width",
484 "class" => "axis",
484 "class" => "axis",
485 "id" => "yAxis"
485 "id" => "yAxis"
486 })
486 })
487
487
488 draw_x_labels
488 draw_x_labels
489 draw_y_labels
489 draw_y_labels
490 end
490 end
491
491
492
492
493 # Where in the X area the label is drawn
493 # Where in the X area the label is drawn
494 # Centered in the field, should be width/2. Start, 0.
494 # Centered in the field, should be width/2. Start, 0.
495 def x_label_offset( width )
495 def x_label_offset( width )
496 0
496 0
497 end
497 end
498
498
499 def make_datapoint_text( x, y, value, style="" )
499 def make_datapoint_text( x, y, value, style="" )
500 if show_data_values
500 if show_data_values
501 @foreground.add_element( "text", {
501 @foreground.add_element( "text", {
502 "x" => x.to_s,
502 "x" => x.to_s,
503 "y" => y.to_s,
503 "y" => y.to_s,
504 "class" => "dataPointLabel",
504 "class" => "dataPointLabel",
505 "style" => "#{style} stroke: #fff; stroke-width: 2;"
505 "style" => "#{style} stroke: #fff; stroke-width: 2;"
506 }).text = value.to_s
506 }).text = value.to_s
507 text = @foreground.add_element( "text", {
507 text = @foreground.add_element( "text", {
508 "x" => x.to_s,
508 "x" => x.to_s,
509 "y" => y.to_s,
509 "y" => y.to_s,
510 "class" => "dataPointLabel"
510 "class" => "dataPointLabel"
511 })
511 })
512 text.text = value.to_s
512 text.text = value.to_s
513 text.attributes["style"] = style if style.length > 0
513 text.attributes["style"] = style if style.length > 0
514 end
514 end
515 end
515 end
516
516
517
517
518 # Draws the X axis labels
518 # Draws the X axis labels
519 def draw_x_labels
519 def draw_x_labels
520 stagger = x_label_font_size + 5
520 stagger = x_label_font_size + 5
521 if show_x_labels
521 if show_x_labels
522 label_width = field_width
522 label_width = field_width
523
523
524 count = 0
524 count = 0
525 for label in get_x_labels
525 for label in get_x_labels
526 if step_include_first_x_label == true then
526 if step_include_first_x_label == true then
527 step = count % step_x_labels
527 step = count % step_x_labels
528 else
528 else
529 step = (count + 1) % step_x_labels
529 step = (count + 1) % step_x_labels
530 end
530 end
531
531
532 if step == 0 then
532 if step == 0 then
533 text = @graph.add_element( "text" )
533 text = @graph.add_element( "text" )
534 text.attributes["class"] = "xAxisLabels"
534 text.attributes["class"] = "xAxisLabels"
535 text.text = label.to_s
535 text.text = label.to_s
536
536
537 x = count * label_width + x_label_offset( label_width )
537 x = count * label_width + x_label_offset( label_width )
538 y = @graph_height + x_label_font_size + 3
538 y = @graph_height + x_label_font_size + 3
539 t = 0 - (font_size / 2)
539 t = 0 - (font_size / 2)
540
540
541 if stagger_x_labels and count % 2 == 1
541 if stagger_x_labels and count % 2 == 1
542 y += stagger
542 y += stagger
543 @graph.add_element( "path", {
543 @graph.add_element( "path", {
544 "d" => "M#{x} #@graph_height v#{stagger}",
544 "d" => "M#{x} #@graph_height v#{stagger}",
545 "class" => "staggerGuideLine"
545 "class" => "staggerGuideLine"
546 })
546 })
547 end
547 end
548
548
549 text.attributes["x"] = x.to_s
549 text.attributes["x"] = x.to_s
550 text.attributes["y"] = y.to_s
550 text.attributes["y"] = y.to_s
551 if rotate_x_labels
551 if rotate_x_labels
552 text.attributes["transform"] =
552 text.attributes["transform"] =
553 "rotate( 90 #{x} #{y-x_label_font_size} )"+
553 "rotate( 90 #{x} #{y-x_label_font_size} )"+
554 " translate( 0 -#{x_label_font_size/4} )"
554 " translate( 0 -#{x_label_font_size/4} )"
555 text.attributes["style"] = "text-anchor: start"
555 text.attributes["style"] = "text-anchor: start"
556 else
556 else
557 text.attributes["style"] = "text-anchor: middle"
557 text.attributes["style"] = "text-anchor: middle"
558 end
558 end
559 end
559 end
560
560
561 draw_x_guidelines( label_width, count ) if show_x_guidelines
561 draw_x_guidelines( label_width, count ) if show_x_guidelines
562 count += 1
562 count += 1
563 end
563 end
564 end
564 end
565 end
565 end
566
566
567
567
568 # Where in the Y area the label is drawn
568 # Where in the Y area the label is drawn
569 # Centered in the field, should be width/2. Start, 0.
569 # Centered in the field, should be width/2. Start, 0.
570 def y_label_offset( height )
570 def y_label_offset( height )
571 0
571 0
572 end
572 end
573
573
574
574
575 def field_width
575 def field_width
576 (@graph_width.to_f - font_size*2*right_font) /
576 (@graph_width.to_f - font_size*2*right_font) /
577 (get_x_labels.length - right_align)
577 (get_x_labels.length - right_align)
578 end
578 end
579
579
580
580
581 def field_height
581 def field_height
582 (@graph_height.to_f - font_size*2*top_font) /
582 (@graph_height.to_f - font_size*2*top_font) /
583 (get_y_labels.length - top_align)
583 (get_y_labels.length - top_align)
584 end
584 end
585
585
586
586
587 # Draws the Y axis labels
587 # Draws the Y axis labels
588 def draw_y_labels
588 def draw_y_labels
589 stagger = y_label_font_size + 5
589 stagger = y_label_font_size + 5
590 if show_y_labels
590 if show_y_labels
591 label_height = field_height
591 label_height = field_height
592
592
593 count = 0
593 count = 0
594 y_offset = @graph_height + y_label_offset( label_height )
594 y_offset = @graph_height + y_label_offset( label_height )
595 y_offset += font_size/1.2 unless rotate_y_labels
595 y_offset += font_size/1.2 unless rotate_y_labels
596 for label in get_y_labels
596 for label in get_y_labels
597 y = y_offset - (label_height * count)
597 y = y_offset - (label_height * count)
598 x = rotate_y_labels ? 0 : -3
598 x = rotate_y_labels ? 0 : -3
599
599
600 if stagger_y_labels and count % 2 == 1
600 if stagger_y_labels and count % 2 == 1
601 x -= stagger
601 x -= stagger
602 @graph.add_element( "path", {
602 @graph.add_element( "path", {
603 "d" => "M#{x} #{y} h#{stagger}",
603 "d" => "M#{x} #{y} h#{stagger}",
604 "class" => "staggerGuideLine"
604 "class" => "staggerGuideLine"
605 })
605 })
606 end
606 end
607
607
608 text = @graph.add_element( "text", {
608 text = @graph.add_element( "text", {
609 "x" => x.to_s,
609 "x" => x.to_s,
610 "y" => y.to_s,
610 "y" => y.to_s,
611 "class" => "yAxisLabels"
611 "class" => "yAxisLabels"
612 })
612 })
613 text.text = label.to_s
613 text.text = label.to_s
614 if rotate_y_labels
614 if rotate_y_labels
615 text.attributes["transform"] = "translate( -#{font_size} 0 ) "+
615 text.attributes["transform"] = "translate( -#{font_size} 0 ) "+
616 "rotate( 90 #{x} #{y} ) "
616 "rotate( 90 #{x} #{y} ) "
617 text.attributes["style"] = "text-anchor: middle"
617 text.attributes["style"] = "text-anchor: middle"
618 else
618 else
619 text.attributes["y"] = (y - (y_label_font_size/2)).to_s
619 text.attributes["y"] = (y - (y_label_font_size/2)).to_s
620 text.attributes["style"] = "text-anchor: end"
620 text.attributes["style"] = "text-anchor: end"
621 end
621 end
622 draw_y_guidelines( label_height, count ) if show_y_guidelines
622 draw_y_guidelines( label_height, count ) if show_y_guidelines
623 count += 1
623 count += 1
624 end
624 end
625 end
625 end
626 end
626 end
627
627
628
628
629 # Draws the X axis guidelines
629 # Draws the X axis guidelines
630 def draw_x_guidelines( label_height, count )
630 def draw_x_guidelines( label_height, count )
631 if count != 0
631 if count != 0
632 @graph.add_element( "path", {
632 @graph.add_element( "path", {
633 "d" => "M#{label_height*count} 0 v#@graph_height",
633 "d" => "M#{label_height*count} 0 v#@graph_height",
634 "class" => "guideLines"
634 "class" => "guideLines"
635 })
635 })
636 end
636 end
637 end
637 end
638
638
639
639
640 # Draws the Y axis guidelines
640 # Draws the Y axis guidelines
641 def draw_y_guidelines( label_height, count )
641 def draw_y_guidelines( label_height, count )
642 if count != 0
642 if count != 0
643 @graph.add_element( "path", {
643 @graph.add_element( "path", {
644 "d" => "M0 #{@graph_height-(label_height*count)} h#@graph_width",
644 "d" => "M0 #{@graph_height-(label_height*count)} h#@graph_width",
645 "class" => "guideLines"
645 "class" => "guideLines"
646 })
646 })
647 end
647 end
648 end
648 end
649
649
650
650
651 # Draws the graph title and subtitle
651 # Draws the graph title and subtitle
652 def draw_titles
652 def draw_titles
653 if show_graph_title
653 if show_graph_title
654 @root.add_element( "text", {
654 @root.add_element( "text", {
655 "x" => (width / 2).to_s,
655 "x" => (width / 2).to_s,
656 "y" => (title_font_size).to_s,
656 "y" => (title_font_size).to_s,
657 "class" => "mainTitle"
657 "class" => "mainTitle"
658 }).text = graph_title.to_s
658 }).text = graph_title.to_s
659 end
659 end
660
660
661 if show_graph_subtitle
661 if show_graph_subtitle
662 y_subtitle = show_graph_title ?
662 y_subtitle = show_graph_title ?
663 title_font_size + 10 :
663 title_font_size + 10 :
664 subtitle_font_size
664 subtitle_font_size
665 @root.add_element("text", {
665 @root.add_element("text", {
666 "x" => (width / 2).to_s,
666 "x" => (width / 2).to_s,
667 "y" => (y_subtitle).to_s,
667 "y" => (y_subtitle).to_s,
668 "class" => "subTitle"
668 "class" => "subTitle"
669 }).text = graph_subtitle.to_s
669 }).text = graph_subtitle.to_s
670 end
670 end
671
671
672 if show_x_title
672 if show_x_title
673 y = @graph_height + @border_top + x_title_font_size
673 y = @graph_height + @border_top + x_title_font_size
674 if show_x_labels
674 if show_x_labels
675 y += x_label_font_size + 5 if stagger_x_labels
675 y += x_label_font_size + 5 if stagger_x_labels
676 y += x_label_font_size + 5
676 y += x_label_font_size + 5
677 end
677 end
678 x = width / 2
678 x = width / 2
679
679
680 @root.add_element("text", {
680 @root.add_element("text", {
681 "x" => x.to_s,
681 "x" => x.to_s,
682 "y" => y.to_s,
682 "y" => y.to_s,
683 "class" => "xAxisTitle",
683 "class" => "xAxisTitle",
684 }).text = x_title.to_s
684 }).text = x_title.to_s
685 end
685 end
686
686
687 if show_y_title
687 if show_y_title
688 x = y_title_font_size + (y_title_text_direction==:bt ? 3 : -3)
688 x = y_title_font_size + (y_title_text_direction==:bt ? 3 : -3)
689 y = height / 2
689 y = height / 2
690
690
691 text = @root.add_element("text", {
691 text = @root.add_element("text", {
692 "x" => x.to_s,
692 "x" => x.to_s,
693 "y" => y.to_s,
693 "y" => y.to_s,
694 "class" => "yAxisTitle",
694 "class" => "yAxisTitle",
695 })
695 })
696 text.text = y_title.to_s
696 text.text = y_title.to_s
697 if y_title_text_direction == :bt
697 if y_title_text_direction == :bt
698 text.attributes["transform"] = "rotate( -90, #{x}, #{y} )"
698 text.attributes["transform"] = "rotate( -90, #{x}, #{y} )"
699 else
699 else
700 text.attributes["transform"] = "rotate( 90, #{x}, #{y} )"
700 text.attributes["transform"] = "rotate( 90, #{x}, #{y} )"
701 end
701 end
702 end
702 end
703 end
703 end
704
704
705 def keys
705 def keys
706 return @data.collect{ |d| d[:title] }
706 return @data.collect{ |d| d[:title] }
707 end
707 end
708
708
709 # Draws the legend on the graph
709 # Draws the legend on the graph
710 def draw_legend
710 def draw_legend
711 if key
711 if key
712 group = @root.add_element( "g" )
712 group = @root.add_element( "g" )
713
713
714 key_count = 0
714 key_count = 0
715 for key_name in keys
715 for key_name in keys
716 y_offset = (KEY_BOX_SIZE * key_count) + (key_count * 5)
716 y_offset = (KEY_BOX_SIZE * key_count) + (key_count * 5)
717 group.add_element( "rect", {
717 group.add_element( "rect", {
718 "x" => 0.to_s,
718 "x" => 0.to_s,
719 "y" => y_offset.to_s,
719 "y" => y_offset.to_s,
720 "width" => KEY_BOX_SIZE.to_s,
720 "width" => KEY_BOX_SIZE.to_s,
721 "height" => KEY_BOX_SIZE.to_s,
721 "height" => KEY_BOX_SIZE.to_s,
722 "class" => "key#{key_count+1}"
722 "class" => "key#{key_count+1}"
723 })
723 })
724 group.add_element( "text", {
724 group.add_element( "text", {
725 "x" => (KEY_BOX_SIZE + 5).to_s,
725 "x" => (KEY_BOX_SIZE + 5).to_s,
726 "y" => (y_offset + KEY_BOX_SIZE).to_s,
726 "y" => (y_offset + KEY_BOX_SIZE).to_s,
727 "class" => "keyText"
727 "class" => "keyText"
728 }).text = key_name.to_s
728 }).text = key_name.to_s
729 key_count += 1
729 key_count += 1
730 end
730 end
731
731
732 case key_position
732 case key_position
733 when :right
733 when :right
734 x_offset = @graph_width + @border_left + 10
734 x_offset = @graph_width + @border_left + 10
735 y_offset = @border_top + 20
735 y_offset = @border_top + 20
736 when :bottom
736 when :bottom
737 x_offset = @border_left + 20
737 x_offset = @border_left + 20
738 y_offset = @border_top + @graph_height + 5
738 y_offset = @border_top + @graph_height + 5
739 if show_x_labels
739 if show_x_labels
740 max_x_label_height_px = (not rotate_x_labels) ?
740 max_x_label_height_px = (not rotate_x_labels) ?
741 x_label_font_size :
741 x_label_font_size :
742 get_x_labels.max{|a,b|
742 get_x_labels.max{|a,b|
743 a.to_s.length<=>b.to_s.length
743 a.to_s.length<=>b.to_s.length
744 }.to_s.length * x_label_font_size * 0.6
744 }.to_s.length * x_label_font_size * 0.6
745 x_label_font_size
745 x_label_font_size
746 y_offset += max_x_label_height_px
746 y_offset += max_x_label_height_px
747 y_offset += max_x_label_height_px + 5 if stagger_x_labels
747 y_offset += max_x_label_height_px + 5 if stagger_x_labels
748 end
748 end
749 y_offset += x_title_font_size + 5 if show_x_title
749 y_offset += x_title_font_size + 5 if show_x_title
750 end
750 end
751 group.attributes["transform"] = "translate(#{x_offset} #{y_offset})"
751 group.attributes["transform"] = "translate(#{x_offset} #{y_offset})"
752 end
752 end
753 end
753 end
754
754
755
755
756 private
756 private
757
757
758 def sort_multiple( arrys, lo=0, hi=arrys[0].length-1 )
758 def sort_multiple( arrys, lo=0, hi=arrys[0].length-1 )
759 if lo < hi
759 if lo < hi
760 p = partition(arrys,lo,hi)
760 p = partition(arrys,lo,hi)
761 sort_multiple(arrys, lo, p-1)
761 sort_multiple(arrys, lo, p-1)
762 sort_multiple(arrys, p+1, hi)
762 sort_multiple(arrys, p+1, hi)
763 end
763 end
764 arrys
764 arrys
765 end
765 end
766
766
767 def partition( arrys, lo, hi )
767 def partition( arrys, lo, hi )
768 p = arrys[0][lo]
768 p = arrys[0][lo]
769 l = lo
769 l = lo
770 z = lo+1
770 z = lo+1
771 while z <= hi
771 while z <= hi
772 if arrys[0][z] < p
772 if arrys[0][z] < p
773 l += 1
773 l += 1
774 arrys.each { |arry| arry[z], arry[l] = arry[l], arry[z] }
774 arrys.each { |arry| arry[z], arry[l] = arry[l], arry[z] }
775 end
775 end
776 z += 1
776 z += 1
777 end
777 end
778 arrys.each { |arry| arry[lo], arry[l] = arry[l], arry[lo] }
778 arrys.each { |arry| arry[lo], arry[l] = arry[l], arry[lo] }
779 l
779 l
780 end
780 end
781
781
782 def style
782 def style
783 if no_css
783 if no_css
784 styles = parse_css
784 styles = parse_css
785 @root.elements.each("//*[@class]") { |el|
785 @root.elements.each("//*[@class]") { |el|
786 cl = el.attributes["class"]
786 cl = el.attributes["class"]
787 style = styles[cl]
787 style = styles[cl]
788 style += el.attributes["style"] if el.attributes["style"]
788 style += el.attributes["style"] if el.attributes["style"]
789 el.attributes["style"] = style
789 el.attributes["style"] = style
790 }
790 }
791 end
791 end
792 end
792 end
793
793
794 def parse_css
794 def parse_css
795 css = get_style
795 css = get_style
796 rv = {}
796 rv = {}
797 while css =~ /^(\.(\w+)(?:\s*,\s*\.\w+)*)\s*\{/m
797 while css =~ /^(\.(\w+)(?:\s*,\s*\.\w+)*)\s*\{/m
798 names_orig = names = $1
798 names_orig = names = $1
799 css = $'
799 css = $'
800 css =~ /([^}]+)\}/m
800 css =~ /([^}]+)\}/m
801 content = $1
801 content = $1
802 css = $'
802 css = $'
803
803
804 nms = []
804 nms = []
805 while names =~ /^\s*,?\s*\.(\w+)/
805 while names =~ /^\s*,?\s*\.(\w+)/
806 nms << $1
806 nms << $1
807 names = $'
807 names = $'
808 end
808 end
809
809
810 content = content.tr( "\n\t", " ")
810 content = content.tr( "\n\t", " ")
811 for name in nms
811 for name in nms
812 current = rv[name]
812 current = rv[name]
813 current = current ? current+"; "+content : content
813 current = current ? current+"; "+content : content
814 rv[name] = current.strip.squeeze(" ")
814 rv[name] = current.strip.squeeze(" ")
815 end
815 end
816 end
816 end
817 return rv
817 return rv
818 end
818 end
819
819
820
820
821 # Override and place code to add defs here
821 # Override and place code to add defs here
822 def add_defs defs
822 def add_defs defs
823 end
823 end
824
824
825
825
826 def start_svg
826 def start_svg
827 # Base document
827 # Base document
828 @doc = Document.new
828 @doc = Document.new
829 @doc << XMLDecl.new
829 @doc << XMLDecl.new
830 @doc << DocType.new( %q{svg PUBLIC "-//W3C//DTD SVG 1.0//EN" } +
830 @doc << DocType.new( %q{svg PUBLIC "-//W3C//DTD SVG 1.0//EN" } +
831 %q{"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"} )
831 %q{"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"} )
832 if style_sheet && style_sheet != ''
832 if style_sheet && style_sheet != ''
833 @doc << Instruction.new( "xml-stylesheet",
833 @doc << Instruction.new( "xml-stylesheet",
834 %Q{href="#{style_sheet}" type="text/css"} )
834 %Q{href="#{style_sheet}" type="text/css"} )
835 end
835 end
836 @root = @doc.add_element( "svg", {
836 @root = @doc.add_element( "svg", {
837 "width" => width.to_s,
837 "width" => width.to_s,
838 "height" => height.to_s,
838 "height" => height.to_s,
839 "viewBox" => "0 0 #{width} #{height}",
839 "viewBox" => "0 0 #{width} #{height}",
840 "xmlns" => "http://www.w3.org/2000/svg",
840 "xmlns" => "http://www.w3.org/2000/svg",
841 "xmlns:xlink" => "http://www.w3.org/1999/xlink",
841 "xmlns:xlink" => "http://www.w3.org/1999/xlink",
842 "xmlns:a3" => "http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/",
842 "xmlns:a3" => "http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/",
843 "a3:scriptImplementation" => "Adobe"
843 "a3:scriptImplementation" => "Adobe"
844 })
844 })
845 @root << Comment.new( " "+"\\"*66 )
845 @root << Comment.new( " "+"\\"*66 )
846 @root << Comment.new( " Created with SVG::Graph " )
846 @root << Comment.new( " Created with SVG::Graph " )
847 @root << Comment.new( " SVG::Graph by Sean E. Russell " )
847 @root << Comment.new( " SVG::Graph by Sean E. Russell " )
848 @root << Comment.new( " Losely based on SVG::TT::Graph for Perl by"+
848 @root << Comment.new( " Losely based on SVG::TT::Graph for Perl by"+
849 " Leo Lapworth & Stephan Morgan " )
849 " Leo Lapworth & Stephan Morgan " )
850 @root << Comment.new( " "+"/"*66 )
850 @root << Comment.new( " "+"/"*66 )
851
851
852 defs = @root.add_element( "defs" )
852 defs = @root.add_element( "defs" )
853 add_defs defs
853 add_defs defs
854 if not(style_sheet && style_sheet != '') and !no_css
854 if not(style_sheet && style_sheet != '') and !no_css
855 @root << Comment.new(" include default stylesheet if none specified ")
855 @root << Comment.new(" include default stylesheet if none specified ")
856 style = defs.add_element( "style", {"type"=>"text/css"} )
856 style = defs.add_element( "style", {"type"=>"text/css"} )
857 style << CData.new( get_style )
857 style << CData.new( get_style )
858 end
858 end
859
859
860 @root << Comment.new( "SVG Background" )
860 @root << Comment.new( "SVG Background" )
861 @root.add_element( "rect", {
861 @root.add_element( "rect", {
862 "width" => width.to_s,
862 "width" => width.to_s,
863 "height" => height.to_s,
863 "height" => height.to_s,
864 "x" => "0",
864 "x" => "0",
865 "y" => "0",
865 "y" => "0",
866 "class" => "svgBackground"
866 "class" => "svgBackground"
867 })
867 })
868 end
868 end
869
869
870
870
871 def calculate_graph_dimensions
871 def calculate_graph_dimensions
872 calculate_left_margin
872 calculate_left_margin
873 calculate_right_margin
873 calculate_right_margin
874 calculate_bottom_margin
874 calculate_bottom_margin
875 calculate_top_margin
875 calculate_top_margin
876 @graph_width = width - @border_left - @border_right
876 @graph_width = width - @border_left - @border_right
877 @graph_height = height - @border_top - @border_bottom
877 @graph_height = height - @border_top - @border_bottom
878 end
878 end
879
879
880 def get_style
880 def get_style
881 return <<EOL
881 return <<EOL
882 /* Copy from here for external style sheet */
882 /* Copy from here for external style sheet */
883 .svgBackground{
883 .svgBackground{
884 fill:#ffffff;
884 fill:#ffffff;
885 }
885 }
886 .graphBackground{
886 .graphBackground{
887 fill:#f0f0f0;
887 fill:#f0f0f0;
888 }
888 }
889
889
890 /* graphs titles */
890 /* graphs titles */
891 .mainTitle{
891 .mainTitle{
892 text-anchor: middle;
892 text-anchor: middle;
893 fill: #000000;
893 fill: #000000;
894 font-size: #{title_font_size}px;
894 font-size: #{title_font_size}px;
895 font-family: "Arial", sans-serif;
895 font-family: "Arial", sans-serif;
896 font-weight: normal;
896 font-weight: normal;
897 }
897 }
898 .subTitle{
898 .subTitle{
899 text-anchor: middle;
899 text-anchor: middle;
900 fill: #999999;
900 fill: #999999;
901 font-size: #{subtitle_font_size}px;
901 font-size: #{subtitle_font_size}px;
902 font-family: "Arial", sans-serif;
902 font-family: "Arial", sans-serif;
903 font-weight: normal;
903 font-weight: normal;
904 }
904 }
905
905
906 .axis{
906 .axis{
907 stroke: #000000;
907 stroke: #000000;
908 stroke-width: 1px;
908 stroke-width: 1px;
909 }
909 }
910
910
911 .guideLines{
911 .guideLines{
912 stroke: #666666;
912 stroke: #666666;
913 stroke-width: 1px;
913 stroke-width: 1px;
914 stroke-dasharray: 5 5;
914 stroke-dasharray: 5 5;
915 }
915 }
916
916
917 .xAxisLabels{
917 .xAxisLabels{
918 text-anchor: middle;
918 text-anchor: middle;
919 fill: #000000;
919 fill: #000000;
920 font-size: #{x_label_font_size}px;
920 font-size: #{x_label_font_size}px;
921 font-family: "Arial", sans-serif;
921 font-family: "Arial", sans-serif;
922 font-weight: normal;
922 font-weight: normal;
923 }
923 }
924
924
925 .yAxisLabels{
925 .yAxisLabels{
926 text-anchor: end;
926 text-anchor: end;
927 fill: #000000;
927 fill: #000000;
928 font-size: #{y_label_font_size}px;
928 font-size: #{y_label_font_size}px;
929 font-family: "Arial", sans-serif;
929 font-family: "Arial", sans-serif;
930 font-weight: normal;
930 font-weight: normal;
931 }
931 }
932
932
933 .xAxisTitle{
933 .xAxisTitle{
934 text-anchor: middle;
934 text-anchor: middle;
935 fill: #ff0000;
935 fill: #ff0000;
936 font-size: #{x_title_font_size}px;
936 font-size: #{x_title_font_size}px;
937 font-family: "Arial", sans-serif;
937 font-family: "Arial", sans-serif;
938 font-weight: normal;
938 font-weight: normal;
939 }
939 }
940
940
941 .yAxisTitle{
941 .yAxisTitle{
942 fill: #ff0000;
942 fill: #ff0000;
943 text-anchor: middle;
943 text-anchor: middle;
944 font-size: #{y_title_font_size}px;
944 font-size: #{y_title_font_size}px;
945 font-family: "Arial", sans-serif;
945 font-family: "Arial", sans-serif;
946 font-weight: normal;
946 font-weight: normal;
947 }
947 }
948
948
949 .dataPointLabel{
949 .dataPointLabel{
950 fill: #000000;
950 fill: #000000;
951 text-anchor:middle;
951 text-anchor:middle;
952 font-size: 10px;
952 font-size: 10px;
953 font-family: "Arial", sans-serif;
953 font-family: "Arial", sans-serif;
954 font-weight: normal;
954 font-weight: normal;
955 }
955 }
956
956
957 .staggerGuideLine{
957 .staggerGuideLine{
958 fill: none;
958 fill: none;
959 stroke: #000000;
959 stroke: #000000;
960 stroke-width: 0.5px;
960 stroke-width: 0.5px;
961 }
961 }
962
962
963 #{get_css}
963 #{get_css}
964
964
965 .keyText{
965 .keyText{
966 fill: #000000;
966 fill: #000000;
967 text-anchor:start;
967 text-anchor:start;
968 font-size: #{key_font_size}px;
968 font-size: #{key_font_size}px;
969 font-family: "Arial", sans-serif;
969 font-family: "Arial", sans-serif;
970 font-weight: normal;
970 font-weight: normal;
971 }
971 }
972 /* End copy for external style sheet */
972 /* End copy for external style sheet */
973 EOL
973 EOL
974 end
974 end
975
975
976 end
976 end
977 end
977 end
978 end
978 end
This diff has been collapsed as it changes many lines, (790 lines changed) Show them Hide them
@@ -1,395 +1,395
1 require 'SVG/Graph/Graph'
1 require 'SVG/Graph/Graph'
2
2
3 module SVG
3 module SVG
4 module Graph
4 module Graph
5 # === Create presentation quality SVG pie graphs easily
5 # === Create presentation quality SVG pie graphs easily
6 #
6 #
7 # == Synopsis
7 # == Synopsis
8 #
8 #
9 # require 'SVG/Graph/Pie'
9 # require 'SVG/Graph/Pie'
10 #
10 #
11 # fields = %w(Jan Feb Mar)
11 # fields = %w(Jan Feb Mar)
12 # data_sales_02 = [12, 45, 21]
12 # data_sales_02 = [12, 45, 21]
13 #
13 #
14 # graph = SVG::Graph::Pie.new({
14 # graph = SVG::Graph::Pie.new({
15 # :height => 500,
15 # :height => 500,
16 # :width => 300,
16 # :width => 300,
17 # :fields => fields,
17 # :fields => fields,
18 # })
18 # })
19 #
19 #
20 # graph.add_data({
20 # graph.add_data({
21 # :data => data_sales_02,
21 # :data => data_sales_02,
22 # :title => 'Sales 2002',
22 # :title => 'Sales 2002',
23 # })
23 # })
24 #
24 #
25 # print "Content-type: image/svg+xml\r\n\r\n"
25 # print "Content-type: image/svg+xml\r\n\r\n"
26 # print graph.burn();
26 # print graph.burn();
27 #
27 #
28 # == Description
28 # == Description
29 #
29 #
30 # This object aims to allow you to easily create high quality
30 # This object aims to allow you to easily create high quality
31 # SVG pie graphs. You can either use the default style sheet
31 # SVG pie graphs. You can either use the default style sheet
32 # or supply your own. Either way there are many options which can
32 # or supply your own. Either way there are many options which can
33 # be configured to give you control over how the graph is
33 # be configured to give you control over how the graph is
34 # generated - with or without a key, display percent on pie chart,
34 # generated - with or without a key, display percent on pie chart,
35 # title, subtitle etc.
35 # title, subtitle etc.
36 #
36 #
37 # = Examples
37 # = Examples
38 #
38 #
39 # http://www.germane-software/repositories/public/SVG/test/single.rb
39 # http://www.germane-software/repositories/public/SVG/test/single.rb
40 #
40 #
41 # == See also
41 # == See also
42 #
42 #
43 # * SVG::Graph::Graph
43 # * SVG::Graph::Graph
44 # * SVG::Graph::BarHorizontal
44 # * SVG::Graph::BarHorizontal
45 # * SVG::Graph::Bar
45 # * SVG::Graph::Bar
46 # * SVG::Graph::Line
46 # * SVG::Graph::Line
47 # * SVG::Graph::Plot
47 # * SVG::Graph::Plot
48 # * SVG::Graph::TimeSeries
48 # * SVG::Graph::TimeSeries
49 #
49 #
50 # == Author
50 # == Author
51 #
51 #
52 # Sean E. Russell <serATgermaneHYPHENsoftwareDOTcom>
52 # Sean E. Russell <serATgermaneHYPHENsoftwareDOTcom>
53 #
53 #
54 # Copyright 2004 Sean E. Russell
54 # Copyright 2004 Sean E. Russell
55 # This software is available under the Ruby license[LICENSE.txt]
55 # This software is available under the Ruby license[LICENSE.txt]
56 #
56 #
57 class Pie < Graph
57 class Pie < Graph
58 # Defaults are those set by Graph::initialize, and
58 # Defaults are those set by Graph::initialize, and
59 # [show_shadow] true
59 # [show_shadow] true
60 # [shadow_offset] 10
60 # [shadow_offset] 10
61 # [show_data_labels] false
61 # [show_data_labels] false
62 # [show_actual_values] false
62 # [show_actual_values] false
63 # [show_percent] true
63 # [show_percent] true
64 # [show_key_data_labels] true
64 # [show_key_data_labels] true
65 # [show_key_actual_values] true
65 # [show_key_actual_values] true
66 # [show_key_percent] false
66 # [show_key_percent] false
67 # [expanded] false
67 # [expanded] false
68 # [expand_greatest] false
68 # [expand_greatest] false
69 # [expand_gap] 10
69 # [expand_gap] 10
70 # [show_x_labels] false
70 # [show_x_labels] false
71 # [show_y_labels] false
71 # [show_y_labels] false
72 # [datapoint_font_size] 12
72 # [datapoint_font_size] 12
73 def set_defaults
73 def set_defaults
74 init_with(
74 init_with(
75 :show_shadow => true,
75 :show_shadow => true,
76 :shadow_offset => 10,
76 :shadow_offset => 10,
77
77
78 :show_data_labels => false,
78 :show_data_labels => false,
79 :show_actual_values => false,
79 :show_actual_values => false,
80 :show_percent => true,
80 :show_percent => true,
81
81
82 :show_key_data_labels => true,
82 :show_key_data_labels => true,
83 :show_key_actual_values => true,
83 :show_key_actual_values => true,
84 :show_key_percent => false,
84 :show_key_percent => false,
85
85
86 :expanded => false,
86 :expanded => false,
87 :expand_greatest => false,
87 :expand_greatest => false,
88 :expand_gap => 10,
88 :expand_gap => 10,
89
89
90 :show_x_labels => false,
90 :show_x_labels => false,
91 :show_y_labels => false,
91 :show_y_labels => false,
92 :datapoint_font_size => 12
92 :datapoint_font_size => 12
93 )
93 )
94 @data = []
94 @data = []
95 end
95 end
96
96
97 # Adds a data set to the graph.
97 # Adds a data set to the graph.
98 #
98 #
99 # graph.add_data( { :data => [1,2,3,4] } )
99 # graph.add_data( { :data => [1,2,3,4] } )
100 #
100 #
101 # Note that the :title is not necessary. If multiple
101 # Note that the :title is not necessary. If multiple
102 # data sets are added to the graph, the pie chart will
102 # data sets are added to the graph, the pie chart will
103 # display the +sums+ of the data. EG:
103 # display the +sums+ of the data. EG:
104 #
104 #
105 # graph.add_data( { :data => [1,2,3,4] } )
105 # graph.add_data( { :data => [1,2,3,4] } )
106 # graph.add_data( { :data => [2,3,5,9] } )
106 # graph.add_data( { :data => [2,3,5,9] } )
107 #
107 #
108 # is the same as:
108 # is the same as:
109 #
109 #
110 # graph.add_data( { :data => [3,5,8,13] } )
110 # graph.add_data( { :data => [3,5,8,13] } )
111 def add_data arg
111 def add_data arg
112 arg[:data].each_index {|idx|
112 arg[:data].each_index {|idx|
113 @data[idx] = 0 unless @data[idx]
113 @data[idx] = 0 unless @data[idx]
114 @data[idx] += arg[:data][idx]
114 @data[idx] += arg[:data][idx]
115 }
115 }
116 end
116 end
117
117
118 # If true, displays a drop shadow for the chart
118 # If true, displays a drop shadow for the chart
119 attr_accessor :show_shadow
119 attr_accessor :show_shadow
120 # Sets the offset of the shadow from the pie chart
120 # Sets the offset of the shadow from the pie chart
121 attr_accessor :shadow_offset
121 attr_accessor :shadow_offset
122 # If true, display the data labels on the chart
122 # If true, display the data labels on the chart
123 attr_accessor :show_data_labels
123 attr_accessor :show_data_labels
124 # If true, display the actual field values in the data labels
124 # If true, display the actual field values in the data labels
125 attr_accessor :show_actual_values
125 attr_accessor :show_actual_values
126 # If true, display the percentage value of each pie wedge in the data
126 # If true, display the percentage value of each pie wedge in the data
127 # labels
127 # labels
128 attr_accessor :show_percent
128 attr_accessor :show_percent
129 # If true, display the labels in the key
129 # If true, display the labels in the key
130 attr_accessor :show_key_data_labels
130 attr_accessor :show_key_data_labels
131 # If true, display the actual value of the field in the key
131 # If true, display the actual value of the field in the key
132 attr_accessor :show_key_actual_values
132 attr_accessor :show_key_actual_values
133 # If true, display the percentage value of the wedges in the key
133 # If true, display the percentage value of the wedges in the key
134 attr_accessor :show_key_percent
134 attr_accessor :show_key_percent
135 # If true, "explode" the pie (put space between the wedges)
135 # If true, "explode" the pie (put space between the wedges)
136 attr_accessor :expanded
136 attr_accessor :expanded
137 # If true, expand the largest pie wedge
137 # If true, expand the largest pie wedge
138 attr_accessor :expand_greatest
138 attr_accessor :expand_greatest
139 # The amount of space between expanded wedges
139 # The amount of space between expanded wedges
140 attr_accessor :expand_gap
140 attr_accessor :expand_gap
141 # The font size of the data point labels
141 # The font size of the data point labels
142 attr_accessor :datapoint_font_size
142 attr_accessor :datapoint_font_size
143
143
144
144
145 protected
145 protected
146
146
147 def add_defs defs
147 def add_defs defs
148 gradient = defs.add_element( "filter", {
148 gradient = defs.add_element( "filter", {
149 "id"=>"dropshadow",
149 "id"=>"dropshadow",
150 "width" => "1.2",
150 "width" => "1.2",
151 "height" => "1.2",
151 "height" => "1.2",
152 } )
152 } )
153 gradient.add_element( "feGaussianBlur", {
153 gradient.add_element( "feGaussianBlur", {
154 "stdDeviation" => "4",
154 "stdDeviation" => "4",
155 "result" => "blur"
155 "result" => "blur"
156 })
156 })
157 end
157 end
158
158
159 # We don't need the graph
159 # We don't need the graph
160 def draw_graph
160 def draw_graph
161 end
161 end
162
162
163 def get_y_labels
163 def get_y_labels
164 [""]
164 [""]
165 end
165 end
166
166
167 def get_x_labels
167 def get_x_labels
168 [""]
168 [""]
169 end
169 end
170
170
171 def keys
171 def keys
172 total = 0
172 total = 0
173 max_value = 0
173 max_value = 0
174 @data.each {|x| total += x }
174 @data.each {|x| total += x }
175 percent_scale = 100.0 / total
175 percent_scale = 100.0 / total
176 count = -1
176 count = -1
177 a = @config[:fields].collect{ |x|
177 a = @config[:fields].collect{ |x|
178 count += 1
178 count += 1
179 v = @data[count]
179 v = @data[count]
180 perc = show_key_percent ? " "+(v * percent_scale).round.to_s+"%" : ""
180 perc = show_key_percent ? " "+(v * percent_scale).round.to_s+"%" : ""
181 x + " [" + v.to_s + "]" + perc
181 x + " [" + v.to_s + "]" + perc
182 }
182 }
183 end
183 end
184
184
185 RADIANS = Math::PI/180
185 RADIANS = Math::PI/180
186
186
187 def draw_data
187 def draw_data
188 @graph = @root.add_element( "g" )
188 @graph = @root.add_element( "g" )
189 background = @graph.add_element("g")
189 background = @graph.add_element("g")
190 midground = @graph.add_element("g")
190 midground = @graph.add_element("g")
191
191
192 diameter = @graph_height > @graph_width ? @graph_width : @graph_height
192 diameter = @graph_height > @graph_width ? @graph_width : @graph_height
193 diameter -= expand_gap if expanded or expand_greatest
193 diameter -= expand_gap if expanded or expand_greatest
194 diameter -= datapoint_font_size if show_data_labels
194 diameter -= datapoint_font_size if show_data_labels
195 diameter -= 10 if show_shadow
195 diameter -= 10 if show_shadow
196 radius = diameter / 2.0
196 radius = diameter / 2.0
197
197
198 xoff = (width - diameter) / 2
198 xoff = (width - diameter) / 2
199 yoff = (height - @border_bottom - diameter)
199 yoff = (height - @border_bottom - diameter)
200 yoff -= 10 if show_shadow
200 yoff -= 10 if show_shadow
201 @graph.attributes['transform'] = "translate( #{xoff} #{yoff} )"
201 @graph.attributes['transform'] = "translate( #{xoff} #{yoff} )"
202
202
203 wedge_text_pad = 5
203 wedge_text_pad = 5
204 wedge_text_pad = 20 if show_percent and show_data_labels
204 wedge_text_pad = 20 if show_percent and show_data_labels
205
205
206 total = 0
206 total = 0
207 max_value = 0
207 max_value = 0
208 @data.each {|x|
208 @data.each {|x|
209 max_value = max_value < x ? x : max_value
209 max_value = max_value < x ? x : max_value
210 total += x
210 total += x
211 }
211 }
212 percent_scale = 100.0 / total
212 percent_scale = 100.0 / total
213
213
214 prev_percent = 0
214 prev_percent = 0
215 rad_mult = 3.6 * RADIANS
215 rad_mult = 3.6 * RADIANS
216 @config[:fields].each_index { |count|
216 @config[:fields].each_index { |count|
217 value = @data[count]
217 value = @data[count]
218 percent = percent_scale * value
218 percent = percent_scale * value
219
219
220 radians = prev_percent * rad_mult
220 radians = prev_percent * rad_mult
221 x_start = radius+(Math.sin(radians) * radius)
221 x_start = radius+(Math.sin(radians) * radius)
222 y_start = radius-(Math.cos(radians) * radius)
222 y_start = radius-(Math.cos(radians) * radius)
223 radians = (prev_percent+percent) * rad_mult
223 radians = (prev_percent+percent) * rad_mult
224 x_end = radius+(Math.sin(radians) * radius)
224 x_end = radius+(Math.sin(radians) * radius)
225 x_end -= 0.00001 if @data.length == 1
225 x_end -= 0.00001 if @data.length == 1
226 y_end = radius-(Math.cos(radians) * radius)
226 y_end = radius-(Math.cos(radians) * radius)
227 path = "M#{radius},#{radius} L#{x_start},#{y_start} "+
227 path = "M#{radius},#{radius} L#{x_start},#{y_start} "+
228 "A#{radius},#{radius} "+
228 "A#{radius},#{radius} "+
229 "0, #{percent >= 50 ? '1' : '0'},1, "+
229 "0, #{percent >= 50 ? '1' : '0'},1, "+
230 "#{x_end} #{y_end} Z"
230 "#{x_end} #{y_end} Z"
231
231
232
232
233 wedge = @foreground.add_element( "path", {
233 wedge = @foreground.add_element( "path", {
234 "d" => path,
234 "d" => path,
235 "class" => "fill#{count+1}"
235 "class" => "fill#{count+1}"
236 })
236 })
237
237
238 translate = nil
238 translate = nil
239 tx = 0
239 tx = 0
240 ty = 0
240 ty = 0
241 half_percent = prev_percent + percent / 2
241 half_percent = prev_percent + percent / 2
242 radians = half_percent * rad_mult
242 radians = half_percent * rad_mult
243
243
244 if show_shadow
244 if show_shadow
245 shadow = background.add_element( "path", {
245 shadow = background.add_element( "path", {
246 "d" => path,
246 "d" => path,
247 "filter" => "url(#dropshadow)",
247 "filter" => "url(#dropshadow)",
248 "style" => "fill: #ccc; stroke: none;"
248 "style" => "fill: #ccc; stroke: none;"
249 })
249 })
250 clear = midground.add_element( "path", {
250 clear = midground.add_element( "path", {
251 "d" => path,
251 "d" => path,
252 "style" => "fill: #fff; stroke: none;"
252 "style" => "fill: #fff; stroke: none;"
253 })
253 })
254 end
254 end
255
255
256 if expanded or (expand_greatest && value == max_value)
256 if expanded or (expand_greatest && value == max_value)
257 tx = (Math.sin(radians) * expand_gap)
257 tx = (Math.sin(radians) * expand_gap)
258 ty = -(Math.cos(radians) * expand_gap)
258 ty = -(Math.cos(radians) * expand_gap)
259 translate = "translate( #{tx} #{ty} )"
259 translate = "translate( #{tx} #{ty} )"
260 wedge.attributes["transform"] = translate
260 wedge.attributes["transform"] = translate
261 clear.attributes["transform"] = translate if clear
261 clear.attributes["transform"] = translate if clear
262 end
262 end
263
263
264 if show_shadow
264 if show_shadow
265 shadow.attributes["transform"] =
265 shadow.attributes["transform"] =
266 "translate( #{tx+shadow_offset} #{ty+shadow_offset} )"
266 "translate( #{tx+shadow_offset} #{ty+shadow_offset} )"
267 end
267 end
268
268
269 if show_data_labels and value != 0
269 if show_data_labels and value != 0
270 label = ""
270 label = ""
271 label += @config[:fields][count] if show_key_data_labels
271 label += @config[:fields][count] if show_key_data_labels
272 label += " ["+value.to_s+"]" if show_actual_values
272 label += " ["+value.to_s+"]" if show_actual_values
273 label += " "+percent.round.to_s+"%" if show_percent
273 label += " "+percent.round.to_s+"%" if show_percent
274
274
275 msr = Math.sin(radians)
275 msr = Math.sin(radians)
276 mcr = Math.cos(radians)
276 mcr = Math.cos(radians)
277 tx = radius + (msr * radius)
277 tx = radius + (msr * radius)
278 ty = radius -(mcr * radius)
278 ty = radius -(mcr * radius)
279
279
280 if expanded or (expand_greatest && value == max_value)
280 if expanded or (expand_greatest && value == max_value)
281 tx += (msr * expand_gap)
281 tx += (msr * expand_gap)
282 ty -= (mcr * expand_gap)
282 ty -= (mcr * expand_gap)
283 end
283 end
284 @foreground.add_element( "text", {
284 @foreground.add_element( "text", {
285 "x" => tx.to_s,
285 "x" => tx.to_s,
286 "y" => ty.to_s,
286 "y" => ty.to_s,
287 "class" => "dataPointLabel",
287 "class" => "dataPointLabel",
288 "style" => "stroke: #fff; stroke-width: 2;"
288 "style" => "stroke: #fff; stroke-width: 2;"
289 }).text = label.to_s
289 }).text = label.to_s
290 @foreground.add_element( "text", {
290 @foreground.add_element( "text", {
291 "x" => tx.to_s,
291 "x" => tx.to_s,
292 "y" => ty.to_s,
292 "y" => ty.to_s,
293 "class" => "dataPointLabel",
293 "class" => "dataPointLabel",
294 }).text = label.to_s
294 }).text = label.to_s
295 end
295 end
296
296
297 prev_percent += percent
297 prev_percent += percent
298 }
298 }
299 end
299 end
300
300
301
301
302 def round val, to
302 def round val, to
303 up = 10**to.to_f
303 up = 10**to.to_f
304 (val * up).to_i / up
304 (val * up).to_i / up
305 end
305 end
306
306
307
307
308 def get_css
308 def get_css
309 return <<EOL
309 return <<EOL
310 .dataPointLabel{
310 .dataPointLabel{
311 fill: #000000;
311 fill: #000000;
312 text-anchor:middle;
312 text-anchor:middle;
313 font-size: #{datapoint_font_size}px;
313 font-size: #{datapoint_font_size}px;
314 font-family: "Arial", sans-serif;
314 font-family: "Arial", sans-serif;
315 font-weight: normal;
315 font-weight: normal;
316 }
316 }
317
317
318 /* key - MUST match fill styles */
318 /* key - MUST match fill styles */
319 .key1,.fill1{
319 .key1,.fill1{
320 fill: #ff0000;
320 fill: #ff0000;
321 fill-opacity: 0.7;
321 fill-opacity: 0.7;
322 stroke: none;
322 stroke: none;
323 stroke-width: 1px;
323 stroke-width: 1px;
324 }
324 }
325 .key2,.fill2{
325 .key2,.fill2{
326 fill: #0000ff;
326 fill: #0000ff;
327 fill-opacity: 0.7;
327 fill-opacity: 0.7;
328 stroke: none;
328 stroke: none;
329 stroke-width: 1px;
329 stroke-width: 1px;
330 }
330 }
331 .key3,.fill3{
331 .key3,.fill3{
332 fill-opacity: 0.7;
332 fill-opacity: 0.7;
333 fill: #00ff00;
333 fill: #00ff00;
334 stroke: none;
334 stroke: none;
335 stroke-width: 1px;
335 stroke-width: 1px;
336 }
336 }
337 .key4,.fill4{
337 .key4,.fill4{
338 fill-opacity: 0.7;
338 fill-opacity: 0.7;
339 fill: #ffcc00;
339 fill: #ffcc00;
340 stroke: none;
340 stroke: none;
341 stroke-width: 1px;
341 stroke-width: 1px;
342 }
342 }
343 .key5,.fill5{
343 .key5,.fill5{
344 fill-opacity: 0.7;
344 fill-opacity: 0.7;
345 fill: #00ccff;
345 fill: #00ccff;
346 stroke: none;
346 stroke: none;
347 stroke-width: 1px;
347 stroke-width: 1px;
348 }
348 }
349 .key6,.fill6{
349 .key6,.fill6{
350 fill-opacity: 0.7;
350 fill-opacity: 0.7;
351 fill: #ff00ff;
351 fill: #ff00ff;
352 stroke: none;
352 stroke: none;
353 stroke-width: 1px;
353 stroke-width: 1px;
354 }
354 }
355 .key7,.fill7{
355 .key7,.fill7{
356 fill-opacity: 0.7;
356 fill-opacity: 0.7;
357 fill: #00ff99;
357 fill: #00ff99;
358 stroke: none;
358 stroke: none;
359 stroke-width: 1px;
359 stroke-width: 1px;
360 }
360 }
361 .key8,.fill8{
361 .key8,.fill8{
362 fill-opacity: 0.7;
362 fill-opacity: 0.7;
363 fill: #ffff00;
363 fill: #ffff00;
364 stroke: none;
364 stroke: none;
365 stroke-width: 1px;
365 stroke-width: 1px;
366 }
366 }
367 .key9,.fill9{
367 .key9,.fill9{
368 fill-opacity: 0.7;
368 fill-opacity: 0.7;
369 fill: #cc6666;
369 fill: #cc6666;
370 stroke: none;
370 stroke: none;
371 stroke-width: 1px;
371 stroke-width: 1px;
372 }
372 }
373 .key10,.fill10{
373 .key10,.fill10{
374 fill-opacity: 0.7;
374 fill-opacity: 0.7;
375 fill: #663399;
375 fill: #663399;
376 stroke: none;
376 stroke: none;
377 stroke-width: 1px;
377 stroke-width: 1px;
378 }
378 }
379 .key11,.fill11{
379 .key11,.fill11{
380 fill-opacity: 0.7;
380 fill-opacity: 0.7;
381 fill: #339900;
381 fill: #339900;
382 stroke: none;
382 stroke: none;
383 stroke-width: 1px;
383 stroke-width: 1px;
384 }
384 }
385 .key12,.fill12{
385 .key12,.fill12{
386 fill-opacity: 0.7;
386 fill-opacity: 0.7;
387 fill: #9966FF;
387 fill: #9966FF;
388 stroke: none;
388 stroke: none;
389 stroke-width: 1px;
389 stroke-width: 1px;
390 }
390 }
391 EOL
391 EOL
392 end
392 end
393 end
393 end
394 end
394 end
395 end
395 end
This diff has been collapsed as it changes many lines, (1000 lines changed) Show them Hide them
@@ -1,500 +1,500
1 require 'SVG/Graph/Graph'
1 require 'SVG/Graph/Graph'
2
2
3 module SVG
3 module SVG
4 module Graph
4 module Graph
5 # === For creating SVG plots of scalar data
5 # === For creating SVG plots of scalar data
6 #
6 #
7 # = Synopsis
7 # = Synopsis
8 #
8 #
9 # require 'SVG/Graph/Plot'
9 # require 'SVG/Graph/Plot'
10 #
10 #
11 # # Data sets are x,y pairs
11 # # Data sets are x,y pairs
12 # # Note that multiple data sets can differ in length, and that the
12 # # Note that multiple data sets can differ in length, and that the
13 # # data in the datasets needn't be in order; they will be ordered
13 # # data in the datasets needn't be in order; they will be ordered
14 # # by the plot along the X-axis.
14 # # by the plot along the X-axis.
15 # projection = [
15 # projection = [
16 # 6, 11, 0, 5, 18, 7, 1, 11, 13, 9, 1, 2, 19, 0, 3, 13,
16 # 6, 11, 0, 5, 18, 7, 1, 11, 13, 9, 1, 2, 19, 0, 3, 13,
17 # 7, 9
17 # 7, 9
18 # ]
18 # ]
19 # actual = [
19 # actual = [
20 # 0, 18, 8, 15, 9, 4, 18, 14, 10, 2, 11, 6, 14, 12,
20 # 0, 18, 8, 15, 9, 4, 18, 14, 10, 2, 11, 6, 14, 12,
21 # 15, 6, 4, 17, 2, 12
21 # 15, 6, 4, 17, 2, 12
22 # ]
22 # ]
23 #
23 #
24 # graph = SVG::Graph::Plot.new({
24 # graph = SVG::Graph::Plot.new({
25 # :height => 500,
25 # :height => 500,
26 # :width => 300,
26 # :width => 300,
27 # :key => true,
27 # :key => true,
28 # :scale_x_integers => true,
28 # :scale_x_integers => true,
29 # :scale_y_integerrs => true,
29 # :scale_y_integerrs => true,
30 # })
30 # })
31 #
31 #
32 # graph.add_data({
32 # graph.add_data({
33 # :data => projection
33 # :data => projection
34 # :title => 'Projected',
34 # :title => 'Projected',
35 # })
35 # })
36 #
36 #
37 # graph.add_data({
37 # graph.add_data({
38 # :data => actual,
38 # :data => actual,
39 # :title => 'Actual',
39 # :title => 'Actual',
40 # })
40 # })
41 #
41 #
42 # print graph.burn()
42 # print graph.burn()
43 #
43 #
44 # = Description
44 # = Description
45 #
45 #
46 # Produces a graph of scalar data.
46 # Produces a graph of scalar data.
47 #
47 #
48 # This object aims to allow you to easily create high quality
48 # This object aims to allow you to easily create high quality
49 # SVG[http://www.w3c.org/tr/svg] scalar plots. You can either use the
49 # SVG[http://www.w3c.org/tr/svg] scalar plots. You can either use the
50 # default style sheet or supply your own. Either way there are many options
50 # default style sheet or supply your own. Either way there are many options
51 # which can be configured to give you control over how the graph is
51 # which can be configured to give you control over how the graph is
52 # generated - with or without a key, data elements at each point, title,
52 # generated - with or without a key, data elements at each point, title,
53 # subtitle etc.
53 # subtitle etc.
54 #
54 #
55 # = Examples
55 # = Examples
56 #
56 #
57 # http://www.germane-software/repositories/public/SVG/test/plot.rb
57 # http://www.germane-software/repositories/public/SVG/test/plot.rb
58 #
58 #
59 # = Notes
59 # = Notes
60 #
60 #
61 # The default stylesheet handles upto 10 data sets, if you
61 # The default stylesheet handles upto 10 data sets, if you
62 # use more you must create your own stylesheet and add the
62 # use more you must create your own stylesheet and add the
63 # additional settings for the extra data sets. You will know
63 # additional settings for the extra data sets. You will know
64 # if you go over 10 data sets as they will have no style and
64 # if you go over 10 data sets as they will have no style and
65 # be in black.
65 # be in black.
66 #
66 #
67 # Unlike the other types of charts, data sets must contain x,y pairs:
67 # Unlike the other types of charts, data sets must contain x,y pairs:
68 #
68 #
69 # [ 1, 2 ] # A data set with 1 point: (1,2)
69 # [ 1, 2 ] # A data set with 1 point: (1,2)
70 # [ 1,2, 5,6] # A data set with 2 points: (1,2) and (5,6)
70 # [ 1,2, 5,6] # A data set with 2 points: (1,2) and (5,6)
71 #
71 #
72 # = See also
72 # = See also
73 #
73 #
74 # * SVG::Graph::Graph
74 # * SVG::Graph::Graph
75 # * SVG::Graph::BarHorizontal
75 # * SVG::Graph::BarHorizontal
76 # * SVG::Graph::Bar
76 # * SVG::Graph::Bar
77 # * SVG::Graph::Line
77 # * SVG::Graph::Line
78 # * SVG::Graph::Pie
78 # * SVG::Graph::Pie
79 # * SVG::Graph::TimeSeries
79 # * SVG::Graph::TimeSeries
80 #
80 #
81 # == Author
81 # == Author
82 #
82 #
83 # Sean E. Russell <serATgermaneHYPHENsoftwareDOTcom>
83 # Sean E. Russell <serATgermaneHYPHENsoftwareDOTcom>
84 #
84 #
85 # Copyright 2004 Sean E. Russell
85 # Copyright 2004 Sean E. Russell
86 # This software is available under the Ruby license[LICENSE.txt]
86 # This software is available under the Ruby license[LICENSE.txt]
87 #
87 #
88 class Plot < Graph
88 class Plot < Graph
89
89
90 # In addition to the defaults set by Graph::initialize, sets
90 # In addition to the defaults set by Graph::initialize, sets
91 # [show_data_values] true
91 # [show_data_values] true
92 # [show_data_points] true
92 # [show_data_points] true
93 # [area_fill] false
93 # [area_fill] false
94 # [stacked] false
94 # [stacked] false
95 def set_defaults
95 def set_defaults
96 init_with(
96 init_with(
97 :show_data_values => true,
97 :show_data_values => true,
98 :show_data_points => true,
98 :show_data_points => true,
99 :area_fill => false,
99 :area_fill => false,
100 :stacked => false
100 :stacked => false
101 )
101 )
102 self.top_align = self.right_align = self.top_font = self.right_font = 1
102 self.top_align = self.right_align = self.top_font = self.right_font = 1
103 end
103 end
104
104
105 # Determines the scaling for the X axis divisions.
105 # Determines the scaling for the X axis divisions.
106 #
106 #
107 # graph.scale_x_divisions = 2
107 # graph.scale_x_divisions = 2
108 #
108 #
109 # would cause the graph to attempt to generate labels stepped by 2; EG:
109 # would cause the graph to attempt to generate labels stepped by 2; EG:
110 # 0,2,4,6,8...
110 # 0,2,4,6,8...
111 attr_accessor :scale_x_divisions
111 attr_accessor :scale_x_divisions
112 # Determines the scaling for the Y axis divisions.
112 # Determines the scaling for the Y axis divisions.
113 #
113 #
114 # graph.scale_y_divisions = 0.5
114 # graph.scale_y_divisions = 0.5
115 #
115 #
116 # would cause the graph to attempt to generate labels stepped by 0.5; EG:
116 # would cause the graph to attempt to generate labels stepped by 0.5; EG:
117 # 0, 0.5, 1, 1.5, 2, ...
117 # 0, 0.5, 1, 1.5, 2, ...
118 attr_accessor :scale_y_divisions
118 attr_accessor :scale_y_divisions
119 # Make the X axis labels integers
119 # Make the X axis labels integers
120 attr_accessor :scale_x_integers
120 attr_accessor :scale_x_integers
121 # Make the Y axis labels integers
121 # Make the Y axis labels integers
122 attr_accessor :scale_y_integers
122 attr_accessor :scale_y_integers
123 # Fill the area under the line
123 # Fill the area under the line
124 attr_accessor :area_fill
124 attr_accessor :area_fill
125 # Show a small circle on the graph where the line
125 # Show a small circle on the graph where the line
126 # goes from one point to the next.
126 # goes from one point to the next.
127 attr_accessor :show_data_points
127 attr_accessor :show_data_points
128 # Set the minimum value of the X axis
128 # Set the minimum value of the X axis
129 attr_accessor :min_x_value
129 attr_accessor :min_x_value
130 # Set the minimum value of the Y axis
130 # Set the minimum value of the Y axis
131 attr_accessor :min_y_value
131 attr_accessor :min_y_value
132
132
133
133
134 # Adds data to the plot. The data must be in X,Y pairs; EG
134 # Adds data to the plot. The data must be in X,Y pairs; EG
135 # [ 1, 2 ] # A data set with 1 point: (1,2)
135 # [ 1, 2 ] # A data set with 1 point: (1,2)
136 # [ 1,2, 5,6] # A data set with 2 points: (1,2) and (5,6)
136 # [ 1,2, 5,6] # A data set with 2 points: (1,2) and (5,6)
137 def add_data data
137 def add_data data
138 @data = [] unless @data
138 @data = [] unless @data
139
139
140 raise "No data provided by #{conf.inspect}" unless data[:data] and
140 raise "No data provided by #{conf.inspect}" unless data[:data] and
141 data[:data].kind_of? Array
141 data[:data].kind_of? Array
142 raise "Data supplied must be x,y pairs! "+
142 raise "Data supplied must be x,y pairs! "+
143 "The data provided contained an odd set of "+
143 "The data provided contained an odd set of "+
144 "data points" unless data[:data].length % 2 == 0
144 "data points" unless data[:data].length % 2 == 0
145 return if data[:data].length == 0
145 return if data[:data].length == 0
146
146
147 x = []
147 x = []
148 y = []
148 y = []
149 data[:data].each_index {|i|
149 data[:data].each_index {|i|
150 (i%2 == 0 ? x : y) << data[:data][i]
150 (i%2 == 0 ? x : y) << data[:data][i]
151 }
151 }
152 sort( x, y )
152 sort( x, y )
153 data[:data] = [x,y]
153 data[:data] = [x,y]
154 @data << data
154 @data << data
155 end
155 end
156
156
157
157
158 protected
158 protected
159
159
160 def keys
160 def keys
161 @data.collect{ |x| x[:title] }
161 @data.collect{ |x| x[:title] }
162 end
162 end
163
163
164 def calculate_left_margin
164 def calculate_left_margin
165 super
165 super
166 label_left = get_x_labels[0].to_s.length / 2 * font_size * 0.6
166 label_left = get_x_labels[0].to_s.length / 2 * font_size * 0.6
167 @border_left = label_left if label_left > @border_left
167 @border_left = label_left if label_left > @border_left
168 end
168 end
169
169
170 def calculate_right_margin
170 def calculate_right_margin
171 super
171 super
172 label_right = get_x_labels[-1].to_s.length / 2 * font_size * 0.6
172 label_right = get_x_labels[-1].to_s.length / 2 * font_size * 0.6
173 @border_right = label_right if label_right > @border_right
173 @border_right = label_right if label_right > @border_right
174 end
174 end
175
175
176
176
177 X = 0
177 X = 0
178 Y = 1
178 Y = 1
179 def x_range
179 def x_range
180 max_value = @data.collect{|x| x[:data][X][-1] }.max
180 max_value = @data.collect{|x| x[:data][X][-1] }.max
181 min_value = @data.collect{|x| x[:data][X][0] }.min
181 min_value = @data.collect{|x| x[:data][X][0] }.min
182 min_value = min_value<min_x_value ? min_value : min_x_value if min_x_value
182 min_value = min_value<min_x_value ? min_value : min_x_value if min_x_value
183
183
184 range = max_value - min_value
184 range = max_value - min_value
185 right_pad = range == 0 ? 10 : range / 20.0
185 right_pad = range == 0 ? 10 : range / 20.0
186 scale_range = (max_value + right_pad) - min_value
186 scale_range = (max_value + right_pad) - min_value
187
187
188 scale_division = scale_x_divisions || (scale_range / 10.0)
188 scale_division = scale_x_divisions || (scale_range / 10.0)
189
189
190 if scale_x_integers
190 if scale_x_integers
191 scale_division = scale_division < 1 ? 1 : scale_division.round
191 scale_division = scale_division < 1 ? 1 : scale_division.round
192 end
192 end
193
193
194 [min_value, max_value, scale_division]
194 [min_value, max_value, scale_division]
195 end
195 end
196
196
197 def get_x_values
197 def get_x_values
198 min_value, max_value, scale_division = x_range
198 min_value, max_value, scale_division = x_range
199 rv = []
199 rv = []
200 min_value.step( max_value, scale_division ) {|v| rv << v}
200 min_value.step( max_value, scale_division ) {|v| rv << v}
201 return rv
201 return rv
202 end
202 end
203 alias :get_x_labels :get_x_values
203 alias :get_x_labels :get_x_values
204
204
205 def field_width
205 def field_width
206 values = get_x_values
206 values = get_x_values
207 max = @data.collect{|x| x[:data][X][-1]}.max
207 max = @data.collect{|x| x[:data][X][-1]}.max
208 dx = (max - values[-1]).to_f / (values[-1] - values[-2])
208 dx = (max - values[-1]).to_f / (values[-1] - values[-2])
209 (@graph_width.to_f - font_size*2*right_font) /
209 (@graph_width.to_f - font_size*2*right_font) /
210 (values.length + dx - right_align)
210 (values.length + dx - right_align)
211 end
211 end
212
212
213
213
214 def y_range
214 def y_range
215 max_value = @data.collect{|x| x[:data][Y].max }.max
215 max_value = @data.collect{|x| x[:data][Y].max }.max
216 min_value = @data.collect{|x| x[:data][Y].min }.min
216 min_value = @data.collect{|x| x[:data][Y].min }.min
217 min_value = min_value<min_y_value ? min_value : min_y_value if min_y_value
217 min_value = min_value<min_y_value ? min_value : min_y_value if min_y_value
218
218
219 range = max_value - min_value
219 range = max_value - min_value
220 top_pad = range == 0 ? 10 : range / 20.0
220 top_pad = range == 0 ? 10 : range / 20.0
221 scale_range = (max_value + top_pad) - min_value
221 scale_range = (max_value + top_pad) - min_value
222
222
223 scale_division = scale_y_divisions || (scale_range / 10.0)
223 scale_division = scale_y_divisions || (scale_range / 10.0)
224
224
225 if scale_y_integers
225 if scale_y_integers
226 scale_division = scale_division < 1 ? 1 : scale_division.round
226 scale_division = scale_division < 1 ? 1 : scale_division.round
227 end
227 end
228
228
229 return [min_value, max_value, scale_division]
229 return [min_value, max_value, scale_division]
230 end
230 end
231
231
232 def get_y_values
232 def get_y_values
233 min_value, max_value, scale_division = y_range
233 min_value, max_value, scale_division = y_range
234 rv = []
234 rv = []
235 min_value.step( max_value, scale_division ) {|v| rv << v}
235 min_value.step( max_value, scale_division ) {|v| rv << v}
236 return rv
236 return rv
237 end
237 end
238 alias :get_y_labels :get_y_values
238 alias :get_y_labels :get_y_values
239
239
240 def field_height
240 def field_height
241 values = get_y_values
241 values = get_y_values
242 max = @data.collect{|x| x[:data][Y].max }.max
242 max = @data.collect{|x| x[:data][Y].max }.max
243 if values.length == 1
243 if values.length == 1
244 dx = values[-1]
244 dx = values[-1]
245 else
245 else
246 dx = (max - values[-1]).to_f / (values[-1] - values[-2])
246 dx = (max - values[-1]).to_f / (values[-1] - values[-2])
247 end
247 end
248 (@graph_height.to_f - font_size*2*top_font) /
248 (@graph_height.to_f - font_size*2*top_font) /
249 (values.length + dx - top_align)
249 (values.length + dx - top_align)
250 end
250 end
251
251
252 def draw_data
252 def draw_data
253 line = 1
253 line = 1
254
254
255 x_min, x_max, x_div = x_range
255 x_min, x_max, x_div = x_range
256 y_min, y_max, y_div = y_range
256 y_min, y_max, y_div = y_range
257 x_step = (@graph_width.to_f - font_size*2) / (x_max-x_min)
257 x_step = (@graph_width.to_f - font_size*2) / (x_max-x_min)
258 y_step = (@graph_height.to_f - font_size*2) / (y_max-y_min)
258 y_step = (@graph_height.to_f - font_size*2) / (y_max-y_min)
259
259
260 for data in @data
260 for data in @data
261 x_points = data[:data][X]
261 x_points = data[:data][X]
262 y_points = data[:data][Y]
262 y_points = data[:data][Y]
263
263
264 lpath = "L"
264 lpath = "L"
265 x_start = 0
265 x_start = 0
266 y_start = 0
266 y_start = 0
267 x_points.each_index { |idx|
267 x_points.each_index { |idx|
268 x = (x_points[idx] - x_min) * x_step
268 x = (x_points[idx] - x_min) * x_step
269 y = @graph_height - (y_points[idx] - y_min) * y_step
269 y = @graph_height - (y_points[idx] - y_min) * y_step
270 x_start, y_start = x,y if idx == 0
270 x_start, y_start = x,y if idx == 0
271 lpath << "#{x} #{y} "
271 lpath << "#{x} #{y} "
272 }
272 }
273
273
274 if area_fill
274 if area_fill
275 @graph.add_element( "path", {
275 @graph.add_element( "path", {
276 "d" => "M#{x_start} #@graph_height #{lpath} V#@graph_height Z",
276 "d" => "M#{x_start} #@graph_height #{lpath} V#@graph_height Z",
277 "class" => "fill#{line}"
277 "class" => "fill#{line}"
278 })
278 })
279 end
279 end
280
280
281 @graph.add_element( "path", {
281 @graph.add_element( "path", {
282 "d" => "M#{x_start} #{y_start} #{lpath}",
282 "d" => "M#{x_start} #{y_start} #{lpath}",
283 "class" => "line#{line}"
283 "class" => "line#{line}"
284 })
284 })
285
285
286 if show_data_points || show_data_values
286 if show_data_points || show_data_values
287 x_points.each_index { |idx|
287 x_points.each_index { |idx|
288 x = (x_points[idx] - x_min) * x_step
288 x = (x_points[idx] - x_min) * x_step
289 y = @graph_height - (y_points[idx] - y_min) * y_step
289 y = @graph_height - (y_points[idx] - y_min) * y_step
290 if show_data_points
290 if show_data_points
291 @graph.add_element( "circle", {
291 @graph.add_element( "circle", {
292 "cx" => x.to_s,
292 "cx" => x.to_s,
293 "cy" => y.to_s,
293 "cy" => y.to_s,
294 "r" => "2.5",
294 "r" => "2.5",
295 "class" => "dataPoint#{line}"
295 "class" => "dataPoint#{line}"
296 })
296 })
297 add_popup(x, y, format( x_points[idx], y_points[idx] )) if add_popups
297 add_popup(x, y, format( x_points[idx], y_points[idx] )) if add_popups
298 end
298 end
299 make_datapoint_text( x, y-6, y_points[idx] ) if show_data_values
299 make_datapoint_text( x, y-6, y_points[idx] ) if show_data_values
300 }
300 }
301 end
301 end
302 line += 1
302 line += 1
303 end
303 end
304 end
304 end
305
305
306 def format x, y
306 def format x, y
307 "(#{(x * 100).to_i / 100}, #{(y * 100).to_i / 100})"
307 "(#{(x * 100).to_i / 100}, #{(y * 100).to_i / 100})"
308 end
308 end
309
309
310 def get_css
310 def get_css
311 return <<EOL
311 return <<EOL
312 /* default line styles */
312 /* default line styles */
313 .line1{
313 .line1{
314 fill: none;
314 fill: none;
315 stroke: #ff0000;
315 stroke: #ff0000;
316 stroke-width: 1px;
316 stroke-width: 1px;
317 }
317 }
318 .line2{
318 .line2{
319 fill: none;
319 fill: none;
320 stroke: #0000ff;
320 stroke: #0000ff;
321 stroke-width: 1px;
321 stroke-width: 1px;
322 }
322 }
323 .line3{
323 .line3{
324 fill: none;
324 fill: none;
325 stroke: #00ff00;
325 stroke: #00ff00;
326 stroke-width: 1px;
326 stroke-width: 1px;
327 }
327 }
328 .line4{
328 .line4{
329 fill: none;
329 fill: none;
330 stroke: #ffcc00;
330 stroke: #ffcc00;
331 stroke-width: 1px;
331 stroke-width: 1px;
332 }
332 }
333 .line5{
333 .line5{
334 fill: none;
334 fill: none;
335 stroke: #00ccff;
335 stroke: #00ccff;
336 stroke-width: 1px;
336 stroke-width: 1px;
337 }
337 }
338 .line6{
338 .line6{
339 fill: none;
339 fill: none;
340 stroke: #ff00ff;
340 stroke: #ff00ff;
341 stroke-width: 1px;
341 stroke-width: 1px;
342 }
342 }
343 .line7{
343 .line7{
344 fill: none;
344 fill: none;
345 stroke: #00ffff;
345 stroke: #00ffff;
346 stroke-width: 1px;
346 stroke-width: 1px;
347 }
347 }
348 .line8{
348 .line8{
349 fill: none;
349 fill: none;
350 stroke: #ffff00;
350 stroke: #ffff00;
351 stroke-width: 1px;
351 stroke-width: 1px;
352 }
352 }
353 .line9{
353 .line9{
354 fill: none;
354 fill: none;
355 stroke: #ccc6666;
355 stroke: #ccc6666;
356 stroke-width: 1px;
356 stroke-width: 1px;
357 }
357 }
358 .line10{
358 .line10{
359 fill: none;
359 fill: none;
360 stroke: #663399;
360 stroke: #663399;
361 stroke-width: 1px;
361 stroke-width: 1px;
362 }
362 }
363 .line11{
363 .line11{
364 fill: none;
364 fill: none;
365 stroke: #339900;
365 stroke: #339900;
366 stroke-width: 1px;
366 stroke-width: 1px;
367 }
367 }
368 .line12{
368 .line12{
369 fill: none;
369 fill: none;
370 stroke: #9966FF;
370 stroke: #9966FF;
371 stroke-width: 1px;
371 stroke-width: 1px;
372 }
372 }
373 /* default fill styles */
373 /* default fill styles */
374 .fill1{
374 .fill1{
375 fill: #cc0000;
375 fill: #cc0000;
376 fill-opacity: 0.2;
376 fill-opacity: 0.2;
377 stroke: none;
377 stroke: none;
378 }
378 }
379 .fill2{
379 .fill2{
380 fill: #0000cc;
380 fill: #0000cc;
381 fill-opacity: 0.2;
381 fill-opacity: 0.2;
382 stroke: none;
382 stroke: none;
383 }
383 }
384 .fill3{
384 .fill3{
385 fill: #00cc00;
385 fill: #00cc00;
386 fill-opacity: 0.2;
386 fill-opacity: 0.2;
387 stroke: none;
387 stroke: none;
388 }
388 }
389 .fill4{
389 .fill4{
390 fill: #ffcc00;
390 fill: #ffcc00;
391 fill-opacity: 0.2;
391 fill-opacity: 0.2;
392 stroke: none;
392 stroke: none;
393 }
393 }
394 .fill5{
394 .fill5{
395 fill: #00ccff;
395 fill: #00ccff;
396 fill-opacity: 0.2;
396 fill-opacity: 0.2;
397 stroke: none;
397 stroke: none;
398 }
398 }
399 .fill6{
399 .fill6{
400 fill: #ff00ff;
400 fill: #ff00ff;
401 fill-opacity: 0.2;
401 fill-opacity: 0.2;
402 stroke: none;
402 stroke: none;
403 }
403 }
404 .fill7{
404 .fill7{
405 fill: #00ffff;
405 fill: #00ffff;
406 fill-opacity: 0.2;
406 fill-opacity: 0.2;
407 stroke: none;
407 stroke: none;
408 }
408 }
409 .fill8{
409 .fill8{
410 fill: #ffff00;
410 fill: #ffff00;
411 fill-opacity: 0.2;
411 fill-opacity: 0.2;
412 stroke: none;
412 stroke: none;
413 }
413 }
414 .fill9{
414 .fill9{
415 fill: #cc6666;
415 fill: #cc6666;
416 fill-opacity: 0.2;
416 fill-opacity: 0.2;
417 stroke: none;
417 stroke: none;
418 }
418 }
419 .fill10{
419 .fill10{
420 fill: #663399;
420 fill: #663399;
421 fill-opacity: 0.2;
421 fill-opacity: 0.2;
422 stroke: none;
422 stroke: none;
423 }
423 }
424 .fill11{
424 .fill11{
425 fill: #339900;
425 fill: #339900;
426 fill-opacity: 0.2;
426 fill-opacity: 0.2;
427 stroke: none;
427 stroke: none;
428 }
428 }
429 .fill12{
429 .fill12{
430 fill: #9966FF;
430 fill: #9966FF;
431 fill-opacity: 0.2;
431 fill-opacity: 0.2;
432 stroke: none;
432 stroke: none;
433 }
433 }
434 /* default line styles */
434 /* default line styles */
435 .key1,.dataPoint1{
435 .key1,.dataPoint1{
436 fill: #ff0000;
436 fill: #ff0000;
437 stroke: none;
437 stroke: none;
438 stroke-width: 1px;
438 stroke-width: 1px;
439 }
439 }
440 .key2,.dataPoint2{
440 .key2,.dataPoint2{
441 fill: #0000ff;
441 fill: #0000ff;
442 stroke: none;
442 stroke: none;
443 stroke-width: 1px;
443 stroke-width: 1px;
444 }
444 }
445 .key3,.dataPoint3{
445 .key3,.dataPoint3{
446 fill: #00ff00;
446 fill: #00ff00;
447 stroke: none;
447 stroke: none;
448 stroke-width: 1px;
448 stroke-width: 1px;
449 }
449 }
450 .key4,.dataPoint4{
450 .key4,.dataPoint4{
451 fill: #ffcc00;
451 fill: #ffcc00;
452 stroke: none;
452 stroke: none;
453 stroke-width: 1px;
453 stroke-width: 1px;
454 }
454 }
455 .key5,.dataPoint5{
455 .key5,.dataPoint5{
456 fill: #00ccff;
456 fill: #00ccff;
457 stroke: none;
457 stroke: none;
458 stroke-width: 1px;
458 stroke-width: 1px;
459 }
459 }
460 .key6,.dataPoint6{
460 .key6,.dataPoint6{
461 fill: #ff00ff;
461 fill: #ff00ff;
462 stroke: none;
462 stroke: none;
463 stroke-width: 1px;
463 stroke-width: 1px;
464 }
464 }
465 .key7,.dataPoint7{
465 .key7,.dataPoint7{
466 fill: #00ffff;
466 fill: #00ffff;
467 stroke: none;
467 stroke: none;
468 stroke-width: 1px;
468 stroke-width: 1px;
469 }
469 }
470 .key8,.dataPoint8{
470 .key8,.dataPoint8{
471 fill: #ffff00;
471 fill: #ffff00;
472 stroke: none;
472 stroke: none;
473 stroke-width: 1px;
473 stroke-width: 1px;
474 }
474 }
475 .key9,.dataPoint9{
475 .key9,.dataPoint9{
476 fill: #cc6666;
476 fill: #cc6666;
477 stroke: none;
477 stroke: none;
478 stroke-width: 1px;
478 stroke-width: 1px;
479 }
479 }
480 .key10,.dataPoint10{
480 .key10,.dataPoint10{
481 fill: #663399;
481 fill: #663399;
482 stroke: none;
482 stroke: none;
483 stroke-width: 1px;
483 stroke-width: 1px;
484 }
484 }
485 .key11,.dataPoint11{
485 .key11,.dataPoint11{
486 fill: #339900;
486 fill: #339900;
487 stroke: none;
487 stroke: none;
488 stroke-width: 1px;
488 stroke-width: 1px;
489 }
489 }
490 .key12,.dataPoint12{
490 .key12,.dataPoint12{
491 fill: #9966FF;
491 fill: #9966FF;
492 stroke: none;
492 stroke: none;
493 stroke-width: 1px;
493 stroke-width: 1px;
494 }
494 }
495 EOL
495 EOL
496 end
496 end
497
497
498 end
498 end
499 end
499 end
500 end
500 end
@@ -1,238 +1,238
1 require 'SVG/Graph/Plot'
1 require 'SVG/Graph/Plot'
2
2
3 module SVG
3 module SVG
4 module Graph
4 module Graph
5 # === For creating SVG plots of scalar temporal data
5 # === For creating SVG plots of scalar temporal data
6 #
6 #
7 # = Synopsis
7 # = Synopsis
8 #
8 #
9 # require 'SVG/Graph/TimeSeriess'
9 # require 'SVG/Graph/TimeSeriess'
10 #
10 #
11 # # Data sets are x,y pairs
11 # # Data sets are x,y pairs
12 # data1 = ["6/17/72", 11, "1/11/72", 7, "4/13/04 17:31", 11,
12 # data1 = ["6/17/72", 11, "1/11/72", 7, "4/13/04 17:31", 11,
13 # "9/11/01", 9, "9/1/85", 2, "9/1/88", 1, "1/15/95", 13]
13 # "9/11/01", 9, "9/1/85", 2, "9/1/88", 1, "1/15/95", 13]
14 # data2 = ["8/1/73", 18, "3/1/77", 15, "10/1/98", 4,
14 # data2 = ["8/1/73", 18, "3/1/77", 15, "10/1/98", 4,
15 # "5/1/02", 14, "3/1/95", 6, "8/1/91", 12, "12/1/87", 6,
15 # "5/1/02", 14, "3/1/95", 6, "8/1/91", 12, "12/1/87", 6,
16 # "5/1/84", 17, "10/1/80", 12]
16 # "5/1/84", 17, "10/1/80", 12]
17 #
17 #
18 # graph = SVG::Graph::TimeSeries.new( {
18 # graph = SVG::Graph::TimeSeries.new( {
19 # :width => 640,
19 # :width => 640,
20 # :height => 480,
20 # :height => 480,
21 # :graph_title => title,
21 # :graph_title => title,
22 # :show_graph_title => true,
22 # :show_graph_title => true,
23 # :no_css => true,
23 # :no_css => true,
24 # :key => true,
24 # :key => true,
25 # :scale_x_integers => true,
25 # :scale_x_integers => true,
26 # :scale_y_integers => true,
26 # :scale_y_integers => true,
27 # :min_x_value => 0,
27 # :min_x_value => 0,
28 # :min_y_value => 0,
28 # :min_y_value => 0,
29 # :show_data_labels => true,
29 # :show_data_labels => true,
30 # :show_x_guidelines => true,
30 # :show_x_guidelines => true,
31 # :show_x_title => true,
31 # :show_x_title => true,
32 # :x_title => "Time",
32 # :x_title => "Time",
33 # :show_y_title => true,
33 # :show_y_title => true,
34 # :y_title => "Ice Cream Cones",
34 # :y_title => "Ice Cream Cones",
35 # :y_title_text_direction => :bt,
35 # :y_title_text_direction => :bt,
36 # :stagger_x_labels => true,
36 # :stagger_x_labels => true,
37 # :x_label_format => "%m/%d/%y",
37 # :x_label_format => "%m/%d/%y",
38 # })
38 # })
39 #
39 #
40 # graph.add_data({
40 # graph.add_data({
41 # :data => projection
41 # :data => projection
42 # :title => 'Projected',
42 # :title => 'Projected',
43 # })
43 # })
44 #
44 #
45 # graph.add_data({
45 # graph.add_data({
46 # :data => actual,
46 # :data => actual,
47 # :title => 'Actual',
47 # :title => 'Actual',
48 # })
48 # })
49 #
49 #
50 # print graph.burn()
50 # print graph.burn()
51 #
51 #
52 # = Description
52 # = Description
53 #
53 #
54 # Produces a graph of temporal scalar data.
54 # Produces a graph of temporal scalar data.
55 #
55 #
56 # = Examples
56 # = Examples
57 #
57 #
58 # http://www.germane-software/repositories/public/SVG/test/timeseries.rb
58 # http://www.germane-software/repositories/public/SVG/test/timeseries.rb
59 #
59 #
60 # = Notes
60 # = Notes
61 #
61 #
62 # The default stylesheet handles upto 10 data sets, if you
62 # The default stylesheet handles upto 10 data sets, if you
63 # use more you must create your own stylesheet and add the
63 # use more you must create your own stylesheet and add the
64 # additional settings for the extra data sets. You will know
64 # additional settings for the extra data sets. You will know
65 # if you go over 10 data sets as they will have no style and
65 # if you go over 10 data sets as they will have no style and
66 # be in black.
66 # be in black.
67 #
67 #
68 # Unlike the other types of charts, data sets must contain x,y pairs:
68 # Unlike the other types of charts, data sets must contain x,y pairs:
69 #
69 #
70 # [ "12:30", 2 ] # A data set with 1 point: ("12:30",2)
70 # [ "12:30", 2 ] # A data set with 1 point: ("12:30",2)
71 # [ "01:00",2, "14:20",6] # A data set with 2 points: ("01:00",2) and
71 # [ "01:00",2, "14:20",6] # A data set with 2 points: ("01:00",2) and
72 # # ("14:20",6)
72 # # ("14:20",6)
73 #
73 #
74 # Note that multiple data sets within the same chart can differ in length,
74 # Note that multiple data sets within the same chart can differ in length,
75 # and that the data in the datasets needn't be in order; they will be ordered
75 # and that the data in the datasets needn't be in order; they will be ordered
76 # by the plot along the X-axis.
76 # by the plot along the X-axis.
77 #
77 #
78 # The dates must be parseable by ParseDate, but otherwise can be
78 # The dates must be parseable by ParseDate, but otherwise can be
79 # any order of magnitude (seconds within the hour, or years)
79 # any order of magnitude (seconds within the hour, or years)
80 #
80 #
81 # = See also
81 # = See also
82 #
82 #
83 # * SVG::Graph::Graph
83 # * SVG::Graph::Graph
84 # * SVG::Graph::BarHorizontal
84 # * SVG::Graph::BarHorizontal
85 # * SVG::Graph::Bar
85 # * SVG::Graph::Bar
86 # * SVG::Graph::Line
86 # * SVG::Graph::Line
87 # * SVG::Graph::Pie
87 # * SVG::Graph::Pie
88 # * SVG::Graph::Plot
88 # * SVG::Graph::Plot
89 #
89 #
90 # == Author
90 # == Author
91 #
91 #
92 # Sean E. Russell <serATgermaneHYPHENsoftwareDOTcom>
92 # Sean E. Russell <serATgermaneHYPHENsoftwareDOTcom>
93 #
93 #
94 # Copyright 2004 Sean E. Russell
94 # Copyright 2004 Sean E. Russell
95 # This software is available under the Ruby license[LICENSE.txt]
95 # This software is available under the Ruby license[LICENSE.txt]
96 #
96 #
97 class TimeSeries < Plot
97 class TimeSeries < Plot
98 # In addition to the defaults set by Graph::initialize and
98 # In addition to the defaults set by Graph::initialize and
99 # Plot::set_defaults, sets:
99 # Plot::set_defaults, sets:
100 # [x_label_format] '%Y-%m-%d %H:%M:%S'
100 # [x_label_format] '%Y-%m-%d %H:%M:%S'
101 # [popup_format] '%Y-%m-%d %H:%M:%S'
101 # [popup_format] '%Y-%m-%d %H:%M:%S'
102 def set_defaults
102 def set_defaults
103 super
103 super
104 init_with(
104 init_with(
105 #:max_time_span => '',
105 #:max_time_span => '',
106 :x_label_format => '%Y-%m-%d %H:%M:%S',
106 :x_label_format => '%Y-%m-%d %H:%M:%S',
107 :popup_format => '%Y-%m-%d %H:%M:%S'
107 :popup_format => '%Y-%m-%d %H:%M:%S'
108 )
108 )
109 end
109 end
110
110
111 # The format string use do format the X axis labels.
111 # The format string use do format the X axis labels.
112 # See Time::strformat
112 # See Time::strformat
113 attr_accessor :x_label_format
113 attr_accessor :x_label_format
114 # Use this to set the spacing between dates on the axis. The value
114 # Use this to set the spacing between dates on the axis. The value
115 # must be of the form
115 # must be of the form
116 # "\d+ ?(days|weeks|months|years|hours|minutes|seconds)?"
116 # "\d+ ?(days|weeks|months|years|hours|minutes|seconds)?"
117 #
117 #
118 # EG:
118 # EG:
119 #
119 #
120 # graph.timescale_divisions = "2 weeks"
120 # graph.timescale_divisions = "2 weeks"
121 #
121 #
122 # will cause the chart to try to divide the X axis up into segments of
122 # will cause the chart to try to divide the X axis up into segments of
123 # two week periods.
123 # two week periods.
124 attr_accessor :timescale_divisions
124 attr_accessor :timescale_divisions
125 # The formatting used for the popups. See x_label_format
125 # The formatting used for the popups. See x_label_format
126 attr_accessor :popup_format
126 attr_accessor :popup_format
127
127
128 # Add data to the plot.
128 # Add data to the plot.
129 #
129 #
130 # d1 = [ "12:30", 2 ] # A data set with 1 point: ("12:30",2)
130 # d1 = [ "12:30", 2 ] # A data set with 1 point: ("12:30",2)
131 # d2 = [ "01:00",2, "14:20",6] # A data set with 2 points: ("01:00",2) and
131 # d2 = [ "01:00",2, "14:20",6] # A data set with 2 points: ("01:00",2) and
132 # # ("14:20",6)
132 # # ("14:20",6)
133 # graph.add_data(
133 # graph.add_data(
134 # :data => d1,
134 # :data => d1,
135 # :title => 'One'
135 # :title => 'One'
136 # )
136 # )
137 # graph.add_data(
137 # graph.add_data(
138 # :data => d2,
138 # :data => d2,
139 # :title => 'Two'
139 # :title => 'Two'
140 # )
140 # )
141 #
141 #
142 # Note that the data must be in time,value pairs, and that the date format
142 # Note that the data must be in time,value pairs, and that the date format
143 # may be any date that is parseable by ParseDate.
143 # may be any date that is parseable by ParseDate.
144 def add_data data
144 def add_data data
145 @data = [] unless @data
145 @data = [] unless @data
146
146
147 raise "No data provided by #{@data.inspect}" unless data[:data] and
147 raise "No data provided by #{@data.inspect}" unless data[:data] and
148 data[:data].kind_of? Array
148 data[:data].kind_of? Array
149 raise "Data supplied must be x,y pairs! "+
149 raise "Data supplied must be x,y pairs! "+
150 "The data provided contained an odd set of "+
150 "The data provided contained an odd set of "+
151 "data points" unless data[:data].length % 2 == 0
151 "data points" unless data[:data].length % 2 == 0
152 return if data[:data].length == 0
152 return if data[:data].length == 0
153
153
154
154
155 x = []
155 x = []
156 y = []
156 y = []
157 data[:data].each_index {|i|
157 data[:data].each_index {|i|
158 if i%2 == 0
158 if i%2 == 0
159 t = DateTime.parse( data[:data][i] ).to_time
159 t = DateTime.parse( data[:data][i] ).to_time
160 x << t.to_i
160 x << t.to_i
161 else
161 else
162 y << data[:data][i]
162 y << data[:data][i]
163 end
163 end
164 }
164 }
165 sort( x, y )
165 sort( x, y )
166 data[:data] = [x,y]
166 data[:data] = [x,y]
167 @data << data
167 @data << data
168 end
168 end
169
169
170
170
171 protected
171 protected
172
172
173 def min_x_value=(value)
173 def min_x_value=(value)
174 @min_x_value = DateTime.parse( value ).to_time
174 @min_x_value = DateTime.parse( value ).to_time
175 end
175 end
176
176
177
177
178 def format x, y
178 def format x, y
179 Time.at( x ).strftime( popup_format )
179 Time.at( x ).strftime( popup_format )
180 end
180 end
181
181
182 def get_x_labels
182 def get_x_labels
183 get_x_values.collect { |v| Time.at(v).strftime( x_label_format ) }
183 get_x_values.collect { |v| Time.at(v).strftime( x_label_format ) }
184 end
184 end
185
185
186 private
186 private
187 def get_x_values
187 def get_x_values
188 rv = []
188 rv = []
189 min, max, scale_division = x_range
189 min, max, scale_division = x_range
190 if timescale_divisions
190 if timescale_divisions
191 timescale_divisions =~ /(\d+) ?(day|week|month|year|hour|minute|second)?/
191 timescale_divisions =~ /(\d+) ?(day|week|month|year|hour|minute|second)?/
192 division_units = $2 ? $2 : "day"
192 division_units = $2 ? $2 : "day"
193 amount = $1.to_i
193 amount = $1.to_i
194 if amount
194 if amount
195 step = nil
195 step = nil
196 case division_units
196 case division_units
197 when "month"
197 when "month"
198 cur = min
198 cur = min
199 while cur < max
199 while cur < max
200 rv << cur
200 rv << cur
201 arr = Time.at( cur ).to_a
201 arr = Time.at( cur ).to_a
202 arr[4] += amount
202 arr[4] += amount
203 if arr[4] > 12
203 if arr[4] > 12
204 arr[5] += (arr[4] / 12).to_i
204 arr[5] += (arr[4] / 12).to_i
205 arr[4] = (arr[4] % 12)
205 arr[4] = (arr[4] % 12)
206 end
206 end
207 cur = Time.local(*arr).to_i
207 cur = Time.local(*arr).to_i
208 end
208 end
209 when "year"
209 when "year"
210 cur = min
210 cur = min
211 while cur < max
211 while cur < max
212 rv << cur
212 rv << cur
213 arr = Time.at( cur ).to_a
213 arr = Time.at( cur ).to_a
214 arr[5] += amount
214 arr[5] += amount
215 cur = Time.local(*arr).to_i
215 cur = Time.local(*arr).to_i
216 end
216 end
217 when "week"
217 when "week"
218 step = 7 * 24 * 60 * 60 * amount
218 step = 7 * 24 * 60 * 60 * amount
219 when "day"
219 when "day"
220 step = 24 * 60 * 60 * amount
220 step = 24 * 60 * 60 * amount
221 when "hour"
221 when "hour"
222 step = 60 * 60 * amount
222 step = 60 * 60 * amount
223 when "minute"
223 when "minute"
224 step = 60 * amount
224 step = 60 * amount
225 when "second"
225 when "second"
226 step = amount
226 step = amount
227 end
227 end
228 min.step( max, step ) {|v| rv << v} if step
228 min.step( max, step ) {|v| rv << v} if step
229
229
230 return rv
230 return rv
231 end
231 end
232 end
232 end
233 min.step( max, scale_division ) {|v| rv << v}
233 min.step( max, scale_division ) {|v| rv << v}
234 return rv
234 return rv
235 end
235 end
236 end
236 end
237 end
237 end
238 end
238 end
General Comments 0
You need to be logged in to leave comments. Login now