1 require 'rexml/document'
2 require 'SVG/Graph/BarBase'
3
4 module SVG
5 module Graph
6 # === Create presentation quality SVG horitonzal bar graphs easily
7 #
8 # = Synopsis
9 #
10 # require 'SVG/Graph/BarHorizontal'
11 #
12 # fields = %w(Jan Feb Mar)
13 # data_sales_02 = [12, 45, 21]
14 #
15 # graph = SVG::Graph::BarHorizontal.new({
16 # :height => 500,
17 # :width => 300,
18 # :fields => fields,
19 # })
20 #
21 # graph.add_data({
22 # :data => data_sales_02,
23 # :title => 'Sales 2002',
24 # })
25 #
26 # print "Content-type: image/svg+xml\r\n\r\n"
27 # print graph.burn
28 #
29 # = Description
30 #
31 # This object aims to allow you to easily create high quality
32 # SVG horitonzal bar graphs. You can either use the default style sheet
33 # or supply your own. Either way there are many options which can
34 # be configured to give you control over how the graph is
35 # generated - with or without a key, data elements at each point,
36 # title, subtitle etc.
37 #
38 # = Examples
39 #
40 # * http://germane-software.com/repositories/public/SVG/test/test.rb
41 #
42 # = See also
43 #
44 # * SVG::Graph::Graph
45 # * SVG::Graph::Bar
46 # * SVG::Graph::Line
47 # * SVG::Graph::Pie
48 # * SVG::Graph::Plot
49 # * SVG::Graph::TimeSeries
50 #
51 # == Author
52 #
53 # Sean E. Russell <serATgermaneHYPHENsoftwareDOTcom>
54 #
55 # Copyright 2004 Sean E. Russell
56 # This software is available under the Ruby license[LICENSE.txt]
57 #
58 class BarHorizontal < BarBase
59 # In addition to the defaults set in BarBase::set_defaults, sets
60 # [rotate_y_labels] true
61 # [show_x_guidelines] true
62 # [show_y_guidelines] false
63 def set_defaults
64 super
65 init_with(
66 :rotate_y_labels => true,
67 :show_x_guidelines => true,
68 :show_y_guidelines => false
69 )
70 self.right_align = self.right_font = 1
71 end
72
73 protected
74
75 def get_x_labels
76 maxvalue = max_value
77 minvalue = min_value
78 range = maxvalue - minvalue
79 top_pad = range == 0 ? 10 : range / 20.0
80 scale_range = (maxvalue + top_pad) - minvalue
81
82 scale_division = scale_divisions || (scale_range / 10.0)
83
84 if scale_integers
85 scale_division = scale_division < 1 ? 1 : scale_division.round
86 end
87
88 rv = []
89 maxvalue = maxvalue%scale_division == 0 ?
90 maxvalue : maxvalue + scale_division
91 minvalue.step( maxvalue, scale_division ) {|v| rv << v}
92 return rv
93 end
94
95 def get_y_labels
96 @config[:fields]
97 end
98
99 def y_label_offset( height )
100 height / -2.0
101 end
102
103 def draw_data
104 minvalue = min_value
105 fieldheight = field_height
106
107 unit_size = (@graph_width.to_f - font_size*2*right_font ) /
108 (get_x_labels.max - get_x_labels.min )
109 bargap = bar_gap ? (fieldheight < 10 ? fieldheight / 2 : 10) : 0
110
111 bar_height = fieldheight - bargap
112 bar_height /= @data.length if stack == :side
113 y_mod = (bar_height / 2) + (font_size / 2)
114
115 field_count = 1
116 @config[:fields].each_index { |i|
117 dataset_count = 0
118 for dataset in @data
119 value = dataset[:data][i]
120
121 top = @graph_height - (fieldheight * field_count)
122 top += (bar_height * dataset_count) if stack == :side
123 # cases (assume 0 = +ve):
124 # value min length left
125 # +ve +ve value.abs - min minvalue.abs
126 # +ve -ve value.abs - 0 minvalue.abs
127 # -ve -ve value.abs - 0 minvalue.abs + value
128 length = (value.abs - (minvalue > 0 ? minvalue : 0)) * unit_size
129 left = (minvalue.abs + (value < 0 ? value : 0)) * unit_size
130
131 @graph.add_element( "rect", {
132 "x" => left.to_s,
133 "y" => top.to_s,
134 "width" => length.to_s,
135 "height" => bar_height.to_s,
136 "class" => "fill#{dataset_count+1}"
137 })
138
139 make_datapoint_text(
140 left+length+5, top+y_mod, value, "text-anchor: start; "
141 )
142 dataset_count += 1
143 end
144 field_count += 1
145 }
146 end
147 end
148 end
149 end