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

Attributes

add_popups[RW]

Add popups for the data points on some graphs, default is false.

font_size[RW]

Set the font size (in points) of the data point labels. Defaults to 12.

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.

inline_style_sheet[RW]

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: ''

key[RW]

Whether to show a key (legend), defaults to true, set to false if you want to hide it.

key_box_size[RW]
key_font_size[RW]

Set the key font size. Defaults to 10.

key_position[RW]

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

key_spacing[RW]
key_width[RW]
min_scale_value[RW]

The point at which the Y axis starts, defaults to nil, 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.

number_format[RW]

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”

popup_radius[RW]

Customize popup radius

rotate_x_labels[RW]

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.

rotate_y_labels[RW]

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.

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 is 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, default is false

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, default is true

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 is 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 is 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/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.

subtitle_font_size[RW]

Set the subtitle font size. Defaults to 14.

title_font_size[RW]

Set the title font size. Defaults to 16.

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_axis_position[RW]

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

x_label_font_size[RW]

Set the font size of the X axis labels. Defaults to 12.

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. Defaults to 14.

x_title_location[RW]

Where the x_title should be positioned, either in the :middle of the axis or at the :end of the axis. Defaults to :middle

y_axis_position[RW]

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

y_label_font_size[RW]

Set the font size of the Y axis labels. Defaults to 12.

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. Defaults to 14.

y_title_location[RW]

Where the y_title should be positioned, either in the :middle of the axis or at the :end of the axis. Defaults to :middle

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

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

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

@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
add_data_init_or_check_optional_keys(conf, datasize) click to toggle source

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
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, 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
burn_svg_only() click to toggle source

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
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, line 221
def clear_data
  @data = []
end
to_iruby() click to toggle source

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

add_datapoint_text_and_popup( x, y, label ) click to toggle source
# 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
add_popup( x, y, label, style="", url="" ) click to toggle source

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
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.

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
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.

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
calculate_rel_position(labels, segment_px, value, axis_length) click to toggle source

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
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.

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
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.

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
draw_graph() click to toggle source

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
draw_legend() click to toggle source

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
draw_titles() click to toggle source

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
draw_x_axis() click to toggle source

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
draw_x_guidelines( label_height, count ) click to toggle source

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
draw_x_labels() click to toggle source

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
draw_y_axis() click to toggle source

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
draw_y_guidelines( label_height, count ) click to toggle source

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
draw_y_labels() click to toggle source

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
field_height() click to toggle source

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
field_width() click to toggle source

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
get_longest_label(arry) click to toggle source

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
get_x_labels() click to toggle source

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
get_y_labels() click to toggle source

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
init_with(config) click to toggle source

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
keys() click to toggle source
# File lib/SVG/Graph/Graph.rb, line 1030
def keys
  i = 0
  return @data.collect{ |d| i+=1; d[:title] || "Serie #{i}" }
end
make_datapoint_text( x, y, value, style="" ) click to toggle source

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
max_x_label_height_px() click to toggle source

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
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. 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
numeric?(object) click to toggle source

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
sort( *arrys ) click to toggle source

implementation of a multiple array sort used for Schedule and Plot

# File lib/SVG/Graph/Graph.rb, line 463
def sort( *arrys )
  new_arrys = arrys.transpose.sort_by(&:first).transpose
  new_arrys.each_index { |k| arrys[k].replace(new_arrys[k]) }
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, line 760
def x_label_offset( width )
  0
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, line 863
def y_label_offset( height )
  0
end

Private Instance Methods

add_defs(defs) click to toggle source

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
calculate_graph_dimensions() click to toggle source
# 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
get_style() click to toggle source
# 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
parse_css() click to toggle source
# 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
start_svg() click to toggle source

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
style() click to toggle source
# 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