svg-graph 1.0.1 → 1.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data.tar.gz.sig +0 -0
- data/History.txt +7 -5
- data/Manifest.txt +53 -17
- data/README.markdown +14 -0
- data/README.txt +10 -3
- data/Rakefile +6 -0
- data/build.xml +143 -0
- data/dist.xml.in +46 -0
- data/images/bar.png +0 -0
- data/images/bar.svg +76 -0
- data/images/bar.svgz +0 -0
- data/images/barhorizontal.png +0 -0
- data/images/barhorizontal.svg +76 -0
- data/images/barhorizontal.svgz +0 -0
- data/images/line.png +0 -0
- data/images/line.svg +80 -0
- data/images/line.svgz +0 -0
- data/images/pie.png +0 -0
- data/images/pie.svg +70 -0
- data/images/pie.svgz +0 -0
- data/images/plot.png +0 -0
- data/images/plot.svg +131 -0
- data/images/plot.svgz +0 -0
- data/images/schedule.png +0 -0
- data/images/schedule.svg +67 -0
- data/images/timeseries.png +0 -0
- data/images/timeseries.svg +179 -0
- data/images/timeseries.svgz +0 -0
- data/index.xml +281 -0
- data/install.rb +161 -0
- data/lib/SVG/Graph/Bar.rb +1 -1
- data/lib/SVG/Graph/Graph.rb +11 -9
- data/lib/SVG/Graph/Line.rb +1 -0
- data/lib/SVG/Graph/Pie.rb +3 -3
- data/lib/SVG/Graph/Plot.rb +69 -20
- data/lib/SVG/Graph/Schedule.rb +19 -5
- data/lib/SVG/Graph/TimeSeries.rb +62 -45
- data/lib/svggraph.rb +1 -2
- data/screenshots.xml +148 -0
- data/style/common.xsl +37 -0
- data/style/release_html.xsl +169 -0
- data/style/release_txt.xsl +25 -0
- data/svg-graph.gemspec +59 -0
- data/test/data.txt +4 -0
- data/test/plot.rb +51 -0
- data/test/schedule.rb +43 -0
- data/test/single.rb +33 -0
- data/test/test.rb +54 -0
- data/test/test_plot.rb +170 -0
- data/test/test_svg_graph.rb +24 -55
- data/test/timeseries.rb +58 -0
- metadata +79 -14
- metadata.gz.sig +2 -1
data/lib/SVG/Graph/Graph.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
797
|
+
names = $1
|
796
798
|
css = $'
|
797
799
|
css =~ /([^}]+)\}/m
|
798
800
|
content = $1
|
data/lib/SVG/Graph/Line.rb
CHANGED
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
|
-
|
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)
|
data/lib/SVG/Graph/Plot.rb
CHANGED
@@ -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
|
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
|
-
|
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 =
|
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
|
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 =
|
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
|
257
|
-
y_min, y_max
|
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
|
-
|
283
|
-
"
|
284
|
-
|
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
|
-
|
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
|
data/lib/SVG/Graph/Schedule.rb
CHANGED
@@ -1,5 +1,8 @@
|
|
1
1
|
require 'SVG/Graph/Plot'
|
2
|
-
|
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
|
-
|
163
|
-
|
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
|
-
|
176
|
-
|
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
|
|
data/lib/SVG/Graph/TimeSeries.rb
CHANGED
@@ -1,5 +1,8 @@
|
|
1
1
|
require 'SVG/Graph/Plot'
|
2
|
-
|
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/
|
11
|
-
#
|
13
|
+
# require 'SVG/Graph/TimeSeries'
|
14
|
+
#
|
12
15
|
# # Data sets are x,y pairs
|
13
|
-
#
|
14
|
-
#
|
15
|
-
#
|
16
|
-
#
|
17
|
-
#
|
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
|
-
# :
|
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
|
-
#
|
43
|
-
#
|
47
|
+
# :data => projection,
|
48
|
+
# :title => 'Projected',
|
44
49
|
# })
|
45
|
-
#
|
50
|
+
#
|
46
51
|
# graph.add_data({
|
47
|
-
#
|
48
|
-
#
|
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
|
144
|
-
#
|
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
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
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
|
-
|
177
|
-
@min_x_value =
|
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
|
-
|
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
|