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.
-
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
Constants
- KEY_BOX_SIZE
Attributes
Add popups for the data points on some graphs
Set the font size (in points) of the data point labels
What the subtitle on the graph should be.
What the title on the graph should be.
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.
Whether to show a key, defaults to false, set to true if you want to show it.
Set the key font size
Where the key should be positioned, defaults to :right, set to :bottom if you want to move it.
The point at which the Y axis starts, defaults to '0', if set to nil it will default to the minimum data value.
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.
Customize popup radius
This turns the X axis labels by 90 degrees. Default it false, to turn on set to true.
This turns the Y axis labels by 90 degrees. Default it false, to turn on set to true.
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.
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.
(Bool) Show the value of each element of data on the graph
Whether to show a subtitle on the graph, defaults to false, set to true to show.
Whether to show a title on the graph, defaults to false, set to true to show.
Show guidelines for the X axis
Whether to show labels on the X axis or not, defaults to true, set to false if you want to turn them off.
Whether to show the title under the X axis labels, default is false, set to true to show.
Show guidelines for the Y axis
Whether to show labels on the Y axis or not, defaults to true, set to false if you want to turn them off.
Whether to show the title under the Y axis labels, default is false, set to true to show.
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.
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.
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).
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.
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.
Set the subtitle font size
Set the title font size
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.
Set the font size of the X axis labels
What the title under X axis should be, e.g. 'Months'.
Set the font size of the X axis title
Set the font size of the Y axis labels
What the title under Y axis should be, e.g. 'Sales in thousands'.
Set the font size of the Y axis title
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
# File lib/SVG/Graph/Graph.rb, line 100 def initialize( config ) @config = config @data = nil self.top_align = self.top_font = self.right_align = self.right_font = 0 init_with({ :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, :stagger_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 right :font_size =>12, :title_font_size =>16, :subtitle_font_size =>14, :x_label_font_size =>12, :y_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, }) set_defaults if self.respond_to? :set_defaults init_with config 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' })
# File lib/SVG/Graph/Graph.rb, line 166 def add_data conf @data = [] unless (defined? @data and !@data.nil?) if conf[:data] and conf[:data].kind_of? Array @data << conf else raise "No data provided by #{conf.inspect}" end 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
# File lib/SVG/Graph/Graph.rb, line 193 def burn raise "No data available" unless @data.size > 0 calculations if methods.include? 'calculations' start_svg calculate_graph_dimensions @foreground = Element.new( "g" ) draw_graph draw_titles draw_legend draw_data @graph.add_element( @foreground ) style data = "" @doc.write( data, 0 ) if @config[:compress] if @@__have_zlib inp, out = IO.pipe gz = Zlib::GzipWriter.new( out ) gz.write data gz.close data = inp.read else data << "<!-- Ruby Zlib not available for SVGZ -->"; end end return data 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
# File lib/SVG/Graph/Graph.rb, line 181 def clear_data @data = [] end
Protected Instance Methods
Adds pop-up point information to a graph.
# File lib/SVG/Graph/Graph.rb, line 414 def add_popup( x, y, label ) txt_width = label.length * font_size * 0.6 + 10 tx = (x+txt_width > width ? x-5 : x+5) t = @foreground.add_element( "text", { "x" => tx.to_s, "y" => (y - font_size).to_s, "visibility" => "hidden", }) t.attributes["style"] = "fill: #000; "+ (x+txt_width > width ? "text-anchor: end;" : "text-anchor: start;") t.text = label.to_s t.attributes["id"] = t.object_id.to_s @foreground.add_element( "circle", { "cx" => x.to_s, "cy" => y.to_s, "r" => "#{@popup_radius}", "style" => "opacity: 0", "onmouseover" => "document.getElementById(#{t.object_id}).setAttribute('visibility', 'visible' )", "onmouseout" => "document.getElementById(#{t.object_id}).setAttribute('visibility', 'hidden' )", }) end
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, line 443 def calculate_bottom_margin @border_bottom = 7 if key and key_position == :bottom @border_bottom += @data.size * (font_size + 5) @border_bottom += 10 end if show_x_labels max_x_label_height_px = (not rotate_x_labels) ? x_label_font_size : get_x_labels.max{|a,b| a.to_s.length<=>b.to_s.length }.to_s.length * x_label_font_size * 0.6 @border_bottom += max_x_label_height_px @border_bottom += max_x_label_height_px + 10 if stagger_x_labels end @border_bottom += x_title_font_size + 5 if show_x_title end
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, line 369 def calculate_left_margin @border_left = 7 # Check for Y labels max_y_label_height_px = @rotate_y_labels ? @y_label_font_size : get_y_labels.max{|a,b| a.to_s.length<=>b.to_s.length }.to_s.length * @y_label_font_size * 0.6 @border_left += max_y_label_height_px if @show_y_labels @border_left += max_y_label_height_px + 10 if @stagger_y_labels @border_left += y_title_font_size + 5 if @show_y_title end
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, line 392 def calculate_right_margin @border_right = 7 if key and key_position == :right val = keys.max { |a,b| a.length <=> b.length } @border_right += val.length * key_font_size * 0.6 @border_right += KEY_BOX_SIZE @border_right += 10 # Some padding around the box end end
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, line 405 def calculate_top_margin @border_top = 5 @border_top += title_font_size if show_graph_title @border_top += 5 @border_top += subtitle_font_size if show_graph_subtitle end
Draws the background, axis, and labels.
# File lib/SVG/Graph/Graph.rb, line 463 def draw_graph @graph = @root.add_element( "g", { "transform" => "translate( #@border_left #@border_top )" }) # Background @graph.add_element( "rect", { "x" => "0", "y" => "0", "width" => @graph_width.to_s, "height" => @graph_height.to_s, "class" => "graphBackground" }) # Axis @graph.add_element( "path", { "d" => "M 0 0 v#@graph_height", "class" => "axis", "id" => "xAxis" }) @graph.add_element( "path", { "d" => "M 0 #@graph_height h#@graph_width", "class" => "axis", "id" => "yAxis" }) draw_x_labels draw_y_labels end
Draws the legend on the graph
# File lib/SVG/Graph/Graph.rb, line 712 def draw_legend if key group = @root.add_element( "g" ) key_count = 0 for key_name in keys y_offset = (KEY_BOX_SIZE * key_count) + (key_count * 5) group.add_element( "rect", { "x" => 0.to_s, "y" => y_offset.to_s, "width" => KEY_BOX_SIZE.to_s, "height" => KEY_BOX_SIZE.to_s, "class" => "key#{key_count+1}" }) group.add_element( "text", { "x" => (KEY_BOX_SIZE + 5).to_s, "y" => (y_offset + KEY_BOX_SIZE).to_s, "class" => "keyText" }).text = key_name.to_s key_count += 1 end case key_position when :right x_offset = @graph_width + @border_left + 10 y_offset = @border_top + 20 when :bottom x_offset = @border_left + 20 y_offset = @border_top + @graph_height + 5 if show_x_labels max_x_label_height_px = (not rotate_x_labels) ? x_label_font_size : get_x_labels.max{|a,b| a.to_s.length<=>b.to_s.length }.to_s.length * x_label_font_size * 0.6 x_label_font_size y_offset += max_x_label_height_px y_offset += max_x_label_height_px + 5 if stagger_x_labels end y_offset += x_title_font_size + 5 if show_x_title end group.attributes["transform"] = "translate(#{x_offset} #{y_offset})" end end
Draws the graph title and subtitle
# File lib/SVG/Graph/Graph.rb, line 653 def draw_titles if show_graph_title @root.add_element( "text", { "x" => (width / 2).to_s, "y" => (title_font_size).to_s, "class" => "mainTitle" }).text = graph_title.to_s end if show_graph_subtitle y_subtitle = show_graph_title ? title_font_size + 10 : subtitle_font_size @root.add_element("text", { "x" => (width / 2).to_s, "y" => (y_subtitle).to_s, "class" => "subTitle" }).text = graph_subtitle.to_s end if show_x_title y = @graph_height + @border_top + x_title_font_size if show_x_labels y += x_label_font_size + 5 if stagger_x_labels y += x_label_font_size + 5 end x = width / 2 @root.add_element("text", { "x" => x.to_s, "y" => y.to_s, "class" => "xAxisTitle", }).text = x_title.to_s end if show_y_title x = y_title_font_size + (y_title_text_direction==:bt ? 3 : -3) y = height / 2 text = @root.add_element("text", { "x" => x.to_s, "y" => y.to_s, "class" => "yAxisTitle", }) text.text = y_title.to_s if y_title_text_direction == :bt text.attributes["transform"] = "rotate( -90, #{x}, #{y} )" else text.attributes["transform"] = "rotate( 90, #{x}, #{y} )" end end end
Draws the X axis guidelines
# File lib/SVG/Graph/Graph.rb, line 631 def draw_x_guidelines( label_height, count ) if count != 0 @graph.add_element( "path", { "d" => "M#{label_height*count} 0 v#@graph_height", "class" => "guideLines" }) end end
Draws the X axis labels
# File lib/SVG/Graph/Graph.rb, line 520 def draw_x_labels stagger = x_label_font_size + 5 if show_x_labels label_width = field_width count = 0 for label in get_x_labels if step_include_first_x_label == true then step = count % step_x_labels else step = (count + 1) % step_x_labels end if step == 0 then text = @graph.add_element( "text" ) text.attributes["class"] = "xAxisLabels" text.text = label.to_s x = count * label_width + x_label_offset( label_width ) y = @graph_height + x_label_font_size + 3 #t = 0 - (font_size / 2) if stagger_x_labels and count % 2 == 1 y += stagger @graph.add_element( "path", { "d" => "M#{x} #@graph_height v#{stagger}", "class" => "staggerGuideLine" }) end text.attributes["x"] = x.to_s text.attributes["y"] = y.to_s if rotate_x_labels text.attributes["transform"] = "rotate( 90 #{x} #{y-x_label_font_size} )"+ " translate( 0 -#{x_label_font_size/4} )" text.attributes["style"] = "text-anchor: start" else text.attributes["style"] = "text-anchor: middle" end end draw_x_guidelines( label_width, count ) if show_x_guidelines count += 1 end end end
Draws the Y axis guidelines
# File lib/SVG/Graph/Graph.rb, line 642 def draw_y_guidelines( label_height, count ) if count != 0 @graph.add_element( "path", { "d" => "M0 #{@graph_height-(label_height*count)} h#@graph_width", "class" => "guideLines" }) end end
Draws the Y axis labels
# File lib/SVG/Graph/Graph.rb, line 589 def draw_y_labels stagger = y_label_font_size + 5 if show_y_labels label_height = field_height count = 0 y_offset = @graph_height + y_label_offset( label_height ) y_offset += font_size/1.2 unless rotate_y_labels for label in get_y_labels y = y_offset - (label_height * count) x = rotate_y_labels ? 0 : -3 if stagger_y_labels and count % 2 == 1 x -= stagger @graph.add_element( "path", { "d" => "M#{x} #{y} h#{stagger}", "class" => "staggerGuideLine" }) end text = @graph.add_element( "text", { "x" => x.to_s, "y" => y.to_s, "class" => "yAxisLabels" }) text.text = label.to_s if rotate_y_labels text.attributes["transform"] = "translate( -#{font_size} 0 ) "+ "rotate( 90 #{x} #{y} ) " text.attributes["style"] = "text-anchor: middle" else text.attributes["y"] = (y - (y_label_font_size/2)).to_s text.attributes["style"] = "text-anchor: end" end draw_y_guidelines( label_height, count ) if show_y_guidelines count += 1 end end end
# File lib/SVG/Graph/Graph.rb, line 582 def field_height (@graph_height.to_f - font_size*2*top_font) / (get_y_labels.length - top_align) end
# File lib/SVG/Graph/Graph.rb, line 576 def field_width (@graph_width.to_f - font_size*2*right_font) / (get_x_labels.length - right_align) end
Overwrite configuration options with supplied options. Used by subclasses.
# File lib/SVG/Graph/Graph.rb, line 356 def init_with config config.each { |key, value| self.send( key.to_s+"=", value ) if self.respond_to? key } @popup_radius ||= 10 end
# File lib/SVG/Graph/Graph.rb, line 706 def keys i = 0 return @data.collect{ |d| i+=1; d[:title] || "Serie #{i}" } end
# File lib/SVG/Graph/Graph.rb, line 500 def make_datapoint_text( x, y, value, style="" ) if show_data_values @foreground.add_element( "text", { "x" => x.to_s, "y" => y.to_s, "class" => "dataPointLabel", "style" => "#{style} stroke: #fff; stroke-width: 2;" }).text = value.to_s text = @foreground.add_element( "text", { "x" => x.to_s, "y" => y.to_s, "class" => "dataPointLabel" }) text.text = value.to_s text.attributes["style"] = style if style.length > 0 end end
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, line 385 def max_y_label_width_px return font_size if rotate_y_labels end
# File lib/SVG/Graph/Graph.rb, line 350 def sort( *arrys ) sort_multiple( arrys ) end
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, line 496 def x_label_offset( width ) 0 end
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, line 571 def y_label_offset( height ) 0 end