svg-graph 1.0.1 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. data.tar.gz.sig +0 -0
  2. data/History.txt +7 -5
  3. data/Manifest.txt +53 -17
  4. data/README.markdown +14 -0
  5. data/README.txt +10 -3
  6. data/Rakefile +6 -0
  7. data/build.xml +143 -0
  8. data/dist.xml.in +46 -0
  9. data/images/bar.png +0 -0
  10. data/images/bar.svg +76 -0
  11. data/images/bar.svgz +0 -0
  12. data/images/barhorizontal.png +0 -0
  13. data/images/barhorizontal.svg +76 -0
  14. data/images/barhorizontal.svgz +0 -0
  15. data/images/line.png +0 -0
  16. data/images/line.svg +80 -0
  17. data/images/line.svgz +0 -0
  18. data/images/pie.png +0 -0
  19. data/images/pie.svg +70 -0
  20. data/images/pie.svgz +0 -0
  21. data/images/plot.png +0 -0
  22. data/images/plot.svg +131 -0
  23. data/images/plot.svgz +0 -0
  24. data/images/schedule.png +0 -0
  25. data/images/schedule.svg +67 -0
  26. data/images/timeseries.png +0 -0
  27. data/images/timeseries.svg +179 -0
  28. data/images/timeseries.svgz +0 -0
  29. data/index.xml +281 -0
  30. data/install.rb +161 -0
  31. data/lib/SVG/Graph/Bar.rb +1 -1
  32. data/lib/SVG/Graph/Graph.rb +11 -9
  33. data/lib/SVG/Graph/Line.rb +1 -0
  34. data/lib/SVG/Graph/Pie.rb +3 -3
  35. data/lib/SVG/Graph/Plot.rb +69 -20
  36. data/lib/SVG/Graph/Schedule.rb +19 -5
  37. data/lib/SVG/Graph/TimeSeries.rb +62 -45
  38. data/lib/svggraph.rb +1 -2
  39. data/screenshots.xml +148 -0
  40. data/style/common.xsl +37 -0
  41. data/style/release_html.xsl +169 -0
  42. data/style/release_txt.xsl +25 -0
  43. data/svg-graph.gemspec +59 -0
  44. data/test/data.txt +4 -0
  45. data/test/plot.rb +51 -0
  46. data/test/schedule.rb +43 -0
  47. data/test/single.rb +33 -0
  48. data/test/test.rb +54 -0
  49. data/test/test_plot.rb +170 -0
  50. data/test/test_svg_graph.rb +24 -55
  51. data/test/timeseries.rb +58 -0
  52. metadata +79 -14
  53. metadata.gz.sig +2 -1
@@ -140,6 +140,7 @@ module SVG
140
140
  :title_font_size =>16,
141
141
  :subtitle_font_size =>14,
142
142
  :x_label_font_size =>12,
143
+ :y_label_font_size =>12,
143
144
  :x_title_font_size =>14,
144
145
  :y_label_font_size =>12,
145
146
  :y_title_font_size =>14,
@@ -365,14 +366,14 @@ module SVG
365
366
  def calculate_left_margin
366
367
  @border_left = 7
367
368
  # Check for Y labels
368
- max_y_label_height_px = rotate_y_labels ?
369
- y_label_font_size :
369
+ max_y_label_height_px = @rotate_y_labels ?
370
+ @y_label_font_size :
370
371
  get_y_labels.max{|a,b|
371
372
  a.to_s.length<=>b.to_s.length
372
- }.to_s.length * y_label_font_size * 0.6
373
- @border_left += max_y_label_height_px if show_y_labels
374
- @border_left += max_y_label_height_px + 10 if stagger_y_labels
375
- @border_left += y_title_font_size + 5 if show_y_title
373
+ }.to_s.length * @y_label_font_size * 0.6
374
+ @border_left += max_y_label_height_px if @show_y_labels
375
+ @border_left += max_y_label_height_px + 10 if @stagger_y_labels
376
+ @border_left += y_title_font_size + 5 if @show_y_title
376
377
  end
377
378
 
378
379
 
