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.
- 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
|