class SVG::Graph::Line

Create presentation quality SVG line graphs easily

Synopsis

require 'SVG/Graph/Line'

fields = %w(Jan Feb Mar);
data_sales_02 = [12, 45, 21]
data_sales_03 = [15, 30, 40]

graph = SVG::Graph::Line.new({
        :height => 500,
        :width => 300,
  :fields => fields,
})

graph.add_data({
        :data => data_sales_02,
  :title => 'Sales 2002',
})

graph.add_data({
        :data => data_sales_03,
  :title => 'Sales 2003',
})

print "Content-type: image/svg+xml\r\n\r\n";
print graph.burn();

Description

This object aims to allow you to easily create high quality SVG line graphs. You can either use the default style sheet or supply your own. Either way there are many options which can be configured to give you control over how the graph is generated - with or without a key, data elements at each point, title, subtitle etc.

Examples

www.germane-software/repositories/public/SVG/test/single.rb

Notes

The default stylesheet handles upto 10 data sets, if you use more you must create your own stylesheet and add the additional settings for the extra data sets. You will know if you go over 10 data sets as they will have no style and be in black.

See also

Author

Sean E. Russell <serATgermaneHYPHENsoftwareDOTcom>

Copyright 2004 Sean E. Russell This software is available under the Ruby license

Attributes

area_fill[RW]

Fill in the area under the plot if true

show_data_points[RW]

Show a small circle on the graph where the line goes from one point to the next.

stacked[RW]
Accumulates each data set. (i.e. Each point increased by sum of

all previous series at same point). Default is 0, set to '1' to show.

Public Class Methods

new(config) click to toggle source

The constructor takes a hash reference, fields (the names for each field on the X axis) MUST be set, all other values are defaulted to those shown above - with the exception of style_sheet which defaults to using the internal style sheet.

Calls superclass method SVG::Graph::Graph::new
   # File lib/SVG/Graph/Line.rb
85 def initialize config
86     raise "fields was not supplied or is empty" unless config[:fields] &&
87     config[:fields].kind_of?(Array) &&
88     config[:fields].length > 0
89     super
90 end

Public Instance Methods

set_defaults() click to toggle source

In addition to the defaults set in Graph::initialize, sets

show_data_points

true

show_data_values

true

stacked

false

area_fill

false

    # File lib/SVG/Graph/Line.rb
 97 def set_defaults
 98 init_with(
 99 :show_data_points   => true,
100 :show_data_values   => true,
101 :stacked            => false,
102 :area_fill          => false
103 )
104 
105 
106 self.top_align = self.top_font = self.right_align = self.right_font = 1
107 end

Protected Instance Methods

calc_coords(field, value, width = field_width, height = field_height) click to toggle source
    # File lib/SVG/Graph/Line.rb
175 def calc_coords(field, value, width = field_width, height = field_height)
176   coords = {:x => 0, :y => 0}
177   coords[:x] = width * field
178   coords[:y] = @graph_height - value * height
179 
180   return coords
181 end
calculate_left_margin() click to toggle source
Calls superclass method SVG::Graph::Graph#calculate_left_margin
    # File lib/SVG/Graph/Line.rb
149 def calculate_left_margin
150   super
151   label_left = @config[:fields][0].length / 2 * font_size * 0.6
152   @border_left = label_left if label_left > @border_left
153 end
draw_data() click to toggle source
    # File lib/SVG/Graph/Line.rb
