Class SVG::Graph::Graph
In: lib/SVG/Graph/Graph.rb
Parent: Object

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.

  • file:test/plot.rb
  • file:test/single.rb
  • file:test/test.rb
  • file:test/timeseries.rb

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

Methods

Included Modules

REXML

Constants

KEY_BOX_SIZE = 12

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

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

[Source]

     # File lib/SVG/Graph/Graph.rb, line 100
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

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'
  })

[Source]

     # File lib/SVG/Graph/Graph.rb, line 166
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

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

[Source]

     # File lib/SVG/Graph/Graph.rb, line 193
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

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

[Source]

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

Protected Instance methods

Adds pop-up point information to a graph.

[Source]

     # File lib/SVG/Graph/Graph.rb, line 414
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

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

[Source]

     # File lib/SVG/Graph/Graph.rb, line 443
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

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

[Source]

     # File lib/SVG/Graph/Graph.rb, line 369
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

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

[Source]

     # File lib/SVG/Graph/Graph.rb, line 392
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

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

[Source]

     # File lib/SVG/Graph/Graph.rb, line 405
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

Draws the background, axis, and labels.

[Source]

     # File lib/SVG/Graph/Graph.rb, line 463
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

Draws the legend on the graph

[Source]

     # File lib/SVG/Graph/Graph.rb, line 712
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

Draws the graph title and subtitle

[Source]

     # File lib/SVG/Graph/Graph.rb, line 653
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

Draws the X axis guidelines

[Source]

     # File lib/SVG/Graph/Graph.rb, line 631
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

Draws the X axis labels

[Source]

     # File lib/SVG/Graph/Graph.rb, line 520
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

Draws the Y axis guidelines

[Source]

     # File lib/SVG/Graph/Graph.rb, line 642
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

Draws the Y axis labels

[Source]

     # File lib/SVG/Graph/Graph.rb, line 589
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

[Source]

     # File lib/SVG/Graph/Graph.rb, line 582
582:       def field_height
583:         (@graph_height.to_f - font_size*2*top_font) /
584:            (get_y_labels.length - top_align)
585:       end

[Source]

     # File lib/SVG/Graph/Graph.rb, line 576
576:       def field_width
577:         (@graph_width.to_f - font_size*2*right_font) /
578:            (get_x_labels.length - right_align)
579:       end

Overwrite configuration options with supplied options. Used by subclasses.

[Source]

     # File lib/SVG/Graph/Graph.rb, line 356
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

[Source]

     # File lib/SVG/Graph/Graph.rb, line 706
706:       def keys 
707:         i = 0
708:         return @data.collect{ |d| i+=1; d[:title] || "Serie #{i}" }
709:       end

[Source]

     # File lib/SVG/Graph/Graph.rb, line 500
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

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

[Source]

     # File lib/SVG/Graph/Graph.rb, line 385
385:       def max_y_label_width_px
386:         return font_size if rotate_y_labels
387:       end

[Source]

     # File lib/SVG/Graph/Graph.rb, line 350
350:       def sort( *arrys )
351:         sort_multiple( arrys )
352:       end

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

[Source]

     # File lib/SVG/Graph/Graph.rb, line 496
496:       def x_label_offset( width )
497:         0
498:       end

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

[Source]

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

[Validate]