class SVG::Graph::Plot

For creating SVG plots of scalar data

Synopsis

require 'SVG/Graph/Plot'

# Data sets are x,y pairs
# Note that multiple data sets can differ in length, and that the
# data in the datasets needn't be in order; they will be ordered
# by the plot along the X-axis.
projection = [
  6, 11,    0, 5,   18, 7,   1, 11,   13, 9,   1, 2,   19, 0,   3, 13,
  7, 9 
]
actual = [
  0, 18,    8, 15,    9, 4,   18, 14,   10, 2,   11, 6,  14, 12,   
  15, 6,   4, 17,   2, 12
]

graph = SVG::Graph::Plot.new({
        :height => 500,
        :width => 300,
  :key => true,
  :scale_x_integers => true,
  :scale_y_integerrs => true,
})

graph.add_data({
        :data => projection
  :title => 'Projected',
})

graph.add_data({
        :data => actual,
  :title => 'Actual',
})

print graph.burn()

Description

Produces a graph of scalar data.

This object aims to allow you to easily create high quality SVG scalar plots. You can either use the default style sheet or supply your own. Either way there are many options which can be configured to give you control over how the graph is generated - with or without a key, data elements at each point, title, subtitle etc.

Examples

www.germane-software/repositories/public/SVG/test/plot.rb

Notes

The default stylesheet handles upto 10 data sets, if you use more you must create your own stylesheet and add the additional settings for the extra data sets. You will know if you go over 10 data sets as they will have no style and be in black.

Unlike the other types of charts, data sets must contain x,y pairs:

[ 1, 2 ]    # A data set with 1 point: (1,2)
[ 1,2, 5,6] # A data set with 2 points: (1,2) and (5,6)

See also

Author

Sean E. Russell <serATgermaneHYPHENsoftwareDOTcom>

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

Constants

X
Y

Attributes

area_fill[RW]

Fill the area under the line

max_x_value[RW]

Set the maximum value of the X axis

max_y_value[RW]

Set the maximum value of the Y axis

min_x_value[RW]

Set the minimum value of the X axis

min_y_value[RW]

Set the minimum value of the Y axis

round_popups[RW]

Round value of data points in popups to integer

scale_x_divisions[RW]

Determines the scaling for the X axis divisions.

graph.scale_x_divisions = 2

would cause the graph to attempt to generate labels stepped by 2; EG: 0,2,4,6,8…

scale_x_integers[RW]

Make the X axis labels integers

scale_y_divisions[RW]

Determines the scaling for the Y axis divisions.

graph.scale_y_divisions = 0.5

would cause the graph to attempt to generate labels stepped by 0.5; EG: 0, 0.5, 1, 1.5, 2, …

scale_y_integers[RW]

Make the Y axis labels integers

show_data_points[RW]

Show a small circle on the graph where the line goes from one point to the next.

show_lines[RW]

Show lines connecting data points

Public Instance Methods

add_data(data) click to toggle source

Adds data to the plot. The data must be in X,Y pairs; EG

[ 1, 2 ]    # A data set with 1 point: (1,2)
[ 1,2, 5,6] # A data set with 2 points: (1,2) and (5,6)
# File lib/SVG/Graph/Plot.rb, line 148
def add_data(data)
        
  @data = [] unless @data

  raise "No data provided by #{conf.inspect}" unless data[:data] and
  data[:data].kind_of? Array
  raise "Data supplied must be x,y pairs!  "+
    "The data provided contained an odd set of "+
    "data points" unless data[:data].length % 2 == 0
  return if data[:data].length == 0

  data[:description] ||= Array.new(data[:data].size/2)
  if data[:description].size != data[:data].size/2
    raise "Description for popups does not have same size as provided data: #{data[:description].size} vs #{data[:data].size/2}"
  end

  x = []
  y = []
  data[:data].each_index {|i|
    (i%2 == 0 ? x : y) << data[:data][i]
  }
  sort( x, y, data[:description] )
  data[:data] = [x,y]
  @data << data
end
set_defaults() click to toggle source

In addition to the defaults set by Graph::initialize, sets

show_data_values

true

#show_data_points

true

#area_fill

false

stacked

false

# File lib/SVG/Graph/Plot.rb, line 96
def set_defaults
  init_with(
            :show_data_values  => true,
            :show_data_points  => true,
            :area_fill         => false,
            :stacked           => false,
            :show_lines        => true,
            :round_popups      => true
           )
           self.top_align = self.right_align = self.top_font = self.right_font = 1
end

Protected Instance Methods

calculate_left_margin() click to toggle source
Calls superclass method SVG::Graph::Graph#calculate_left_margin
# File lib/SVG/Graph/Plot.rb, line 180
def calculate_left_margin
  super
  label_left = get_x_labels[0].to_s.length / 2 * font_size * 0.6
  @border_left = label_left if label_left > @border_left
end
calculate_right_margin() click to toggle source
# File lib/SVG/Graph/Plot.rb, line 186
def calculate_right_margin
  super
  label_right = get_x_labels[-1].to_s.length / 2 * font_size * 0.6
  @border_right = label_right if label_right > @border_right