@@ -533,7 +534,7 @@ module SVG
533
534
 
534
535
  x = count * label_width + x_label_offset( label_width )
535
536
  y = @graph_height + x_label_font_size + 3
536
- t = 0 - (font_size / 2)
537
+ #t = 0 - (font_size / 2)
537
538
 
538
539
  if stagger_x_labels and count % 2 == 1
539
540
  y += stagger
@@ -700,7 +701,8 @@ module SVG
700
701
  end
701
702
 
702
703
  def keys
703
- return @data.collect{ |d| d[:title] }
704
+ i = 0
705
+ return @data.collect{ |d| i+=1; d[:title] || "Serie #{i}" }
704
706
  end
705
707
 
706
708
  # Draws the legend on the graph
@@ -792,7 +794,7 @@ module SVG
792
794
  css = get_style
793
795
  rv = {}
794
796
  while css =~ /^(\.(\w+)(?:\s*,\s*\.\w+)*)\s*\{/m
795
- names_orig = names = $1
797
+ names = $1
796
798
  css = $'
797
799
  css =~ /([^}]+)\}/m
798
800
  content = $1
@@ -102,6 +102,7 @@ module SVG
102
102
  :area_fill => false
103
103
  )
104
104
 
105
+
105
106
  self.top_align = self.top_font = self.right_align = self.right_font = 1
106
107
  end
107
108
 
data/lib/SVG/Graph/Pie.rb CHANGED
@@ -170,11 +170,11 @@ module SVG
170
170
 
171
171
  def keys
172
172
  total = 0
173
- max_value = 0
173
+ #max_value = 0
174
174
  @data.each {|x| total += x }
175
175
  percent_scale = 100.0 / total
176
176
  count = -1
