class SVG::Graph::Graph

Base object for generating SVG Graphs

Synopsis

This class is only used as a superclass of specialized charts. Do not attempt to use this class directly, unless creating a new chart type.

For examples of how to subclass this class, see the existing specific subclasses, such as SVG::Graph::Pie.

Examples

For examples of how to use this package, see either the test files, or the documentation for the specific class you want to use.

Description

This package should be used as a base for creating SVG graphs.

Acknowledgements

Leo Lapworth for creating the SVG::TT::Graph package which this Ruby port is based on.

Stephen Morgan for creating the TT template and SVG.

See

Author

Sean E. Russell <serATgermaneHYPHENsoftwareDOTcom>

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

Constants

KEY_BOX_SIZE

Attributes

add_popups[RW]

Add popups for the data points on some graphs

font_size[RW]

Set the font size (in points) of the data point labels

graph_subtitle[RW]

What the subtitle on the graph should be.

graph_title[RW]

What the title on the graph should be.

height[RW]

Set the height of the graph box, this is the total height of the SVG box created - not the graph it self which auto scales to fix the space.

key[RW]

Whether to show a key, defaults to false, set to true if you want to show it.

key_font_size[RW]

Set the key font size

key_position[RW]

Where the key should be positioned, defaults to :right, set to :bottom if you want to move it.

min_scale_value[RW]

The point at which the Y axis starts, defaults to '0', if set to nil it will default to the minimum data value.

no_css[RW]

Do not use CSS if set to true. Many SVG viewers do not support CSS, but not using CSS can result in larger SVGs as well as making it impossible to change colors after the chart is generated. Defaults to false.

popup_radius[RW]

Customize popup radius

right_align[RW]
right_font[RW]
rotate_x_labels[RW]

This turns the X axis labels by 90 degrees. Default it false, to turn on set to true.

rotate_y_labels[RW]

This turns the Y axis labels by 90 degrees. Default it false, to turn on set to true.

scale_divisions[RW]

This defines the gap between markers on the Y axis, default is a 10th of the max_value, e.g. you will have 10 markers on the Y axis. NOTE: do not set this too low - you are limited to 999 markers, after that the graph won't generate.

scale_integers[RW]

Ensures only whole numbers are used as the scale divisions. Default it false, to turn on set to true. This has no effect if scale divisions are less than 1.

show_data_values[RW]

(Bool) Show the value of each element of data on the graph

show_graph_subtitle[RW]

Whether to show a subtitle on the graph, defaults to false, set to true to show.

show_graph_title[RW]

Whether to show a title on the graph, defaults to false, set to true to show.

show_x_guidelines[RW]

Show guidelines for the X axis

show_x_labels[RW]

Whether to show labels on the X axis or not, defaults to true, set to false if you want to turn them off.

show_x_title[RW]

Whether to show the title under the X axis labels, default is false, set to true to show.

show_y_guidelines[RW]

Show guidelines for the Y axis

show_y_labels[RW]

Whether to show labels on the Y axis or not, defaults to true, set to false if you want to turn them off.

show_y_title[RW]

Whether to show the title under the Y axis labels, default is false, set to true to show.

stagger_x_labels[RW]

This puts the X labels at alternative levels so if they are long field names they will not overlap so easily. Default it false, to turn on set to true.

stagger_y_labels[RW]

This puts the Y labels at alternative levels so if they are long field names they will not overlap so easily. Default it false, to turn on set to true.

step_include_first_x_label[RW]

Whether to (when taking “steps” between X axis labels) step from the first label (i.e. always include the first label) or step from the X axis origin (i.e. start with a gap if step_x_labels is greater than one).

step_x_labels[RW]

How many “steps” to use between displayed X axis labels, a step of one means display every label, a step of two results in every other label being displayed (label <gap> label <gap> label), a step of three results in every third label being displayed (label <gap> <gap> label <gap> <gap> label) and so on.

style_sheet[RW]

Set the path to an external stylesheet, set to '' if you want to revert back to using the defaut internal version.