183 def draw_data
184   minvalue = min_value
185   fieldheight = (@graph_height.to_f - font_size*2*top_font) / 
186                    (get_y_labels.max - get_y_labels.min)
187   fieldwidth = field_width
188   line = @data.length
189 
190   prev_sum = Array.new(@config[:fields].length).fill(0)
191   cum_sum = Array.new(@config[:fields].length).fill(-minvalue)
192 
193   for data in @data.reverse
194     lpath = ""
195     apath = ""
196 
197     if not stacked then cum_sum.fill(-minvalue) end
198     
199     data[:data].each_index do |i|
200       cum_sum[i] += data[:data][i]
201       
202       c = calc_coords(i, cum_sum[i], fieldwidth, fieldheight)
203       
204       lpath << "#{c[:x]} #{c[:y]} "
205     end
206   
207     if area_fill
208       if stacked then
209         (prev_sum.length - 1).downto 0 do |i|
210           c = calc_coords(i, prev_sum[i], fieldwidth, fieldheight)
211           
212           apath << "#{c[:x]} #{c[:y]} "
213         end
214     
215         c = calc_coords(0, prev_sum[0], fieldwidth, fieldheight)
216       else
217         apath = "V#@graph_height"
218         c = calc_coords(0, 0, fieldwidth, fieldheight)
219       end
220         
221       @graph.add_element("path", {
222         "d" => "M#{c[:x]} #{c[:y]} L" + lpath + apath + "Z",
223         "class" => "fill#{line}"
224       })
225     end
226   
227     @graph.add_element("path", {
228       "d" => "M0 #@graph_height L" + lpath,
229       "class" => "line#{line}"
230     })
231     
232     if show_data_points || show_data_values
233       cum_sum.each_index do |i|
234         if show_data_points
235           @graph.add_element( "circle", {
236             "cx" => (fieldwidth * i).to_s,
237             "cy" => (@graph_height - cum_sum[i] * fieldheight).to_s,
238             "r" => "2.5",
239             "class" => "dataPoint#{line}"
240           })
241         end
242         make_datapoint_text( 
243           fieldwidth * i, 
244           @graph_height - cum_sum[i] * fieldheight - 6,
245           cum_sum[i] + minvalue
246         )
247       end
248     end
249 
250     prev_sum = cum_sum.dup
251     line -= 1
252   end
253 end
get_css() click to toggle source
    # File lib/SVG/Graph/Line.rb