177
- a = @config[:fields].collect{ |x|
177
+ @config[:fields].collect{ |x|
178
178
  count += 1
179
179
  v = @data[count]
180
180
  perc = show_key_percent ? " "+(v * percent_scale).round.to_s+"%" : ""
@@ -275,7 +275,7 @@ module SVG
275
275
  msr = Math.sin(radians)
276
276
  mcr = Math.cos(radians)
277
277
  tx = radius + (msr * radius)
278
- ty = radius -(mcr * radius)
278
+ ty = radius - (mcr * radius)
279
279
 
280
280
  if expanded or (expand_greatest && value == max_value)
281
281
  tx += (msr * expand_gap)
@@ -97,7 +97,9 @@ module SVG
97
97
  :show_data_values => true,
98
98
  :show_data_points => true,
99
99
  :area_fill => false,
100
- :stacked => false
100
+ :stacked => false,
101
+ :show_lines => true,
102
+ :round_popups => true
101
103
  )
102
104
  self.top_align = self.right_align = self.top_font = self.right_font = 1
103
105
  end
@@ -127,14 +129,22 @@ module SVG
127
129
  attr_accessor :show_data_points
128
130
  # Set the minimum value of the X axis
129
131
  attr_accessor :min_x_value
132
+ # Set the maximum value of the X axis
133
+ attr_accessor :max_x_value
130
134
  # Set the minimum value of the Y axis
131
135
  attr_accessor :min_y_value
136
+ # Set the maximum value of the Y axis
137
+ attr_accessor :max_y_value
138
+ # Show lines connecting data points
139
+ attr_accessor :show_lines
140
+ # Round value of data points in popups to integer
141
+ attr_accessor :round_popups
132
142
 
133
143
 
134
144
  # Adds data to the plot. The data must be in X,Y pairs; EG
135
145
  # [ 1, 2 ] # A data set with 1 point: (1,2)
136
146
  # [ 1,2, 5,6] # A data set with 2 points: (1,2) and (5,6)
137
- def add_data data
147
+ def add_data(data)
138
148
 
139
149
  @data = [] unless @data
140
150
 
@@ -145,17 +155,21 @@ module SVG
145
155
  "data points" unless data[:data].length % 2 == 0
146
156
  return if data[:data].length == 0
147
157
 
158
+ data[:description] ||= Array.new(data[:data].size/2)
159
+ if data[:description].size != data[:data].size/2
160
+ raise "Description for popups does not have same size as provided data: #{data[:description].size} vs #{data[:data].size/2}"
161
+ end
162
+
148
163
  x = []
149
164
  y = []
150
165
  data[:data].each_index {|i|
151
166
  (i%2 == 0 ? x : y) << data[:data][i]
152
167
  }
153
- sort( x, y )
168
+ sort( x, y, data[:description] )
154
169
  data[:data] = [x,y]
155
170
  @data << data
156
171
  end
157
172
 
158
-
159
173
  protected
160
174
 
161
175
  def keys
@@ -177,10 +191,22 @@ module SVG
177
191
 
178
192
  X = 0
179
193
  Y = 1
180
- def x_range
194
+
195
+ def max_x_range
181
196
  max_value = @data.collect{|x| x[:data][X][-1] }.max
197
+ max_value = max_value > max_x_value ? max_value : max_x_value if max_x_value
198
+ max_value
199
+ end
200
+
201
+ def min_x_range
182
202
  min_value = @data.collect{|x| x[:data][X][0] }.min
183
- min_value = min_value<min_x_value ? min_value : min_x_value if min_x_value
203
+ min_value = min_value < min_x_value ? min_value : min_x_value if min_x_value
204
+ min_value
205
+ end
206
+
207
+ def x_range
208
+ max_value = max_x_range
209
+ min_value = min_x_range
184
210
 
185
211
  range = max_value - min_value
186
212
  right_pad = range == 0 ? 10 : range / 20.0
@@ -205,17 +231,28 @@ module SVG
205
231
 
206
232
  def field_width
207
233
  values = get_x_values
208
- max = @data.collect{|x| x[:data][X][-1]}.max
234
+ max = max_x_range
209
235
  dx = (max - values[-1]).to_f / (values[-1] - values[-2])
210
236
  (@graph_width.to_f - font_size*2*right_font) /
211
237
  (values.length + dx - right_align)
212
238
  end
213
239
 
214
240
 
215
- def y_range
241
+ def max_y_range
216
242
  max_value = @data.collect{|x| x[:data][Y].max }.max
243
+ max_value = max_value > max_y_value ? max_value : max_y_value if max_y_value
244
+ max_value
245
+ end
246
+
247
+ def min_y_range
217
248
  min_value = @data.collect{|x| x[:data][Y].min }.min
218
- min_value = min_value<min_y_value ? min_value : min_y_value if min_y_value
249
+ min_value = min_value < min_y_value ? min_value : min_y_value if min_y_value
250
+ min_value
251
+ end
252
+
253
+ def y_range
254
+ max_value = max_y_range
255
+ min_value = min_y_range
219
256
 
220
257
  range = max_value - min_value
221
258
  top_pad = range == 0 ? 10 : range / 20.0
@@ -232,15 +269,21 @@ module SVG
232
269
 
233
270
  def get_y_values
234
271
  min_value, max_value, scale_division = y_range
272
+ if max_value != min_value
273
+ while (max_value - min_value) < scale_division
274
+ scale_division /= 10.0
275
+ end
276
+ end
235
277
  rv = []
236
278
  min_value.step( max_value, scale_division ) {|v| rv << v}
279
+ rv << rv[0] + 1 if rv.length == 1
237
280
  return rv
238
281
  end
239
282
  alias :get_y_labels :get_y_values
240
283
 
241
284
  def field_height
242
285
  values = get_y_values
243
- max = @data.collect{|x| x[:data][Y].max }.max
286
+ max = max_y_range
244
287
  if values.length == 1
245
288
  dx = values[-1]
246
289
  else
@@ -252,9 +295,9 @@ module SVG
252
295
 
253
296
  def draw_data
254
297
  line = 1
255
-
256
- x_min, x_max, x_div = x_range
257
- y_min, y_max, y_div = y_range
298
+
299
+ x_min, x_max = x_range
300
+ y_min, y_max = y_range
258
301
  x_step = (@graph_width.to_f - font_size*2) / (x_max-x_min)
259
302
  y_step = (@graph_height.to_f - font_size*2) / (y_max-y_min)
260
303
 
@@ -279,10 +322,12 @@ module SVG
279
322
  })
