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
Attributes
Add popups for the data points on some graphs, default is false.
Set the font size (in points) of the data point labels. Defaults to 12.
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.
Define as String the stylesheet contents to be inlined, set to '' to disable. This can be used, when referring to a url via :style_sheet is not suitable. E.g. in situations where there will be no internet access or the graph must consist of only one file.
If not empty, the :style_sheet parameter (url) above will be ignored and is not written to the file see also github.com/erullmann/svg-graph2/commit/55eb6e983f6fcc69cc5a110d0ee6e05f906f639a Default: ''
Whether to show a key (legend), defaults to true, set to false if you want to hide it.
Set the key font size. Defaults to 10.
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 nil, 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.
Number format values and Y axis representation like 1.2345667 represent as 1.23, Any valid format accepted by sprintf can be specified. If you don't want to change the format in any way you can use “%s”. Defaults to “%.2f”
Customize popup radius
This turns the X axis labels by 90 degrees when true or by a custom amount when a numeric value is given. Default is false, to turn on set to true.
This turns the Y axis labels by 90 degrees when true or by a custom amount when a numeric value is given. Default is false, to turn on set to true or numeric value.
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 is 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, default is false
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, default is true
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 is 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 is 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/url 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. Defaults to 14.
Set the title font size. Defaults to 16.
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.
By default (nil/undefined) the x-axis is at the bottom of the graph. With this property a custom position for the x-axis can be defined. Valid values are between :min_scale_value and maximum value of the data. Default: nil
Set the font size of the X axis labels. Defaults to 12.
What the title under X axis should be, e.g. 'Months'.
Set the font size of the X axis title. Defaults to 14.
Where the x_title
should be positioned, either in the :middle of the axis or at the :end of the axis. Defaults to :middle
By default (nil/undefined) the y-axis is the left border of the graph. With this property a custom position for the y-axis can be defined. Valid values are any values in the range of x-values (in case of a Plot
) or any of the :fields values (in case of Line/Bar Graphs, note the '==' operator is used to find at which value to draw the axis). Default: nil
Set the font size of the Y axis labels. Defaults to 12.
What the title under Y axis should be, e.g. 'Sales in thousands'.
Set the font size of the Y axis title. Defaults to 14.
Where the y_title
should be positioned, either in the :middle of the axis or at the :end of the axis. Defaults to :middle
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
x_axis_position
-
nil
y_axis_position
-
nil
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'
x_title_location
-
:middle | :end
show_y_title
-
false
y_title_text_direction
-
:bt | :tb
y_title
-
'Y Scale'
y_title_location
-
:middle | :end
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
number_format
-
'%.2f'
# File lib/SVG/Graph/Graph.rb, line 104 def initialize( config ) @config = config # array of Hash @data = [] #self.top_align = self.top_font = 0 #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, :x_axis_position => nil, :y_axis_position => nil, :min_scale_value => nil, :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', :x_title_location => :middle, # or :end :show_y_title => false, :y_title_text_direction => :bt, # other option is :tb :y_title => 'Y Scale', :y_title_location => :middle, # or :end :show_graph_title => false, :graph_title => 'Graph Title', :show_graph_subtitle => false, :graph_subtitle => 'Graph Sub Title', :key => true, :key_width => nil, :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_title_font_size => 14, :key_font_size => 10, :key_box_size => 12, :key_spacing => 5, :no_css => false, :add_popups => false, :popup_radius => 10, :number_format => '%.2f', :style_sheet => '', :inline_style_sheet => '' }) set_defaults if self.respond_to? :set_defaults # override default values with user supplied values 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' })
@param conf [Hash] with the following keys:
:data [Array] mandatory :title [String] mandatory name of data series for legend of graph :description [Array<String>] (optional) if given, description for each datapoint (shown in popups) :shape [Array<String>] (optional) if given, DataPoint shape is chosen based on this string instead of description :url [Array<String>] (optional) if given, link will be added to each datapoint
# File lib/SVG/Graph/Graph.rb, line 190 def add_data(conf) @data ||= [] raise "No data provided by #{conf.inspect}" unless conf[:data].is_a?(Array) add_data_init_or_check_optional_keys(conf, conf[:data].size) @data << conf end
Checks all optional keys of the add_data
method
# File lib/SVG/Graph/Graph.rb, line 199 def add_data_init_or_check_optional_keys(conf, datasize) conf[:description] ||= Array.new(datasize) conf[:shape] ||= Array.new(datasize) conf[:url] ||= Array.new(datasize) if conf[:description].size != datasize raise "Description for popups does not have same size as provided data: #{conf[:description].size} vs #{conf[:data].size/2}" end if conf[:shape].size != datasize raise "Shapes for points do not have same size as provided data: #{conf[:shape].size} vs #{conf[:data].size/2}" end if conf[:url].size != datasize raise "URLs for points do not have same size as provided data: #{conf[:url].size} vs #{conf[:data].size/2}" 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 234 def burn raise "No data available" unless @data.size > 0 start_svg calculate_graph_dimensions @foreground = Element.new( "g" ) draw_graph draw_titles draw_legend draw_data # this method needs to be implemented by child classes @graph.add_element( @foreground ) style data = "" @doc.write( data, 0 ) if @config[:compress] if defined?(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
Burns the graph but returns only the <svg> node as String without the Doctype and XML Declaration. This allows easy integration into existing xml documents.
@return [String] the SVG
node which represents the Graph
# File lib/SVG/Graph/Graph.rb, line 270 def burn_svg_only # initialize all instance variables by burning the graph burn f = REXML::Formatters::Pretty.new(0) f.compact = true out = '' f.write(@root, out) return out 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 221 def clear_data @data = [] end
Burns the graph to an SVG
string and returns it with a text/html mime type to be displayed in IRuby.
@return [Array] A 2-dimension array containing the SVg string and a mime-type. This is the format expected by IRuby.
# File lib/SVG/Graph/Graph.rb, line 284 def to_iruby ["text/html", burn_svg_only] end
Protected Instance Methods
# File lib/SVG/Graph/Graph.rb, line 548 def add_datapoint_text_and_popup( x, y, label ) add_popup( x, y, label ) make_datapoint_text( x, y, label ) end
Adds pop-up point information to a graph only if the config option is set.
# File lib/SVG/Graph/Graph.rb, line 554 def add_popup( x, y, label, style="", url="" ) if add_popups if( numeric?(label) ) label = @number_format % label end txt_width = label.length * font_size * 0.6 + 10 tx = (x+txt_width > @graph_width ? x-5 : x+5) g = Element.new( "g" ) g.attributes["id"] = g.object_id.to_s g.attributes["visibility"] = "hidden" # First add the mask t = g.add_element( "text", { "x" => tx.to_s, "y" => (y - font_size).to_s, "class" => "dataPointPopupMask" }) t.attributes["style"] = style + (x+txt_width > @graph_width ? "text-anchor: end;" : "text-anchor: start;") t.text = label.to_s # Then add the text t = g.add_element( "text", { "x" => tx.to_s, "y" => (y - font_size).to_s, "class" => "dataPointPopup" }) t.attributes["style"] = style + (x+txt_width > @graph_width ? "text-anchor: end;" : "text-anchor: start;") t.text = label.to_s @foreground.add_element( g ) # add a circle to catch the mouseover mouseover = Element.new( "circle" ) mouseover.add_attributes({ "cx" => x.to_s, "cy" => y.to_s, "r" => "#{popup_radius}", "style" => "opacity: 0", "onmouseover" => "document.getElementById(#{g.object_id.to_s}).style.visibility ='visible'", "onmouseout" => "document.getElementById(#{g.object_id.to_s}).style.visibility = 'hidden'", }) if !url.nil? href = Element.new("a") href.add_attribute("xlink:href", url) href.add_element(mouseover) @foreground.add_element(href) else @foreground.add_element(mouseover) end elsif !url.nil? # add a circle to catch the mouseover mouseover = Element.new( "circle" ) mouseover.add_attributes({ "cx" => x.to_s, "cy" => y.to_s, "r" => "#{popup_radius}", "style" => "opacity: 0", }) href = Element.new("a") href.add_attribute("xlink:href", url) href.add_element(mouseover) @foreground.add_element(href) end # if add_popups end
Override this (and call super) to change the margin to the bottom of the plot area. Results in @border_bottom being set.
7 + max label height(font size or string length, depending on rotate) + title height
# File lib/SVG/Graph/Graph.rb, line 640 def calculate_bottom_margin @border_bottom = 7 if key and key_position == :bottom @border_bottom += @data.size * (font_size + 5) @border_bottom += 10 end @border_bottom += max_x_label_height_px if (show_x_title && (x_title_location ==:middle)) @border_bottom += x_title_font_size + 5 end end
Override this (and call super) to change the margin to the left of the plot area. Results in @border_left being set.
By default it is 7 + max label height(font size or string length, depending on rotate) + title height
# File lib/SVG/Graph/Graph.rb, line 480 def calculate_left_margin @border_left = 7 # Check size of Y labels @border_left += max_y_label_width_px if (show_y_title && (y_title_location ==:middle)) @border_left += y_title_font_size + 5 end end
calculates the relative position betewen 0 and 1 of a value on the axis can be multiplied with either @graph_height or @graph_width to get the absolute position in pixels. If labels are strings, checks if one of label matches with the value and returns this position. If labels are numeric, compute relative position between first and last value If nothing else applies or the value is nil, the relative position is 0 @param labels [Array] the array of x or y labels, see {#get_x_labels} or {#get_y_labels} @param segment_px [Float] number of pixels per label, see {#field_width} or {#field_height} @param value [Numeric, String] the value for which the relative position is computed @param axis_length [Numeric] either @graph_width or @graph_height @return [Float] relative position between 0 and 1, returns 0
# File lib/SVG/Graph/Graph.rb, line 727 def calculate_rel_position(labels, segment_px, value, axis_length) # default value, y-axis on the left side, or x-axis at bottom # puts "calculate_rel_position:" # p labels # p segment_px # p value # p axis_length relative_position = 0 if !value.nil? # only if (labels[0].is_a? Numeric) && (labels[-1].is_a? Numeric) && (value.is_a? Numeric) # labels are numeric, compute relative position between first and last value range = labels[-1] - labels[0] position = value - labels[0] # compute how many segments long the offset is relative_to_segemts = position/range * (labels.size - 1) # convert from segments to relative position on the axis axis, # the number of segments (i.e. relative_to_segemts >= 1) relative_position = relative_to_segemts * segment_px / axis_length elsif labels[0].is_a? String # labels are strings, see if one of label matches with the position # and place the axis there index = labels.index(value) if !index.nil? # index would be nil if label is not found offset_px = segment_px * index relative_position = offset_px/axis_length # between 0 and 1 end end end # value.nil? return relative_position end
Override this (and call super) to change the margin to the right of the plot area. Results in @border_right being set.
By default it is 7 + width of the key if it is placed on the right
or the maximum of this value or the tilte length (if title is placed at :end)
# File lib/SVG/Graph/Graph.rb, line 517 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 if key_width.nil? @border_right else @border_right = [key_width, @border_right].min end end if (x_title_location == :end) @border_right = [@border_right, x_title.length * x_title_font_size * 0.6].max end end
Override this (and call super) to change the margin to the top of the plot area. Results in @border_top being set.
This is 5 + the Title size + 5 + subTitle size
# File lib/SVG/Graph/Graph.rb, line 541 def calculate_top_margin @border_top = 5 @border_top += [title_font_size, y_title_font_size].max if (show_graph_title || (y_title_location ==:end)) @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 668 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" }) draw_x_axis draw_y_axis draw_x_labels draw_y_labels end
Draws the legend on the graph
# File lib/SVG/Graph/Graph.rb, line 1036 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 * key_spacing) 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 + key_spacing).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 + (key_spacing * 2) y_offset = @border_top + (key_spacing * 2) when :bottom x_offset = @border_left + (key_spacing * 2) y_offset = @border_top + @graph_height + key_spacing if show_x_labels y_offset += max_x_label_height_px end y_offset += x_title_font_size + key_spacing 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 968 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 + subtitle_font_size + 5 : 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 if (x_title_location == :end) y = @graph_height + @border_top + x_title_font_size/2.0 x = @border_left + @graph_width + x_title.length * x_title_font_size * 0.6/2.0 else y = @graph_height + @border_top + x_title_font_size + max_x_label_height_px x = @border_left + @graph_width / 2 end @root.add_element("text", { "x" => x.to_s, "y" => y.to_s, "class" => "xAxisTitle", }).text = x_title.to_s end if show_y_title if (y_title_location == :end) x = y_title.length * y_title_font_size * 0.6/2.0 # positioning is not optimal but ok for now y = @border_top - y_title_font_size/2.0 else x = y_title_font_size + (y_title_text_direction==:bt ? 3 : -3) y = @border_top + @graph_height / 2 end text = @root.add_element("text", { "x" => x.to_s, "y" => y.to_s, "class" => "yAxisTitle", }) text.text = y_title.to_s # only rotate text if it is at the middle left of the y-axis # ignore the text_direction if y_title_location is set to :end if (y_title_location != :end) if y_title_text_direction == :bt text.attributes["transform"] = "rotate( -90, #{x}, #{y} )" else text.attributes["transform"] = "rotate( 90, #{x}, #{y} )" end end end end
draws the x-axis; can be overridden by child classes
# File lib/SVG/Graph/Graph.rb, line 690 def draw_x_axis # relative position on y-axis (hence @graph_height is our axis length) relative_position = calculate_rel_position(get_y_labels, field_height, @x_axis_position, @graph_height) # X-Axis y_offset = (1 - relative_position) * @graph_height @graph.add_element( "path", { "d" => "M 0 #{y_offset} h#@graph_width", "class" => "axis", "id" => "yAxis" }) end
Draws the X axis guidelines, parallel to the y-axis
# File lib/SVG/Graph/Graph.rb, line 946 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. The x-axis (@graph_width) is diveded into {#get_x_labels.length} equal sections. The (center) x-coordinate for a label hence is label_index * width_of_section
# File lib/SVG/Graph/Graph.rb, line 806 def draw_x_labels stagger = x_label_font_size + 5 label_width = field_width count = 0 x_axis_already_drawn = false 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 # only draw every n-th label as defined by step_x_labels if step == 0 && show_x_labels then textStr = label.to_s if( numeric?(label) ) textStr = @number_format % label end text = @graph.add_element( "text" ) text.attributes["class"] = "xAxisLabels" text.text = textStr 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 degrees = 90 if numeric? rotate_x_labels degrees = rotate_x_labels end text.attributes["transform"] = "rotate( #{degrees} #{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 # if step == 0 && show_x_labels draw_x_guidelines( label_width, count ) if show_x_guidelines count += 1 end # for label in get_x_labels end
draws the y-axis; can be overridden by child classes
# File lib/SVG/Graph/Graph.rb, line 703 def draw_y_axis # relative position on x-axis (hence @graph_width is our axis length) relative_position = calculate_rel_position(get_x_labels, field_width, @y_axis_position, @graph_width) # Y-Axis x_offset = relative_position * @graph_width @graph.add_element( "path", { "d" => "M #{x_offset} 0 v#@graph_height", "class" => "axis", "id" => "xAxis" }) end
Draws the Y axis guidelines, parallel to the x-axis
# File lib/SVG/Graph/Graph.rb, line 957 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, the Y-Axis (@graph_height) is divided equally into get_y_labels
.lenght sections So the y coordinate for an arbitrary value is calculated as follows:
y = @graph_height equals the min_value #normalize value of a single scale_division: count = value /(@y_scale_division) y = @graph_height - count * field_height
# File lib/SVG/Graph/Graph.rb, line 900 def draw_y_labels stagger = y_label_font_size + 5 label_height = field_height label_width = max_y_label_width_px count = 0 y_offset = @graph_height + y_label_offset( label_height ) y_offset += font_size/3.0 for label in get_y_labels if show_y_labels # x = 0, y = 0 is top left right next to graph area y = y_offset - (label_height * count) x = -label_width/2.0 + y_label_font_size/2.0 if stagger_y_labels and count % 2 == 1 x -= stagger @graph.add_element( "path", { "d" => "M0 #{y} h#{-stagger}", "class" => "staggerGuideLine" }) end text = @graph.add_element( "text", { "x" => x.to_s, "y" => y.to_s, "class" => "yAxisLabels" }) textStr = label.to_s if( numeric?(label) ) textStr = @number_format % label end text.text = textStr # note text-anchor is at bottom of textfield text.attributes["style"] = "text-anchor: middle" degrees = rotate_y_labels text.attributes["transform"] = "translate( -#{font_size} 0 ) " + "rotate( #{degrees} #{x} #{y} ) " # text.attributes["y"] = (y - (y_label_font_size/2)).to_s end # if show_y_labels draw_y_guidelines( label_height, count ) if show_y_guidelines count += 1 end # for label in get_y_labels end
space in px between the y-labels
# File lib/SVG/Graph/Graph.rb, line 886 def field_height #(@graph_height.to_f - font_size*2*top_font) / # (get_y_labels.length - top_align) @graph_height.to_f / get_y_labels.length end
space in px between x-labels
# File lib/SVG/Graph/Graph.rb, line 879 def field_width # -1 is to use entire x-axis # otherwise there is always 1 division unused @graph_width.to_f / ( get_x_labels.length - 1 ) end
returns the longest label from an array of labels as string each object in the array must support .to_s
# File lib/SVG/Graph/Graph.rb, line 625 def get_longest_label(arry) longest_label = arry.max{|a,b| # respect number_format a = @number_format % a if numeric?(a) b = @number_format % b if numeric?(b) a.to_s.length <=> b.to_s.length } longest_label = @number_format % longest_label if numeric?(longest_label) return longest_label end
override this method in child class must return the array of labels for the x-axis
# File lib/SVG/Graph/Graph.rb, line 869 def get_x_labels end
override this method in child class must return the array of labels for the y-axis this method defines @y_scale_division
# File lib/SVG/Graph/Graph.rb, line 875 def get_y_labels end
Overwrite configuration options with supplied options. Used by subclasses.
# File lib/SVG/Graph/Graph.rb, line 470 def init_with config config.each { |key, value| self.send( key.to_s+"=", value ) if self.respond_to? key } end
# File lib/SVG/Graph/Graph.rb, line 1030 def keys i = 0 return @data.collect{ |d| i+=1; d[:title] || "Serie #{i}" } end
adds the datapoint text to the graph only if the config option is set
# File lib/SVG/Graph/Graph.rb, line 771 def make_datapoint_text( x, y, value, style="" ) if show_data_values textStr = value if( numeric?(value) ) textStr = @number_format % value end # change anchor is label overlaps axis, normally anchor is middle (that's why we compute length/2) if x < textStr.length/2 * font_size style << "text-anchor: start;" elsif x > @graph_width - textStr.length/2 * font_size style << "text-anchor: end;" end # background for better readability text = @foreground.add_element( "text", { "x" => x.to_s, "y" => y.to_s, "class" => "dataPointLabelBackground", }) text.text = textStr text.attributes["style"] = style if style.length > 0 # actual label text = @foreground.add_element( "text", { "x" => x.to_s, "y" => y.to_s, "class" => "dataPointLabel" }) text.text = textStr text.attributes["style"] = style if style.length > 0 end end
returns the maximum height of the labels respect the rotation or 0 if the labels are not shown
# File lib/SVG/Graph/Graph.rb, line 654 def max_x_label_height_px return 0 if !show_x_labels if rotate_x_labels max_height = get_longest_label(get_x_labels).to_s.length * x_label_font_size * 0.6 else max_height = x_label_font_size + 3 end max_height += 5 + x_label_font_size if stagger_x_labels return max_height end
Calculates the width of the widest Y label. This will be the character height if the Y labels are rotated. Returns 0 if labels are not shown
# File lib/SVG/Graph/Graph.rb, line 492 def max_y_label_width_px return 0 if !show_y_labels base_width = y_label_font_size + 3 if rotate_y_labels == true self.rotate_y_labels = 90 end if rotate_y_labels == false self.rotate_y_labels = 0 end # don't change rotate_y_label, if neither true nor false label_width = get_longest_label(get_y_labels).to_s.length * y_label_font_size * 0.5 rotated_width = label_width * Math.cos( rotate_y_labels * Math::PI / 180).abs() max_width = base_width + rotated_width if stagger_y_labels max_width += 5 + y_label_font_size end return max_width end
check if an object can be converted to float
# File lib/SVG/Graph/Graph.rb, line 765 def numeric?(object) # true if Float(object) rescue false object.is_a? Numeric 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 760 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 863 def y_label_offset( height ) 0 end
Private Instance Methods
Override and place code to add defs here @param defs [REXML::Element]
# File lib/SVG/Graph/Graph.rb, line 1118 def add_defs defs end
# File lib/SVG/Graph/Graph.rb, line 1179 def calculate_graph_dimensions calculate_left_margin calculate_right_margin calculate_bottom_margin calculate_top_margin @graph_width = width - @border_left - @border_right @graph_height = height - @border_top - @border_bottom end
# File lib/SVG/Graph/Graph.rb, line 1188 def get_style return <<EOL /* Copy from here for external style sheet */ .svgBackground{ fill:#ffffff; } .graphBackground{ fill:#f0f0f0; } /* graphs titles */ .mainTitle{ text-anchor: middle; fill: #000000; font-size: #{title_font_size}px; font-family: "Arial", sans-serif; font-weight: normal; } .subTitle{ text-anchor: middle; fill: #999999; font-size: #{subtitle_font_size}px; font-family: "Arial", sans-serif; font-weight: normal; } .axis{ stroke: #000000; stroke-width: 1px; } .guideLines{ stroke: #666666; stroke-width: 1px; stroke-dasharray: 5 5; } .xAxisLabels{ text-anchor: middle; fill: #000000; font-size: #{x_label_font_size}px; font-family: "Arial", sans-serif; font-weight: normal; } .yAxisLabels{ text-anchor: end; fill: #000000; font-size: #{y_label_font_size}px; font-family: "Arial", sans-serif; font-weight: normal; } .xAxisTitle{ text-anchor: middle; fill: #ff0000; font-size: #{x_title_font_size}px; font-family: "Arial", sans-serif; font-weight: normal; } .yAxisTitle{ fill: #ff0000; text-anchor: middle; font-size: #{y_title_font_size}px; font-family: "Arial", sans-serif; font-weight: normal; } .dataPointLabel, .dataPointLabelBackground, .dataPointPopup, .dataPointPopupMask{ fill: #000000; text-anchor:middle; font-size: 10px; font-family: "Arial", sans-serif; font-weight: normal; } .dataPointLabelBackground{ stroke: #ffffff; stroke-width: 2; } .dataPointPopupMask{ stroke: white; stroke-width: 7; } .dataPointPopup{ fill: black; stroke-width: 2; } .staggerGuideLine{ fill: none; stroke: #000000; stroke-width: 0.5px; } #{get_css} .keyText{ fill: #000000; text-anchor:start; font-size: #{key_font_size}px; font-family: "Arial", sans-serif; font-weight: normal; } /* End copy for external style sheet */ EOL end
# File lib/SVG/Graph/Graph.rb, line 1089 def parse_css css = get_style rv = {} while css =~ /^(\.(\w+)(?:\s*,\s*\.\w+)*)\s*\{/m names = $1 css = $' css =~ /([^}]+)\}/m content = $1 css = $' nms = [] while names =~ /^\s*,?\s*\.(\w+)/ nms << $1 names = $' end content = content.tr( "\n\t", " ") for name in nms current = rv[name] current = current ? current+"; "+content : content rv[name] = current.strip.squeeze(" ") end end return rv end
Creates the XML document and adds the root svg element with the width, height and viewBox attributes already set. The element is stored as @root.
In addition a rectangle background of the same size as the svg is added.
# File lib/SVG/Graph/Graph.rb, line 1128 def start_svg # Base document @doc = Document.new @doc << XMLDecl.new @doc << DocType.new( %q{svg PUBLIC "-//W3C//DTD SVG 1.0//EN" } + %q{"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"} ) if style_sheet && style_sheet != '' && inline_style_sheet.to_s.empty? # if inline_style_sheet is defined, url style sheet is ignored @doc << Instruction.new( "xml-stylesheet", %Q{href="#{style_sheet}" type="text/css"} ) end @root = @doc.add_element( "svg", { "width" => width.to_s, "height" => height.to_s, "viewBox" => "0 0 #{width} #{height}", "xmlns" => "http://www.w3.org/2000/svg", "xmlns:xlink" => "http://www.w3.org/1999/xlink", "xmlns:a3" => "http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/", "a3:scriptImplementation" => "Adobe" }) @root << Comment.new( " "+"\\"*66 ) @root << Comment.new( " Created with SVG::Graph " ) @root << Comment.new( " SVG::Graph by Sean E. Russell " ) @root << Comment.new( " Losely based on SVG::TT::Graph for Perl by"+ " Leo Lapworth & Stephan Morgan " ) @root << Comment.new( " "+"/"*66 ) defs = @root.add_element( "defs" ) add_defs defs if !no_css if inline_style_sheet && inline_style_sheet != '' style = defs.add_element( "style", {"type"=>"text/css"} ) style << CData.new( inline_style_sheet ) else @root << Comment.new(" include default stylesheet if none specified ") style = defs.add_element( "style", {"type"=>"text/css"} ) style << CData.new( get_style ) end end @root << Comment.new( "SVG Background" ) @root.add_element( "rect", { "width" => width.to_s, "height" => height.to_s, "x" => "0", "y" => "0", "class" => "svgBackground" }) end
# File lib/SVG/Graph/Graph.rb, line 1077 def style if no_css styles = parse_css @root.elements.each("//*[@class]") { |el| cl = el.attributes["class"] style = styles[cl] style += el.attributes["style"] if el.attributes["style"] el.attributes["style"] = style } end end