To create an external stylesheet create a graph using the default internal version and copy the stylesheet section to an external file and edit from there.

subtitle_font_size[RW]

Set the subtitle font size

title_font_size[RW]

Set the title font size

top_align[RW]
top_font[RW]
width[RW]

Set the width of the graph box, this is the total width of the SVG box created - not the graph it self which auto scales to fix the space.

x_label_font_size[RW]

Set the font size of the X axis labels

x_title[RW]

What the title under X axis should be, e.g. 'Months'.

x_title_font_size[RW]

Set the font size of the X axis title

y_label_font_size[RW]

Set the font size of the Y axis labels

y_title[RW]

What the title under Y axis should be, e.g. 'Sales in thousands'.

y_title_font_size[RW]

Set the font size of the Y axis title

y_title_text_direction[RW]

Aligns writing mode for Y axis label. Defaults to :bt (Bottom to Top). Change to :tb (Top to Bottom) to reverse.

Public Class Methods

new( config ) click to toggle source

Initialize the graph object with the graph settings. You won't instantiate this class directly; see the subclass for options.

width

500

height

300

show_x_guidelines

false

show_y_guidelines

true

show_data_values

true

min_scale_value

0

show_x_labels

true

stagger_x_labels

false

rotate_x_labels

false

step_x_labels

1

step_include_first_x_label

true

show_y_labels

true

rotate_y_labels

false

scale_integers

false

show_x_title

false

x_title

'X Field names'

show_y_title

false

y_title_text_direction

:bt

y_title

'Y Scale'

show_graph_title

false

graph_title

'Graph Title'

show_graph_subtitle

false

graph_subtitle

'Graph Sub Title'

key

true,

key_position

:right, # bottom or righ

font_size

12

title_font_size

16

subtitle_font_size

14

x_label_font_size

12

x_title_font_size

14

y_label_font_size

12

y_title_font_size

14

key_font_size

10

no_css

false

add_popups

false

    # File lib/SVG/Graph/Graph.rb
100       def initialize( config )
101         @config = config
102         @data = nil
103         self.top_align = self.top_font = self.right_align = self.right_font = 0
104 
105         init_with({
106           :width                => 500,
107           :height                => 300,
108           :show_x_guidelines    => false,
109           :show_y_guidelines    => true,
110           :show_data_values     => true,
111 
112 #          :min_scale_value      => 0,
113 
114           :show_x_labels        => true,
115           :stagger_x_labels     => false,
116           :rotate_x_labels      => false,
117           :step_x_labels        => 1,
118           :step_include_first_x_label => true,
119 
120           :show_y_labels        => true,
121           :rotate_y_labels      => false,
122           :stagger_y_labels     => false,
123           :scale_integers       => false,
124 
125           :show_x_title         => false,
126           :x_title              => 'X Field names',
127 
128           :show_y_title         => false,
129           :y_title_text_direction => :bt,
130           :y_title              => 'Y Scale',
131 
132           :show_graph_title      => false,
133           :graph_title          => 'Graph Title',
134           :show_graph_subtitle  => false,
135           :graph_subtitle        => 'Graph Sub Title',
136           :key                  => true, 
137           :key_position          => :right, # bottom or right
138 
139           :font_size            =>12,
140           :title_font_size      =>16,
141           :subtitle_font_size   =>14,
142           :x_label_font_size    =>12,
143           :y_label_font_size    =>12,
144           :x_title_font_size    =>14,
145           :y_label_font_size    =>12,
146           :y_title_font_size    =>14,
147           :key_font_size        =>10,
148           
149           :no_css               =>false,
150           :add_popups           =>false,
151         })
152         set_defaults if self.respond_to? :set_defaults
153         init_with config
154       end

Public Instance Methods

add_data(conf) click to toggle source

This method allows you do add data to the graph object. It can be called several times to add more data sets in.

data_sales_02 = [12, 45, 21];

graph.add_data({
  :data => data_sales_02,
  :title => 'Sales 2002'
})
    # File lib/SVG/Graph/Graph.rb