280
323
  end
281
324
 
282
- @graph.add_element( "path", {
283
- "d" => "M#{x_start} #{y_start} #{lpath}",
284
- "class" => "line#{line}"
285
- })
325
+ if show_lines
326
+ @graph.add_element( "path", {
327
+ "d" => "M#{x_start} #{y_start} #{lpath}",
328
+ "class" => "line#{line}"
329
+ })
330
+ end
286
331
 
287
332
  if show_data_points || show_data_values
288
333
  x_points.each_index { |idx|
@@ -295,7 +340,7 @@ module SVG
295
340
  "r" => "2.5",
296
341
  "class" => "dataPoint#{line}"
297
342
  })
298
- add_popup(x, y, format( x_points[idx], y_points[idx] )) if add_popups
343
+ add_popup(x, y, format( x_points[idx], y_points[idx], data[:description][idx])) if add_popups
299
344
  end
300
345
  make_datapoint_text( x, y-6, y_points[idx] ) if show_data_values
301
346
  }
@@ -304,8 +349,12 @@ module SVG
304
349
  end
305
350
  end
306
351
 
307
- def format x, y
308
- "(#{(x * 100).to_i / 100}, #{(y * 100).to_i / 100})"
352
+ def format x, y, desc
353
+ info = []
354
+ info << (round_popups ? (x * 100).to_i / 100 : x)
355
+ info << (round_popups ? (y * 100).to_i / 100 : y)
356
+ info << desc
357
+ "(#{info.compact.join(', ')})"
309
358
  end
310
359
 
311
360
  def get_css
@@ -1,5 +1,8 @@
1
1
  require 'SVG/Graph/Plot'
2
- require 'parsedate'
2
+ TIME_PARSE_AVAIL = (RUBY_VERSION =~ /1\.9\./) ? true : false
3
+ if not TIME_PARSE_AVAIL then
4
+ require 'parsedate'
5
+ end
3
6
 
4
7
  module SVG
5
8
  module Graph
@@ -159,8 +162,13 @@ module SVG
159
162
  if im3 == 0
160
163
  y << data[:data][i]
161
164
  else
162
- arr = ParseDate.parsedate( data[:data][i] )
163
- t = Time.local( *arr[0,6].compact )
165
+ if TIME_PARSE_AVAIL then
166
+ arr = DateTime.parse(data[:data][i])
167
+ t = arr.to_time
168
+ else
169
+ arr = ParseDate.parsedate( data[:data][i] )
170
+ t = Time.local( *arr[0,6].compact )
171
+ end
164
172
  (im3 == 1 ? x_start : x_end) << t.to_i
165
173
  end
166
174
  }
@@ -172,8 +180,14 @@ module SVG
172
180
  protected
173
181
 
174
182
  def min_x_value=(value)
175
- arr = ParseDate.parsedate( value )
176
- @min_x_value = Time.local( *arr[0,6].compact ).to_i
183
+ if TIME_PARSE_AVAIL then
184
+ arr = Time.parse(value)
185
+ t = arr.to_time
186
+ else
187
+ arr = ParseDate.parsedate( value )
188
+ t = Time.local( *arr[0,6].compact )
189
+ end
190
+ @min_x_value = t.to_i
177
191
  end
178
192
 
179
193
 
@@ -1,5 +1,8 @@
1
1
  require 'SVG/Graph/Plot'
2
- require 'parsedate'
2
+ TIME_PARSE_AVAIL = (RUBY_VERSION =~ /1\.9\./) ? true : false
3
+ if not TIME_PARSE_AVAIL then
4
+ require 'parsedate'
5
+ end
3
6
 
4
7
  module SVG
5
8
  module Graph
@@ -7,15 +10,17 @@ module SVG
7
10
  #
8
11
  # = Synopsis
9
12
  #
10
- # require 'SVG/Graph/TimeSeriess'
11
- #
13
+ # require 'SVG/Graph/TimeSeries'
14
+ #
12
15
  # # 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