end
draw_data() click to toggle source
# File lib/SVG/Graph/Plot.rb, line 297
def draw_data
  line = 1
  
  x_min, x_max = x_range
  y_min, y_max = y_range
  x_step = (@graph_width.to_f - font_size*2) / (x_max-x_min)
  y_step = (@graph_height.to_f -  font_size*2) / (y_max-y_min)

  for data in @data
    x_points = data[:data][X]
    y_points = data[:data][Y]

    lpath = "L"
    x_start = 0
    y_start = 0
    x_points.each_index { |idx|
      x = (x_points[idx] -  x_min) * x_step
      y = @graph_height - (y_points[idx] -  y_min) * y_step
      x_start, y_start = x,y if idx == 0
      lpath << "#{x} #{y} "
    }

    if area_fill
      @graph.add_element( "path", {
        "d" => "M#{x_start} #@graph_height #{lpath} V#@graph_height Z",
        "class" => "fill#{line}"
      })
    end

    if show_lines
      @graph.add_element( "path", {
        "d" => "M#{x_start} #{y_start} #{lpath}",
        "class" => "line#{line}"
      })
    end

    if show_data_points || show_data_values
      x_points.each_index { |idx|
        x = (x_points[idx] -  x_min) * x_step
        y = @graph_height - (y_points[idx] -  y_min) * y_step
        if show_data_points
          DataPoint.new(x, y, line).shape(data[:description][idx]).each{|s|
            @graph.add_element( *s )
          }
          add_popup(x, y, format( x_points[idx], y_points[idx], data[:description][idx])) if add_popups
        end
        make_datapoint_text( x, y-6, y_points[idx] ) if show_data_values
      }
    end
    line += 1
  end
end
field_height() click to toggle source
# File lib/SVG/Graph/Plot.rb, line 285
def field_height
  values = get_y_values
  max = max_y_range
  if values.length == 1
    dx = values[-1]
  else
    dx = (max - values[-1]).to_f / (values[-1] - values[-2])
  end
  (@graph_height.to_f - font_size*2*top_font) /
    (values.length + dx - top_align)
end
field_width() click to toggle source
# File lib/SVG/Graph/Plot.rb, line 233
def field_width
  values = get_x_values
  max = max_x_range
  dx = (max - values[-1]).to_f / (values[-1] - values[-2])
  (@graph_width.to_f - font_size*2*right_font) /
    (values.length + dx - right_align)
end
format(x, y, desc) click to toggle source
# File lib/SVG/Graph/Plot.rb, line 350
def format x, y, desc
  info = []
  info << (round_popups ? (x * 100).to_i / 100 : x)
  info << (round_popups ? (y * 100).to_i / 100 : y)
  info << desc
  "(#{info.compact.join(', ')})"
end
get_css() click to toggle source
# File lib/SVG/Graph/Plot.rb, line 358
      def get_css
        return <<EOL
/* default line styles */
.line1{
        fill: none;
        stroke: #ff0000;
        stroke-width: 1px;     
}
.line2{
        fill: none;
        stroke: #0000ff;
        stroke-width: 1px;     
}
.line3{
        fill: none;
        stroke: #00ff00;
        stroke-width: 1px;     
}
.line4{
        fill: none;
        stroke: #ffcc00;
        stroke-width: 1px;     
}
.line5{
        fill: none;
        stroke: #00ccff;
        stroke-width: 1px;     
}
.line6{
        fill: none;
        stroke: #ff00ff;
        stroke-width: 1px;     
}
.line7{
        fill: none;
        stroke: #00ffff;
        stroke-width: 1px;     
}
.line8{
        fill: none;
        stroke: #ffff00;
        stroke-width: 1px;     
}
.line9{
        fill: none;
        stroke: #ccc6666;
        stroke-width: 1px;     
}
.line10{
        fill: none;
        stroke: #663399;
        stroke-width: 1px;     
}
.line11{
        fill: none;
        stroke: #339900;
        stroke-width: 1px;     
}
.line12{
        fill: none;
        stroke: #9966FF;
        stroke-width: 1px;     
}
/* default fill styles */
.fill1{
        fill: #cc0000;
        fill-opacity: 0.2;
        stroke: none;
}
.fill2{
        fill: #0000cc;
        fill-opacity: 0.2;
        stroke: none;
}
.fill3{
        fill: #00cc00;
        fill-opacity: 0.2;
        stroke: none;
}
.fill4{
        fill: #ffcc00;
        fill-opacity: 0.2;
        stroke: none;
}
.fill5{
        fill: #00ccff;
        fill-opacity: 0.2;
        stroke: none;
}
.fill6{
        fill: #ff00ff;
        fill-opacity: 0.2;
        stroke: none;
}
.fill7{
        fill: #00ffff;
        fill-opacity: 0.2;
        stroke: none;
}
.fill8{
        fill: #ffff00;
        fill-opacity: 0.2;
        stroke: none;
}
.fill9{
        fill: #cc6666;
        fill-opacity: 0.2;
        stroke: none;
}
.fill10{
        fill: #663399;
        fill-opacity: 0.2;
        stroke: none;
}
.fill11{
        fill: #339900;
        fill-opacity: 0.2;
        stroke: none;
}
.fill12{
        fill: #9966FF;
        fill-opacity: 0.2;
        stroke: none;
}
/* default line styles */
.key1,.dataPoint1{
        fill: #ff0000;
        stroke: none;
        stroke-width: 1px;     
}
.key2,.dataPoint2{
        fill: #0000ff;
        stroke: none;
        stroke-width: 1px;     
}
.key3,.dataPoint3{
        fill: #00ff00;
        stroke: none;
        stroke-width: 1px;     
}
.key4,.dataPoint4{
        fill: #ffcc00;
        stroke: none;
        stroke-width: 1px;     
}
.key5,.dataPoint5{
        fill: #00ccff;
        stroke: none;
        stroke-width: 1px;     
}
.key6,.dataPoint6{
        fill: #ff00ff;
        stroke: none;
        stroke-width: 1px;     
}
.key7,.dataPoint7{
        fill: #00ffff;
        stroke: none;
        stroke-width: 1px;     
}
.key8,.dataPoint8{
        fill: #ffff00;
        stroke: none;
        stroke-width: 1px;     
}
.key9,.dataPoint9{
        fill: #cc6666;
        stroke: none;
        stroke-width: 1px;     
}
.key10,.dataPoint10{
        fill: #663399;
        stroke: none;
        stroke-width: 1px;     
}
.key11,.dataPoint11{
        fill: #339900;
        stroke: none;
        stroke-width: 1px;     
}
.key12,.dataPoint12{
        fill: #9966FF;
        stroke: none;
        stroke-width: 1px;     
}
EOL
      end