166 def add_data conf
167     @data = [] unless (defined? @data and !@data.nil?) 
168 
169   if conf[:data] and conf[:data].kind_of? Array
170     @data << conf
171   else
172     raise "No data provided by #{conf.inspect}"
173   end
174 end
burn() click to toggle source

This method processes the template with the data and config which has been set and returns the resulting SVG.

This method will croak unless at least one data set has been added to the graph object.

print graph.burn
    # File lib/SVG/Graph/Graph.rb
193 def burn
194   raise "No data available" unless @data.size > 0
195   
196   calculations if methods.include? 'calculations'
197 
198   start_svg
199   calculate_graph_dimensions
200   @foreground = Element.new( "g" )
201   draw_graph
202   draw_titles
203   draw_legend
204   draw_data
205   @graph.add_element( @foreground )
206   style
207 
208   data = ""
209   @doc.write( data, 0 )
210 
211   if @config[:compress]
212     if @@__have_zlib
213       inp, out = IO.pipe
214       gz = Zlib::GzipWriter.new( out )
215       gz.write data
216       gz.close
217       data = inp.read
218     else
219       data << "<!-- Ruby Zlib not available for SVGZ -->";
220     end
221   end
222   
223   return data
224 end
clear_data() click to toggle source

This method removes all data from the object so that you can reuse it to create a new graph but with the same config options.

graph.clear_data
    # File lib/SVG/Graph/Graph.rb
181 def clear_data 
182   @data = []
183 end

Protected Instance Methods

add_popup( x, y, label ) click to toggle source

Adds pop-up point information to a graph.

    # File lib/SVG/Graph/Graph.rb
414 def add_popup( x, y, label )
415   txt_width = label.length * font_size * 0.6 + 10
416   tx = (x+txt_width > width ? x-5 : x+5)
417   t = @foreground.add_element( "text", {
418     "x" => tx.to_s,
419     "y" => (y - font_size).to_s,
420     "visibility" => "hidden",
421   })
422   t.attributes["style"] = "fill: #000; "+
423     (x+txt_width > width ? "text-anchor: end;" : "text-anchor: start;")
424   t.text = label.to_s
425   t.attributes["id"] = t.object_id.to_s
426 
427   @foreground.add_element( "circle", {
428     "cx" => x.to_s,
429     "cy" => y.to_s,
430     "r" => "#{@popup_radius}",
431     "style" => "opacity: 0",
432     "onmouseover" => 
433       "document.getElementById(#{t.object_id}).setAttribute('visibility', 'visible' )",
434     "onmouseout" => 
435       "document.getElementById(#{t.object_id}).setAttribute('visibility', 'hidden' )",
436   })
437 
438 end
calculate_bottom_margin() click to toggle source

Override this (and call super) to change the margin to the bottom of the plot area. Results in @border_bottom being set.

    # File lib/SVG/Graph/Graph.rb
443 def calculate_bottom_margin
444   @border_bottom = 7
445   if key and key_position == :bottom
446     @border_bottom += @data.size * (font_size + 5)
447     @border_bottom += 10
448   end
449   if show_x_labels
450             max_x_label_height_px = (not rotate_x_labels) ? 
451       x_label_font_size :
452       get_x_labels.max{|a,b| 
453         a.to_s.length<=>b.to_s.length
454       }.to_s.length * x_label_font_size * 0.6
455     @border_bottom += max_x_label_height_px
456     @border_bottom += max_x_label_height_px + 10 if stagger_x_labels
457   end
458   @border_bottom += x_title_font_size + 5 if show_x_title
459 end
calculate_left_margin() click to toggle source

Override this (and call super) to change the margin to the left of the plot area. Results in @border_left being set.

    # File lib/SVG/Graph/Graph.rb