- #
16
+ # projection = ["6/17/72", 11, "1/11/72", 7, "4/13/04 17:31", 11,
17
+ # "9/11/01", 9, "9/1/85", 2, "9/1/88", 1, "1/15/95", 13]
18
+ # actual = ["8/1/73", 18, "3/1/77", 15, "10/1/98", 4,
19
+ # "5/1/02", 14, "3/1/95", 6, "8/1/91", 12, "12/1/87", 6,
20
+ # "5/1/84", 17, "10/1/80", 12]
21
+ #
22
+ # title = "Ice Cream Cone Consumption"
23
+ #
19
24
  # graph = SVG::Graph::TimeSeries.new( {
20
25
  # :width => 640,
21
26
  # :height => 480,
@@ -27,7 +32,7 @@ module SVG
27
32
  # :scale_y_integers => true,
28
33
  # :min_x_value => 0,
29
34
  # :min_y_value => 0,
30
- # :show_data_labels => true,
35
+ # :show_data_values => true,
31
36
  # :show_x_guidelines => true,
32
37
  # :show_x_title => true,
33
38
  # :x_title => "Time",
@@ -39,13 +44,13 @@ module SVG
39
44
  # })
40
45
  #
41
46
  # graph.add_data({
42
- # :data => projection
43
- # :title => 'Projected',
47
+ # :data => projection,
48
+ # :title => 'Projected',
44
49
  # })
45
- #
50
+ #
46
51
  # graph.add_data({
47
- # :data => actual,
48
- # :title => 'Actual',
52
+ # :data => actual,
53
+ # :title => 'Actual',
49
54
  # })
50
55
  #
51
56
  # print graph.burn()
@@ -140,46 +145,35 @@ module SVG
140
145
  # :title => 'Two'
141
146
  # )
142
147
  #
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.
148
+ # Note that the data must be in time,value pairs. The time may be any date in
149
+ # a format that is parseable by ParseDate, a Time object, or a number of seconds
150
+ # after the unix epoch.
145
151
  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
152
+ data[:data].each_index do |i|
153
+ data[:data][i] = parse_time(data[:data][i]).to_i if i % 2 == 0
154
+ end
155
+ super(data)
170
156
  end
171
157
 
172
158
 
173
159
  protected
174
160
 
175
161
  def min_x_value=(value)
176
- arr = ParseDate.parsedate( value )
177
- @min_x_value = Time.local( *arr[0,6].compact ).to_i
162
+ t = parse_time(value)
163
+ @min_x_value = t.to_i
178
164
  end
179
165
 
166
+ def max_x_value=(value)
167
+ t = parse_time(value)
168
+ @max_x_value = t.to_i
169
+ end
180
170
 
181
- def format x, y
182
- Time.at( x ).strftime( popup_format )
171
+ def format x, y, description
172
+ info = [
173
+ Time.at(x).strftime(popup_format),
174
+ round_popups ? (y * 100).to_i / 100 : y,
175
+ description
176
+ ].compact.join(', ')
183
177
  end
184
178
 
185
179
  def get_x_labels
@@ -187,6 +181,29 @@ module SVG
187
181
  end
188
182
 
189
183
  private
184
+
185
+ # Accepts date time as a string, number of seconds since the epoch, or Time
186
+ # object and returns a Time object. Raises an error if not a valid date time
187
+ # representation.
188
+ def parse_time(time)
189
+ case time
190
+ when Time
191
+ return time
192
+ when String
193
+ if TIME_PARSE_AVAIL then
194
+ arr = DateTime.parse(time)
195
+ return arr.to_time
196
+ else
197
+ arr = ParseDate.parsedate(time)
198
+ return Time.local( *arr[0,6].compact )
199
+ end
200
+ when Integer
201
+ return Time.at(time)
202
+ else
203
+ raise "Can not parse time #{time.inspect}"
204
+ end
205
+ end
206
+
190
207
  def get_x_values
191
208
  rv = []
192
209
  min, max, scale_division = x_range