get_x_labels()
Alias for: get_x_values
get_x_values() click to toggle source
# File lib/SVG/Graph/Plot.rb, line 225
def get_x_values
  min_value, max_value, scale_division = x_range
  rv = []
  min_value.step( max_value, scale_division ) {|v| rv << v}
  return rv
end
Also aliased as: get_x_labels
get_y_labels()
Alias for: get_y_values
get_y_values() click to toggle source
# File lib/SVG/Graph/Plot.rb, line 271
def get_y_values
  min_value, max_value, scale_division = y_range
  if max_value != min_value
    while (max_value - min_value) < scale_division
      scale_division /= 10.0
    end
  end
  rv = []
  min_value.step( max_value, scale_division ) {|v| rv << v}
  rv << rv[0] + 1 if rv.length == 1
  return rv
end
Also aliased as: get_y_labels
keys() click to toggle source
# File lib/SVG/Graph/Plot.rb, line 176
def keys
  @data.collect{ |x| x[:title] }
end
max_x_range() click to toggle source
# File lib/SVG/Graph/Plot.rb, line 196
def max_x_range
  max_value = @data.collect{|x| x[:data][X][-1] }.max
  max_value = max_value > max_x_value ? max_value : max_x_value if max_x_value
  max_value
end
max_y_range() click to toggle source
# File lib/SVG/Graph/Plot.rb, line 242
def max_y_range
  max_value = @data.collect{|x| x[:data][Y].max }.max
  max_value = max_value > max_y_value ? max_value : max_y_value if max_y_value
  max_value
end
min_x_range() click to toggle source
# File lib/SVG/Graph/Plot.rb, line 202
def min_x_range
  min_value = @data.collect{|x| x[:data][X][0] }.min
  min_value = min_value < min_x_value ? min_value : min_x_value if min_x_value
  min_value
end
min_y_range() click to toggle source
# File lib/SVG/Graph/Plot.rb, line 248
def min_y_range
  min_value = @data.collect{|x| x[:data][Y].min }.min
  min_value = min_value < min_y_value ? min_value : min_y_value if min_y_value
  min_value
end
x_range() click to toggle source
# File lib/SVG/Graph/Plot.rb, line 208
def x_range
  max_value = max_x_range
  min_value = min_x_range

  range = max_value - min_value
  right_pad = range == 0 ? 10 : range / 20.0
  scale_range = (max_value + right_pad) - min_value

  scale_division = scale_x_divisions || (scale_range / 10.0)

  if scale_x_integers
    scale_division = scale_division < 1 ? 1 : scale_division.round
  end

  [min_value, max_value, scale_division]
end
y_range() click to toggle source
# File lib/SVG/Graph/Plot.rb, line 254
def y_range
  max_value = max_y_range
  min_value = min_y_range

  range = max_value - min_value
  top_pad = range == 0 ? 10 : range / 20.0
  scale_range = (max_value + top_pad) - min_value

  scale_division = scale_y_divisions || (scale_range / 10.0)

  if scale_y_integers
    scale_division = scale_division < 1 ? 1 : scale_division.round
  end

  return [min_value, max_value, scale_division]
end