svg-graph-test 0.0.1
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.
- checksums.yaml +7 -0
- data/GPL.txt +340 -0
- data/History.txt +97 -0
- data/LICENSE.txt +57 -0
- data/README.md +132 -0
- data/README.txt +89 -0
- data/Rakefile +42 -0
- data/lib/SVG/Graph/Bar.rb +154 -0
- data/lib/SVG/Graph/BarBase.rb +149 -0
- data/lib/SVG/Graph/BarHorizontal.rb +155 -0
- data/lib/SVG/Graph/C3js.rb +274 -0
- data/lib/SVG/Graph/DataPoint.rb +86 -0
- data/lib/SVG/Graph/ErrBar.rb +198 -0
- data/lib/SVG/Graph/Graph.rb +1338 -0
- data/lib/SVG/Graph/Line.rb +467 -0
- data/lib/SVG/Graph/Pie.rb +442 -0
- data/lib/SVG/Graph/Plot.rb +636 -0
- data/lib/SVG/Graph/Schedule.rb +386 -0
- data/lib/SVG/Graph/TimeSeries.rb +263 -0
- data/lib/svggraph.rb +18 -0
- data/test/test_svg_graph.rb +68 -0
- metadata +79 -0
@@ -0,0 +1,467 @@
|
|
1
|
+
require_relative 'Graph'
|
2
|
+
|
3
|
+
module SVG
|
4
|
+
module Graph
|
5
|
+
# === Create presentation quality SVG line graphs easily
|
6
|
+
#
|
7
|
+
# = Synopsis
|
8
|
+
#
|
9
|
+
# require 'SVG/Graph/Line'
|
10
|
+
#
|
11
|
+
# fields = %w(Jan Feb Mar);
|
12
|
+
# data_sales_02 = [12, 45, 21]
|
13
|
+
# data_sales_03 = [15, 30, 40]
|
14
|
+
#
|
15
|
+
# graph = SVG::Graph::Line.new({
|
16
|
+
# :height => 500,
|
17
|
+
# :width => 300,
|
18
|
+
# :fields => fields,
|
19
|
+
# })
|
20
|
+
#
|
21
|
+
# graph.add_data({
|
22
|
+
# :data => data_sales_02,
|
23
|
+
# :title => 'Sales 2002',
|
24
|
+
# })
|
25
|
+
#
|
26
|
+
# graph.add_data({
|
27
|
+
# :data => data_sales_03,
|
28
|
+
# :title => 'Sales 2003',
|
29
|
+
# })
|
30
|
+
#
|
31
|
+
# print "Content-type: image/svg+xml\r\n\r\n";
|
32
|
+
# print graph.burn();
|
33
|
+
#
|
34
|
+
# = Description
|
35
|
+
#
|
36
|
+
# This object aims to allow you to easily create high quality
|
37
|
+
# SVG line graphs. You can either use the default style sheet
|
38
|
+
# or supply your own. Either way there are many options which can
|
39
|
+
# be configured to give you control over how the graph is
|
40
|
+
# generated - with or without a key, data elements at each point,
|
41
|
+
# title, subtitle etc.
|
42
|
+
#
|
43
|
+
# = Examples
|
44
|
+
#
|
45
|
+
# https://github.com/lumean/svg-graph2/blob/master/examples/line.rb
|
46
|
+
#
|
47
|
+
# = Notes
|
48
|
+
# Only number of fileds datapoints will be drawn, additional data values
|
49
|
+
# are ignored. Nil values in data are skipped and
|
50
|
+
# interpolated as straight line to the next datapoint.
|
51
|
+
#
|
52
|
+
# The default stylesheet handles upto 10 data sets, if you
|
53
|
+
# use more you must create your own stylesheet and add the
|
54
|
+
# additional settings for the extra data sets. You will know
|
55
|
+
# if you go over 10 data sets as they will have no style and
|
56
|
+
# be in black.
|
57
|
+
#
|
58
|
+
# = See also
|
59
|
+
#
|
60
|
+
# * SVG::Graph::Graph
|
61
|
+
# * SVG::Graph::BarHorizontal
|
62
|
+
# * SVG::Graph::Bar
|
63
|
+
# * SVG::Graph::Pie
|
64
|
+
# * SVG::Graph::Plot
|
65
|
+
# * SVG::Graph::TimeSeries
|
66
|
+
#
|
67
|
+
# == Author
|
68
|
+
#
|
69
|
+
# Sean E. Russell <serATgermaneHYPHENsoftwareDOTcom>
|
70
|
+
#
|
71
|
+
# Copyright 2004 Sean E. Russell
|
72
|
+
# This software is available under the Ruby license[LICENSE.txt]
|
73
|
+
#
|
74
|
+
class Line < SVG::Graph::Graph
|
75
|
+
# Show a small circle on the graph where the line
|
76
|
+
# goes from one point to the next.
|
77
|
+
attr_accessor :show_data_points
|
78
|
+
# Accumulates each data set. (i.e. Each point increased by sum of
|
79
|
+
# all previous series at same point). Default is 0, set to '1' to show.
|
80
|
+
attr_accessor :stacked
|
81
|
+
# Fill in the area under the plot if true
|
82
|
+
attr_accessor :area_fill
|
83
|
+
|
84
|
+
# The constructor takes a hash reference, :fields (the names for each
|
85
|
+
# field on the X axis) MUST be set, all other values are defaulted to
|
86
|
+
# those shown above - with the exception of style_sheet which defaults
|
87
|
+
# to using the internal style sheet.
|
88
|
+
def initialize config
|
89
|
+
raise "fields was not supplied or is empty" unless config[:fields] &&
|
90
|
+
config[:fields].kind_of?(Array) &&
|
91
|
+
config[:fields].length > 0
|
92
|
+
super
|
93
|
+
end
|
94
|
+
|
95
|
+
# In addition to the defaults set in Graph::initialize, sets
|
96
|
+
# [show_data_points] true
|
97
|
+
# [show_data_values] true
|
98
|
+
# [stacked] false
|
99
|
+
# [area_fill] false
|
100
|
+
def set_defaults
|
101
|
+
init_with(
|
102
|
+
:show_data_points => true,
|
103
|
+
:show_data_values => true,
|
104
|
+
:stacked => false,
|
105
|
+
:area_fill => false
|
106
|
+
)
|
107
|
+
end
|
108
|
+
|
109
|
+
protected
|
110
|
+
|
111
|
+
def max_value
|
112
|
+
max = 0
|
113
|
+
if stacked
|
114
|
+
sums = Array.new(@config[:fields].length).fill(0)
|
115
|
+
|
116
|
+
@data.each do |data|
|
117
|
+
sums.each_index do |i|
|
118
|
+
sums[i] += data[:data][i].to_f
|
119
|
+
end
|
120
|
+
end
|
121
|
+
max = sums.max
|
122
|
+
else
|
123
|
+
# compact removes nil values when computing the max
|
124
|
+
max = @data.collect{ |x|
|
125
|
+
x[:data].compact.max
|
126
|
+
}.max
|
127
|
+
end
|
128
|
+
|
129
|
+
return max
|
130
|
+
end
|
131
|
+
|
132
|
+
def min_value
|
133
|
+
min = 0
|
134
|
+
# compact removes nil values
|
135
|
+
if (!min_scale_value.nil?) then
|
136
|
+
min = min_scale_value
|
137
|
+
elsif (stacked == true) then
|
138
|
+
min = @data[-1][:data].compact.min
|
139
|
+
else
|
140
|
+
min = @data.collect{|x| x[:data].compact.min}.min
|
141
|
+
end
|
142
|
+
|
143
|
+
return min
|
144
|
+
end
|
145
|
+
|
146
|
+
def get_x_labels
|
147
|
+
@config[:fields]
|
148
|
+
end
|
149
|
+
|
150
|
+
def calculate_left_margin
|
151
|
+
super
|
152
|
+
label_left = @config[:fields][0].length / 2 * font_size * 0.6
|
153
|
+
@border_left = label_left if label_left > @border_left
|
154
|
+
end
|
155
|
+
|
156
|
+
def get_y_labels
|
157
|
+
maxvalue = max_value
|
158
|
+
minvalue = min_value
|
159
|
+
range = maxvalue - minvalue
|
160
|
+
# add some padding on top of the graph
|
161
|
+
if range == 0
|
162
|
+
maxvalue += 10
|
163
|
+
else
|
164
|
+
maxvalue += range / 20.0
|
165
|
+
end
|
166
|
+
scale_range = maxvalue - minvalue
|
167
|
+
|
168
|
+
@y_scale_division = scale_divisions || (scale_range / 10.0)
|
169
|
+
@y_offset = 0
|
170
|
+
|
171
|
+
if scale_integers
|
172
|
+
@y_scale_division = @y_scale_division < 1 ? 1 : @y_scale_division.round
|
173
|
+
@y_offset = (minvalue.to_f - minvalue.floor).to_f
|
174
|
+
minvalue = minvalue.floor
|
175
|
+
end
|
176
|
+
|
177
|
+
rv = []
|
178
|
+
|
179
|
+
minvalue.step( maxvalue, @y_scale_division ) {|v| rv << v}
|
180
|
+
return rv
|
181
|
+
end
|
182
|
+
|
183
|
+
def calc_coords(field, value, width = field_width, height = field_height)
|
184
|
+
coords = {:x => 0, :y => 0}
|
185
|
+
coords[:x] = width * field
|
186
|
+
# make sure we do float division, otherwise coords get messed up
|
187
|
+
coords[:y] = @graph_height - (value + @y_offset)/@y_scale_division.to_f * height
|
188
|
+
return coords
|
189
|
+
end
|
190
|
+
|
191
|
+
def draw_data
|
192
|
+
minvalue = min_value
|
193
|
+
fieldheight = field_height
|
194
|
+
fieldwidth = field_width
|
195
|
+
line = @data.length
|
196
|
+
# always zero for filling areas
|
197
|
+
prev_sum = Array.new(@config[:fields].length).fill(-@y_offset)
|
198
|
+
# cumulated sum (used for stacked graphs)
|
199
|
+
cum_sum = Array.new(@config[:fields].length).fill(nil)
|
200
|
+
|
201
|
+
for data in @data.reverse
|
202
|
+
lpath = ""
|
203
|
+
apath = ""
|
204
|
+
|
205
|
+
# reset cum_sum if we are not in a stacked graph
|
206
|
+
if not stacked then cum_sum.fill(nil) end
|
207
|
+
|
208
|
+
# only consider as many datapoints as we have fields
|
209
|
+
@config[:fields].each_index do |i|
|
210
|
+
next if data[:data][i].nil?
|
211
|
+
if cum_sum[i].nil? #first time init
|
212
|
+
cum_sum[i] = data[:data][i] - minvalue
|
213
|
+
else # in case of stacked
|
214
|
+
cum_sum[i] += data[:data][i]
|
215
|
+
end
|
216
|
+
c = calc_coords(i, cum_sum[i], fieldwidth, fieldheight)
|
217
|
+
lpath << "#{c[:x]} #{c[:y]} "
|
218
|
+
end
|
219
|
+
|
220
|
+
if area_fill
|
221
|
+
if stacked then
|
222
|
+
(prev_sum.length - 1).downto 0 do |i|
|
223
|
+
next if prev_sum[i].nil?
|
224
|
+
c = calc_coords(i, prev_sum[i], fieldwidth, fieldheight)
|
225
|
+
|
226
|
+
apath << "#{c[:x]} #{c[:y]} "
|
227
|
+
end
|
228
|
+
|
229
|
+
c = calc_coords(0, prev_sum[0], fieldwidth, fieldheight)
|
230
|
+
else
|
231
|
+
apath = "V#@graph_height"
|
232
|
+
c = calc_coords(0, -@y_offset, fieldwidth, fieldheight)
|
233
|
+
end
|
234
|
+
|
235
|
+
@graph.add_element("path", {
|
236
|
+
"d" => "M#{c[:x]} #{c[:y]} L" + lpath + apath + "Z",
|
237
|
+
"class" => "fill#{line}"
|
238
|
+
})
|
239
|
+
end
|
240
|
+
|
241
|
+
matcher = /^(\S+ \S+) (.*)/.match lpath
|
242
|
+
@graph.add_element("path", {
|
243
|
+
"d" => "M#{matcher[1]} L#{matcher[2]}",
|
244
|
+
"class" => "line#{line}"
|
245
|
+
})
|
246
|
+
|
247
|
+
if show_data_points || show_data_values || add_popups
|
248
|
+
cum_sum.each_index do |i|
|
249
|
+
# skip datapoint if nil
|
250
|
+
next if cum_sum[i].nil?
|
251
|
+
c = calc_coords(i, cum_sum[i], fieldwidth, fieldheight)
|
252
|
+
if show_data_points
|
253
|
+
shape_selection_string = data[:description][i].to_s
|
254
|
+
if !data[:shape][i].nil?
|
255
|
+
shape_selection_string = data[:shape][i].to_s
|
256
|
+
end
|
257
|
+
DataPoint.new(c[:x], c[:y], line).shape(shape_selection_string).each{|s|
|
258
|
+
@graph.add_element( *s )
|
259
|
+
}
|
260
|
+
end
|
261
|
+
|
262
|
+
make_datapoint_text( c[:x], c[:y] - font_size/2, cum_sum[i] + minvalue)
|
263
|
+
# number format shall not apply to popup (use .to_s conversion)
|
264
|
+
descr = ""
|
265
|
+
if !data[:description][i].to_s.empty?
|
266
|
+
descr = ", #{data[:description][i].to_s}"
|
267
|
+
end
|
268
|
+
add_popup(c[:x], c[:y], (cum_sum[i] + minvalue).to_s + descr, "", data[:url][i].to_s)
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
prev_sum = cum_sum.dup
|
273
|
+
line -= 1
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
|
278
|
+
def get_css
|
279
|
+
return <<EOL
|
280
|
+
/* default line styles */
|
281
|
+
.line1{
|
282
|
+
fill: none;
|
283
|
+
stroke: #ff0000;
|
284
|
+
stroke-width: 1px;
|
285
|
+
}
|
286
|
+
.line2{
|
287
|
+
fill: none;
|
288
|
+
stroke: #0000ff;
|
289
|
+
stroke-width: 1px;
|
290
|
+
}
|
291
|
+
.line3{
|
292
|
+
fill: none;
|
293
|
+
stroke: #00ff00;
|
294
|
+
stroke-width: 1px;
|
295
|
+
}
|
296
|
+
.line4{
|
297
|
+
fill: none;
|
298
|
+
stroke: #ffcc00;
|
299
|
+
stroke-width: 1px;
|
300
|
+
}
|
301
|
+
.line5{
|
302
|
+
fill: none;
|
303
|
+
stroke: #00ccff;
|
304
|
+
stroke-width: 1px;
|
305
|
+
}
|
306
|
+
.line6{
|
307
|
+
fill: none;
|
308
|
+
stroke: #ff00ff;
|
309
|
+
stroke-width: 1px;
|
310
|
+
}
|
311
|
+
.line7{
|
312
|
+
fill: none;
|
313
|
+
stroke: #00ffff;
|
314
|
+
stroke-width: 1px;
|
315
|
+
}
|
316
|
+
.line8{
|
317
|
+
fill: none;
|
318
|
+
stroke: #ffff00;
|
319
|
+
stroke-width: 1px;
|
320
|
+
}
|
321
|
+
.line9{
|
322
|
+
fill: none;
|
323
|
+
stroke: #cc6666;
|
324
|
+
stroke-width: 1px;
|
325
|
+
}
|
326
|
+
.line10{
|
327
|
+
fill: none;
|
328
|
+
stroke: #663399;
|
329
|
+
stroke-width: 1px;
|
330
|
+
}
|
331
|
+
.line11{
|
332
|
+
fill: none;
|
333
|
+
stroke: #339900;
|
334
|
+
stroke-width: 1px;
|
335
|
+
}
|
336
|
+
.line12{
|
337
|
+
fill: none;
|
338
|
+
stroke: #9966FF;
|
339
|
+
stroke-width: 1px;
|
340
|
+
}
|
341
|
+
/* default fill styles */
|
342
|
+
.fill1{
|
343
|
+
fill: #cc0000;
|
344
|
+
fill-opacity: 0.2;
|
345
|
+
stroke: none;
|
346
|
+
}
|
347
|
+
.fill2{
|
348
|
+
fill: #0000cc;
|
349
|
+
fill-opacity: 0.2;
|
350
|
+
stroke: none;
|
351
|
+
}
|
352
|
+
.fill3{
|
353
|
+
fill: #00cc00;
|
354
|
+
fill-opacity: 0.2;
|
355
|
+
stroke: none;
|
356
|
+
}
|
357
|
+
.fill4{
|
358
|
+
fill: #ffcc00;
|
359
|
+
fill-opacity: 0.2;
|
360
|
+
stroke: none;
|
361
|
+
}
|
362
|
+
.fill5{
|
363
|
+
fill: #00ccff;
|
364
|
+
fill-opacity: 0.2;
|
365
|
+
stroke: none;
|
366
|
+
}
|
367
|
+
.fill6{
|
368
|
+
fill: #ff00ff;
|
369
|
+
fill-opacity: 0.2;
|
370
|
+
stroke: none;
|
371
|
+
}
|
372
|
+
.fill7{
|
373
|
+
fill: #00ffff;
|
374
|
+
fill-opacity: 0.2;
|
375
|
+
stroke: none;
|
376
|
+
}
|
377
|
+
.fill8{
|
378
|
+
fill: #ffff00;
|
379
|
+
fill-opacity: 0.2;
|
380
|
+
stroke: none;
|
381
|
+
}
|
382
|
+
.fill9{
|
383
|
+
fill: #cc6666;
|
384
|
+
fill-opacity: 0.2;
|
385
|
+
stroke: none;
|
386
|
+
}
|
387
|
+
.fill10{
|
388
|
+
fill: #663399;
|
389
|
+
fill-opacity: 0.2;
|
390
|
+
stroke: none;
|
391
|
+
}
|
392
|
+
.fill11{
|
393
|
+
fill: #339900;
|
394
|
+
fill-opacity: 0.2;
|
395
|
+
stroke: none;
|
396
|
+
}
|
397
|
+
.fill12{
|
398
|
+
fill: #9966FF;
|
399
|
+
fill-opacity: 0.2;
|
400
|
+
stroke: none;
|
401
|
+
}
|
402
|
+
/* default line styles */
|
403
|
+
.key1,.dataPoint1{
|
404
|
+
fill: #ff0000;
|
405
|
+
stroke: none;
|
406
|
+
stroke-width: 1px;
|
407
|
+
}
|
408
|
+
.key2,.dataPoint2{
|
409
|
+
fill: #0000ff;
|
410
|
+
stroke: none;
|
411
|
+
stroke-width: 1px;
|
412
|
+
}
|
413
|
+
.key3,.dataPoint3{
|
414
|
+
fill: #00ff00;
|
415
|
+
stroke: none;
|
416
|
+
stroke-width: 1px;
|
417
|
+
}
|
418
|
+
.key4,.dataPoint4{
|
419
|
+
fill: #ffcc00;
|
420
|
+
stroke: none;
|
421
|
+
stroke-width: 1px;
|
422
|
+
}
|
423
|
+
.key5,.dataPoint5{
|
424
|
+
fill: #00ccff;
|
425
|
+
stroke: none;
|
426
|
+
stroke-width: 1px;
|
427
|
+
}
|
428
|
+
.key6,.dataPoint6{
|
429
|
+
fill: #ff00ff;
|
430
|
+
stroke: none;
|
431
|
+
stroke-width: 1px;
|
432
|
+
}
|
433
|
+
.key7,.dataPoint7{
|
434
|
+
fill: #00ffff;
|
435
|
+
stroke: none;
|
436
|
+
stroke-width: 1px;
|
437
|
+
}
|
438
|
+
.key8,.dataPoint8{
|
439
|
+
fill: #ffff00;
|
440
|
+
stroke: none;
|
441
|
+
stroke-width: 1px;
|
442
|
+
}
|
443
|
+
.key9,.dataPoint9{
|
444
|
+
fill: #cc6666;
|
445
|
+
stroke: none;
|
446
|
+
stroke-width: 1px;
|
447
|
+
}
|
448
|
+
.key10,.dataPoint10{
|
449
|
+
fill: #663399;
|
450
|
+
stroke: none;
|
451
|
+
stroke-width: 1px;
|
452
|
+
}
|
453
|
+
.key11,.dataPoint11{
|
454
|
+
fill: #339900;
|
455
|
+
stroke: none;
|
456
|
+
stroke-width: 1px;
|
457
|
+
}
|
458
|
+
.key12,.dataPoint12{
|
459
|
+
fill: #9966FF;
|
460
|
+
stroke: none;
|
461
|
+
stroke-width: 1px;
|
462
|
+
}
|
463
|
+
EOL
|
464
|
+
end
|
465
|
+
end
|
466
|
+
end
|
467
|
+
end
|