To check out this repository please hg clone the following URL, or open the URL using EasyMercurial or your preferred Mercurial client.

Statistics Download as Zip
| Branch: | Tag: | Revision:

root / .svn / pristine / 74 / 74c1617ddcbb99d4a4a61efc78116f68f0818d6b.svn-base @ 1297:0a574315af3e

History | View | Annotate | Download (7.34 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