369 def calculate_left_margin
370   @border_left = 7
371   # Check for Y labels
372   max_y_label_height_px = @rotate_y_labels ? 
373     @y_label_font_size :
374     get_y_labels.max{|a,b| 
375       a.to_s.length<=>b.to_s.length
376     }.to_s.length * @y_label_font_size * 0.6
377   @border_left += max_y_label_height_px if @show_y_labels
378   @border_left += max_y_label_height_px + 10 if @stagger_y_labels
379   @border_left += y_title_font_size + 5 if @show_y_title
380 end
calculate_right_margin() click to toggle source

Override this (and call super) to change the margin to the right of the plot area. Results in @border_right being set.

    # File lib/SVG/Graph/Graph.rb
392 def calculate_right_margin
393   @border_right = 7
394   if key and key_position == :right
395     val = keys.max { |a,b| a.length <=> b.length }
396     @border_right += val.length * key_font_size * 0.6 
397     @border_right += KEY_BOX_SIZE
398     @border_right += 10    # Some padding around the box
399   end
400 end
calculate_top_margin() click to toggle source

Override this (and call super) to change the margin to the top of the plot area. Results in @border_top being set.

    # File lib/SVG/Graph/Graph.rb
405 def calculate_top_margin
406   @border_top = 5
407   @border_top += title_font_size if show_graph_title
408   @border_top += 5
409   @border_top += subtitle_font_size if show_graph_subtitle
410 end
draw_graph() click to toggle source

Draws the background, axis, and labels.

    # File lib/SVG/Graph/Graph.rb
463 def draw_graph
464   @graph = @root.add_element( "g", {
465     "transform" => "translate( #@border_left #@border_top )"
466   })
467 
468   # Background
469   @graph.add_element( "rect", {
470     "x" => "0",
471     "y" => "0",
472     "width" => @graph_width.to_s,
473     "height" => @graph_height.to_s,
474     "class" => "graphBackground"
475   })
476 
477   # Axis
478   @graph.add_element( "path", {
479     "d" => "M 0 0 v#@graph_height",
480     "class" => "axis",
481     "id" => "xAxis"
482   })
483   @graph.add_element( "path", {
484     "d" => "M 0 #@graph_height h#@graph_width",
485     "class" => "axis",
486     "id" => "yAxis"
487   })
488 
489   draw_x_labels
490   draw_y_labels
491 end
draw_legend() click to toggle source

Draws the legend on the graph

    # File lib/SVG/Graph/Graph.rb
712 def draw_legend
713   if key
714     group = @root.add_element( "g" )
715 
716     key_count = 0
717     for key_name in keys
718       y_offset = (KEY_BOX_SIZE * key_count) + (key_count * 5)
719       group.add_element( "rect", {
720         "x" => 0.to_s,
721         "y" => y_offset.to_s,
722         "width" => KEY_BOX_SIZE.to_s,
723         "height" => KEY_BOX_SIZE.to_s,
724         "class" => "key#{key_count+1}"
725       })
726       group.add_element( "text", {
727         "x" => (KEY_BOX_SIZE + 5).to_s,
728         "y" => (y_offset + KEY_BOX_SIZE).to_s,
729         "class" => "keyText"
730       }).text = key_name.to_s
731       key_count += 1
732     end
733 
734     case key_position
735     when :right
736       x_offset = @graph_width + @border_left + 10
737       y_offset = @border_top + 20
738     when :bottom
739       x_offset = @border_left + 20
740       y_offset = @border_top + @graph_height + 5
741       if show_x_labels
742                     max_x_label_height_px = (not rotate_x_labels) ? 
743                           x_label_font_size :
744                           get_x_labels.max{|a,b| 
745                             a.to_s.length<=>b.to_s.length
746                           }.to_s.length * x_label_font_size * 0.6
747           x_label_font_size
748         y_offset += max_x_label_height_px
749         y_offset += max_x_label_height_px + 5 if stagger_x_labels
750       end
751       y_offset += x_title_font_size + 5 if show_x_title
752     end
753     group.attributes["transform"] = "translate(#{x_offset} #{y_offset})"
754   end
755 end
draw_titles() click to toggle source

Draws the graph title and subtitle

    # File lib/SVG/Graph/Graph.rb
