File: lib/SVG/Graph/TimeSeries.rb

Overview
Module Structure
Class Hierarchy
Code

Overview

Module Structure

  module: <Toplevel Module>
  module: SVG#4
  module: Graph#5
  class: TimeSeries#98
inherits from
  Plot ( SVG::Graph )
has properties
method: set_defaults #103
attribute: x_label_format [RW] #114
attribute: timescale_divisions [RW] #125
attribute: popup_format [RW] #127
method: add_data #145
method: min_x_value= / 1 #175
method: format #181
method: get_x_labels #185
method: get_x_values #190

Class Hierarchy

Object ( Builtin-Module )
Graph ( SVG::Graph )
Plot ( SVG::Graph )
  TimeSeries    #98

Code

   1  require 'SVG/Graph/Plot'
   2  require 'parsedate'
   3 
   4  module SVG
   5    module Graph
   6      # === For creating SVG plots of scalar temporal data
   7      # 
   8      # = Synopsis
   9      # 
  10      #   require 'SVG/Graph/TimeSeriess'
  11      # 
  12      #   # Data sets are x,y pairs
  13      #   data1 = ["6/17/72", 11,    "1/11/72", 7,    "4/13/04 17:31", 11, 
  14      #           "9/11/01", 9,    "9/1/85", 2,    "9/1/88", 1,    "1/15/95", 13]
  15      #   data2 = ["8/1/73", 18,    "3/1/77", 15,    "10/1/98", 4, 
  16      #           "5/1/02", 14,    "3/1/95", 6,    "8/1/91", 12,    "12/1/87", 6, 
  17      #           "5/1/84", 17,    "10/1/80", 12]
  18      #
  19      #   graph = SVG::Graph::TimeSeries.new( {
  20      #     :width => 640,
  21      #     :height => 480,
  22      #     :graph_title => title,
  23      #     :show_graph_title => true,
  24      #     :no_css => true,
  25      #     :key => true,
  26      #     :scale_x_integers => true,
  27      #     :scale_y_integers => true,
  28      #     :min_x_value => 0,
  29      #     :min_y_value => 0,
  30      #     :show_data_labels => true,
  31      #     :show_x_guidelines => true,
  32      #     :show_x_title => true,
  33      #     :x_title => "Time",
  34      #     :show_y_title => true,
  35      #     :y_title => "Ice Cream Cones",
  36      #     :y_title_text_direction => :bt,
  37      #     :stagger_x_labels => true,
  38      #     :x_label_format => "%m/%d/%y",
  39      #   })
  40      #   
  41      #   graph.add_data({
  42      #           :data => projection
  43      #     :title => 'Projected',
  44      #   })
  45      # 
  46      #   graph.add_data({
  47      #           :data => actual,
  48      #     :title => 'Actual',
  49      #   })
  50      #   
  51      #   print graph.burn()
  52      #
  53      # = Description
  54      # 
  55      # Produces a graph of temporal scalar data.
  56      # 
  57      # = Examples
  58      #
  59      # http://www.germane-software/repositories/public/SVG/test/timeseries.rb
  60      # 
  61      # = Notes
  62      # 
  63      # The default stylesheet handles upto 10 data sets, if you
  64      # use more you must create your own stylesheet and add the
  65      # additional settings for the extra data sets. You will know
  66      # if you go over 10 data sets as they will have no style and
  67      # be in black.
  68      #
  69      # Unlike the other types of charts, data sets must contain x,y pairs:
  70      #
  71      #   [ "12:30", 2 ]          # A data set with 1 point: ("12:30",2)
  72      #   [ "01:00",2, "14:20",6] # A data set with 2 points: ("01:00",2) and 
  73      #                           #                           ("14:20",6)  
  74      #
  75      # Note that multiple data sets within the same chart can differ in length, 
  76      # and that the data in the datasets needn't be in order; they will be ordered
  77      # by the plot along the X-axis.
  78      # 
  79      # The dates must be parseable by ParseDate, but otherwise can be
  80      # any order of magnitude (seconds within the hour, or years)
  81      # 
  82      # = See also
  83      # 
  84      # * SVG::Graph::Graph
  85      # * SVG::Graph::BarHorizontal
  86      # * SVG::Graph::Bar
  87      # * SVG::Graph::Line
  88      # * SVG::Graph::Pie
  89      # * SVG::Graph::Plot
  90      #
  91      # == Author
  92      #
  93      # Sean E. Russell <serATgermaneHYPHENsoftwareDOTcom>
  94      #
  95      # Copyright 2004 Sean E. Russell
  96      # This software is available under the Ruby license[LICENSE.txt]
  97      #
  98      class TimeSeries < Plot
  99        # In addition to the defaults set by Graph::initialize and
 100        # Plot::set_defaults, sets:
 101        # [x_label_format] '%Y-%m-%d %H:%M:%S'
 102        # [popup_format]  '%Y-%m-%d %H:%M:%S'
 103        def set_defaults
 104          super
 105          init_with(
 106            #:max_time_span     => '',
 107            :x_label_format     => '%Y-%m-%d %H:%M:%S',
 108            :popup_format       => '%Y-%m-%d %H:%M:%S'
 109          )
 110        end
 111 
 112        # The format string use do format the X axis labels.
 113        # See Time::strformat
 114        attr_accessor :x_label_format
 115        # Use this to set the spacing between dates on the axis.  The value
 116        # must be of the form 
 117        # "\d+ ?(days|weeks|months|years|hours|minutes|seconds)?"
 118        # 
 119        # EG:
 120        #
 121        #   graph.timescale_divisions = "2 weeks"
 122        #
 123        # will cause the chart to try to divide the X axis up into segments of
 124        # two week periods.
 125        attr_accessor :timescale_divisions
 126        # The formatting used for the popups.  See x_label_format
 127        attr_accessor :popup_format
 128 
 129        # Add data to the plot.
 130        #
 131        #   d1 = [ "12:30", 2 ]          # A data set with 1 point: ("12:30",2)
 132        #   d2 = [ "01:00",2, "14:20",6] # A data set with 2 points: ("01:00",2) and 
 133        #                                #                           ("14:20",6)  
 134        #   graph.add_data( 
 135        #     :data => d1,
 136        #     :title => 'One'
 137        #   )
 138        #   graph.add_data(
 139        #     :data => d2,
 140        #     :title => 'Two'
 141        #   )
 142        #
 143        # Note that the data must be in time,value pairs, and that the date format
 144        # may be any date that is parseable by ParseDate.
 145        def add_data data
 146          @data = [] unless @data
 147         
 148          raise "No data provided by #{@data.inspect}" unless data[:data] and
 149                                                      data[:data].kind_of? Array
 150          raise "Data supplied must be x,y pairs!  "+
 151            "The data provided contained an odd set of "+
 152            "data points" unless data[:data].length % 2 == 0
 153          return if data[:data].length == 0
 154 
 155 
 156          x = []
 157          y = []
 158          data[:data].each_index {|i|
 159            if i%2 == 0
 160              arr = ParseDate.parsedate( data[:data][i] )
 161              t = Time.local( *arr[0,6].compact )
 162              x << t.to_i
 163            else
 164              y << data[:data][i]
 165            end
 166          }
 167          sort( x, y )
 168          data[:data] = [x,y]
 169          @data << data
 170        end
 171 
 172 
 173        protected
 174 
 175        def min_x_value=(value)
 176          arr = ParseDate.parsedate( value )
 177          @min_x_value = Time.local( *arr[0,6].compact ).to_i
 178        end
 179 
 180 
 181        def format x, y
 182          Time.at( x ).strftime( popup_format )
 183        end
 184 
 185        def get_x_labels
 186          get_x_values.collect { |v| Time.at(v).strftime( x_label_format ) }
 187        end
 188        
 189        private
 190        def get_x_values
 191          rv = []
 192          min, max, scale_division = x_range
 193          if timescale_divisions
 194            timescale_divisions =~ /(\d+) ?(day|week|month|year|hour|minute|second)?/
 195            division_units = $2 ? $2 : "day"
 196            amount = $1.to_i
 197            if amount
 198              step =  nil
 199              case division_units
 200              when "month"
 201                cur = min
 202                while cur < max
 203                  rv << cur
 204                  arr = Time.at( cur ).to_a
 205                  arr[4] += amount
 206                  if arr[4] > 12
 207                    arr[5] += (arr[4] / 12).to_i
 208                    arr[4] = (arr[4] % 12)
 209                  end
 210                  cur = Time.local(*arr).to_i
 211                end
 212              when "year"
 213                cur = min
 214                while cur < max
 215                  rv << cur
 216                  arr = Time.at( cur ).to_a
 217                  arr[5] += amount
 218                  cur = Time.local(*arr).to_i
 219                end
 220              when "week"
 221                step = 7 * 24 * 60 * 60 * amount
 222              when "day"
 223                step = 24 * 60 * 60 * amount
 224              when "hour"
 225                step = 60 * 60 * amount
 226              when "minute"
 227                step = 60 * amount
 228              when "second"
 229                step = amount
 230              end
 231              min.step( max, step ) {|v| rv << v} if step
 232 
 233              return rv
 234            end
 235          end
 236          min.step( max, scale_division ) {|v| rv << v}
 237          return rv
 238        end
 239      end
 240    end
 241  end