256       def get_css
257         return <<EOL
258 /* default line styles */
259 .line1{
260         fill: none;
261         stroke: #ff0000;
262         stroke-width: 1px;     
263 }
264 .line2{
265         fill: none;
266         stroke: #0000ff;
267         stroke-width: 1px;     
268 }
269 .line3{
270         fill: none;
271         stroke: #00ff00;
272         stroke-width: 1px;     
273 }
274 .line4{
275         fill: none;
276         stroke: #ffcc00;
277         stroke-width: 1px;     
278 }
279 .line5{
280         fill: none;
281         stroke: #00ccff;
282         stroke-width: 1px;     
283 }
284 .line6{
285         fill: none;
286         stroke: #ff00ff;
287         stroke-width: 1px;     
288 }
289 .line7{
290         fill: none;
291         stroke: #00ffff;
292         stroke-width: 1px;     
293 }
294 .line8{
295         fill: none;
296         stroke: #ffff00;
297         stroke-width: 1px;     
298 }
299 .line9{
300         fill: none;
301         stroke: #ccc6666;
302         stroke-width: 1px;     
303 }
304 .line10{
305         fill: none;
306         stroke: #663399;
307         stroke-width: 1px;     
308 }
309 .line11{
310         fill: none;
311         stroke: #339900;
312         stroke-width: 1px;     
313 }
314 .line12{
315         fill: none;
316         stroke: #9966FF;
317         stroke-width: 1px;     
318 }
319 /* default fill styles */
320 .fill1{
321         fill: #cc0000;
322         fill-opacity: 0.2;
323         stroke: none;
324 }
325 .fill2{
326         fill: #0000cc;
327         fill-opacity: 0.2;
328         stroke: none;
329 }
330 .fill3{
331         fill: #00cc00;
332         fill-opacity: 0.2;
333         stroke: none;
334 }
335 .fill4{
336         fill: #ffcc00;
337         fill-opacity: 0.2;
338         stroke: none;
339 }
340 .fill5{
341         fill: #00ccff;
342         fill-opacity: 0.2;
343         stroke: none;
344 }
345 .fill6{
346         fill: #ff00ff;
347         fill-opacity: 0.2;
348         stroke: none;
349 }
350 .fill7{
351         fill: #00ffff;
352         fill-opacity: 0.2;
353         stroke: none;
354 }
355 .fill8{
356         fill: #ffff00;
357         fill-opacity: 0.2;
358         stroke: none;
359 }
360 .fill9{
361         fill: #cc6666;
362         fill-opacity: 0.2;
363         stroke: none;
364 }
365 .fill10{
366         fill: #663399;
367         fill-opacity: 0.2;
368         stroke: none;
369 }
370 .fill11{
371         fill: #339900;
372         fill-opacity: 0.2;
373         stroke: none;
374 }
375 .fill12{
376         fill: #9966FF;
377         fill-opacity: 0.2;
378         stroke: none;
379 }
380 /* default line styles */
381 .key1,.dataPoint1{
382         fill: #ff0000;
383         stroke: none;
384         stroke-width: 1px;     
385 }
386 .key2,.dataPoint2{
387         fill: #0000ff;
388         stroke: none;
389         stroke-width: 1px;     
390 }
391 .key3,.dataPoint3{
392         fill: #00ff00;
393         stroke: none;
394         stroke-width: 1px;     
395 }
396 .key4,.dataPoint4{
397         fill: #ffcc00;
398         stroke: none;
399         stroke-width: 1px;     
400 }
401 .key5,.dataPoint5{
402         fill: #00ccff;
403         stroke: none;
404         stroke-width: 1px;     
405 }
406 .key6,.dataPoint6{
407         fill: #ff00ff;
408         stroke: none;
409         stroke-width: 1px;     
410 }
411 .key7,.dataPoint7{
412         fill: #00ffff;
413         stroke: none;
414         stroke-width: 1px;     
415 }
416 .key8,.dataPoint8{
417         fill: #ffff00;
418         stroke: none;
419         stroke-width: 1px;     
420 }
421 .key9,.dataPoint9{
422         fill: #cc6666;
423         stroke: none;
424         stroke-width: 1px;     
425 }
426 .key10,.dataPoint10{
427         fill: #663399;
428         stroke: none;
429         stroke-width: 1px;     
430 }
431 .key11,.dataPoint11{
432         fill: #339900;
433         stroke: none;
434         stroke-width: 1px;     
435 }
436 .key12,.dataPoint12{
437         fill: #9966FF;
438         stroke: none;
439         stroke-width: 1px;     
440 }
441 EOL
442       end
get_x_labels() click to toggle source
    # File lib/SVG/Graph/Line.rb
145 def get_x_labels
146   @config[:fields]
147 end
get_y_labels() click to toggle source
    # File lib/SVG/Graph/Line.rb
155 def get_y_labels
156   maxvalue = max_value
157   minvalue = min_value
158   range = maxvalue - minvalue
159   top_pad = range == 0 ? 10 : range / 20.0
160   scale_range = (maxvalue + top_pad) - minvalue
161 
162   scale_division = scale_divisions || (scale_range / 10.0)
163 
164   if scale_integers
165     scale_division = scale_division < 1 ? 1 : scale_division.round
166   end
167 
168   rv = []
169   maxvalue = maxvalue%scale_division == 0 ? 
170     maxvalue : maxvalue + scale_division
171   minvalue.step( maxvalue, scale_division ) {|v| rv << v}
172   return rv
173 end
max_value() click to toggle source
    # File lib/SVG/Graph/Line.rb
111 def max_value
112     max = 0
113     
114     if (stacked == true) then
115       sums = Array.new(@config[:fields].length).fill(0)
116     
117       @data.each do |data|
118         sums.each_index do |i|
119           sums[i] += data[:data][i].to_f
120         end
121       end
122       
123       max = sums.max
124     else
125       max = @data.collect{|x| x[:data].max}.max
126     end
127     
128     return max
129 end
min_value() click to toggle source
    # File lib/SVG/Graph/Line.rb
131 def min_value
132   min = 0
133   
134   if (min_scale_value.nil? == false) then
135     min = min_scale_value
136   elsif (stacked == true) then
137     min = @data[-1][:data].min
138   else
139     min = @data.collect{|x| x[:data].min}.min
140   end
141 
142   return min
143 end