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