653 def draw_titles
654   if show_graph_title
655     @root.add_element( "text", {
656       "x" => (width / 2).to_s,
657       "y" => (title_font_size).to_s,
658       "class" => "mainTitle"
659     }).text = graph_title.to_s
660   end
661 
662   if show_graph_subtitle
663     y_subtitle = show_graph_title ? 
664       title_font_size + 10 :
665       subtitle_font_size
666     @root.add_element("text", {
667       "x" => (width / 2).to_s,
668       "y" => (y_subtitle).to_s,
669       "class" => "subTitle"
670     }).text = graph_subtitle.to_s
671   end
672 
673   if show_x_title
674     y = @graph_height + @border_top + x_title_font_size
675     if show_x_labels
676       y += x_label_font_size + 5 if stagger_x_labels
677       y += x_label_font_size + 5
678     end
679     x = width / 2
680 
681     @root.add_element("text", {
682       "x" => x.to_s,
683       "y" => y.to_s,
684       "class" => "xAxisTitle",
685     }).text = x_title.to_s
686   end
687 
688   if show_y_title
689     x = y_title_font_size + (y_title_text_direction==:bt ? 3 : -3)
690     y = height / 2
691 
692     text = @root.add_element("text", {
693       "x" => x.to_s,
694       "y" => y.to_s,
695       "class" => "yAxisTitle",
696     })
697     text.text = y_title.to_s
698     if y_title_text_direction == :bt
699       text.attributes["transform"] = "rotate( -90, #{x}, #{y} )"
700     else
701       text.attributes["transform"] = "rotate( 90, #{x}, #{y} )"
702     end
703   end
704 end
draw_x_guidelines( label_height, count ) click to toggle source

Draws the X axis guidelines

    # File lib/SVG/Graph/Graph.rb
631 def draw_x_guidelines( label_height, count )
632   if count != 0
633     @graph.add_element( "path", {
634       "d" => "M#{label_height*count} 0 v#@graph_height",
635       "class" => "guideLines"
636     })
637   end
638 end
draw_x_labels() click to toggle source

Draws the X axis labels

    # File lib/SVG/Graph/Graph.rb
520 def draw_x_labels
521   stagger = x_label_font_size + 5
522   if show_x_labels
523     label_width = field_width
524 
525     count = 0
526     for label in get_x_labels
527       if step_include_first_x_label == true then
528         step = count % step_x_labels
529       else
530         step = (count + 1) % step_x_labels
531       end
532 
533       if step == 0 then
534         text = @graph.add_element( "text" )
535         text.attributes["class"] = "xAxisLabels"
536         text.text = label.to_s
537 
538         x = count * label_width + x_label_offset( label_width )
539         y = @graph_height + x_label_font_size + 3
540         #t = 0 - (font_size / 2)
541 
542         if stagger_x_labels and count % 2 == 1
543           y += stagger
544           @graph.add_element( "path", {
545             "d" => "M#{x} #@graph_height v#{stagger}",
546             "class" => "staggerGuideLine"
547           })
548         end
549 
550         text.attributes["x"] = x.to_s
551         text.attributes["y"] = y.to_s
552         if rotate_x_labels
553           text.attributes["transform"] = 
554             "rotate( 90 #{x} #{y-x_label_font_size} )"+
555             " translate( 0 -#{x_label_font_size/4} )"
556           text.attributes["style"] = "text-anchor: start"
557         else
558           text.attributes["style"] = "text-anchor: middle"
559         end
560       end
561 
562       draw_x_guidelines( label_width, count ) if show_x_guidelines
563       count += 1
564     end
565   end
566 end
draw_y_guidelines( label_height, count ) click to toggle source

Draws the Y axis guidelines

    # File lib/SVG/Graph/Graph.rb
642 def draw_y_guidelines( label_height, count )
643   if count != 0
644     @graph.add_element( "path", {
645       "d" => "M0 #{@graph_height-(label_height*count)} h#@graph_width",
646       "class" => "guideLines"
647     })
648   end
649 end
draw_y_labels() click to toggle source

