Mercurial > hg > soundsoftware-site
comparison lib/SVG/Graph/.svn/text-base/TimeSeries.rb.svn-base @ 0:513646585e45
* Import Redmine trunk SVN rev 3859
author | Chris Cannam |
---|---|
date | Fri, 23 Jul 2010 15:52:44 +0100 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:513646585e45 |
---|---|
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 |