annotate lib/SVG/Graph/TimeSeries.rb @ 8:0c83d98252d9 yuya

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