Draws the Y axis labels

    # File lib/SVG/Graph/Graph.rb
589 def draw_y_labels
590   stagger = y_label_font_size + 5
591   if show_y_labels
592     label_height = field_height
593 
594     count = 0
595     y_offset = @graph_height + y_label_offset( label_height )
596     y_offset += font_size/1.2 unless rotate_y_labels
597     for label in get_y_labels
598       y = y_offset - (label_height * count)
599       x = rotate_y_labels ? 0 : -3
600 
601       if stagger_y_labels and count % 2 == 1
602         x -= stagger
603         @graph.add_element( "path", {
604           "d" => "M#{x} #{y} h#{stagger}",
605           "class" => "staggerGuideLine"
606         })
607       end
608 
609       text = @graph.add_element( "text", {
610         "x" => x.to_s,
611         "y" => y.to_s,
612         "class" => "yAxisLabels"
613       })
614       text.text = label.to_s
615       if rotate_y_labels
616         text.attributes["transform"] = "translate( -#{font_size} 0 ) "+
617           "rotate( 90 #{x} #{y} ) "
618         text.attributes["style"] = "text-anchor: middle"
619       else
620         text.attributes["y"] = (y - (y_label_font_size/2)).to_s
621         text.attributes["style"] = "text-anchor: end"
622       end
623       draw_y_guidelines( label_height, count ) if show_y_guidelines
624       count += 1
625     end
626   end
627 end
field_height() click to toggle source
    # File lib/SVG/Graph/Graph.rb
582 def field_height
583   (@graph_height.to_f - font_size*2*top_font) /
584      (get_y_labels.length - top_align)
585 end
field_width() click to toggle source
    # File lib/SVG/Graph/Graph.rb
576 def field_width
577   (@graph_width.to_f - font_size*2*right_font) /
578      (get_x_labels.length - right_align)
579 end
init_with(config) click to toggle source

Overwrite configuration options with supplied options. Used by subclasses.

    # File lib/SVG/Graph/Graph.rb
356 def init_with config
357   config.each { |key, value|
358       self.send( key.to_s+"=", value ) if self.respond_to?  key
359   }
360   @popup_radius ||= 10
361 end
keys() click to toggle source
    # File lib/SVG/Graph/Graph.rb
706 def keys 
707   i = 0
708   return @data.collect{ |d| i+=1; d[:title] || "Serie #{i}" }
709 end
make_datapoint_text( x, y, value, style="" ) click to toggle source
    # File lib/SVG/Graph/Graph.rb
500 def make_datapoint_text( x, y, value, style="" )
501   if show_data_values
502     @foreground.add_element( "text", {
503       "x" => x.to_s,
504       "y" => y.to_s,
505       "class" => "dataPointLabel",
506       "style" => "#{style} stroke: #fff; stroke-width: 2;"
507     }).text = value.to_s
508     text = @foreground.add_element( "text", {
509       "x" => x.to_s,
510       "y" => y.to_s,
511       "class" => "dataPointLabel"
512     })
513     text.text = value.to_s
514     text.attributes["style"] = style if style.length > 0
515   end
516 end
max_y_label_width_px() click to toggle source

Calculates the width of the widest Y label. This will be the character height if the Y labels are rotated

    # File lib/SVG/Graph/Graph.rb
385 def max_y_label_width_px
386   return font_size if rotate_y_labels
387 end
sort( *arrys ) click to toggle source
    # File lib/SVG/Graph/Graph.rb
350 def sort( *arrys )
351   sort_multiple( arrys )
352 end
x_label_offset( width ) click to toggle source

Where in the X area the label is drawn Centered in the field, should be width/2. Start, 0.

    # File lib/SVG/Graph/Graph.rb
496 def x_label_offset( width )
497   0
498 end
y_label_offset( height ) click to toggle source

Where in the Y area the label is drawn Centered in the field, should be width/2. Start, 0.

    # File lib/SVG/Graph/Graph.rb
571 def y_label_offset( height )
572   0
573 end