git_pm 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.
- data/.document +5 -0
- data/.gitignore +21 -0
- data/LICENSE +20 -0
- data/README.rdoc +17 -0
- data/Rakefile +45 -0
- data/VERSION +1 -0
- data/bin/git_pm +20 -0
- data/lib/SVG/Graph/Bar.rb +148 -0
- data/lib/SVG/Graph/BarBase.rb +139 -0
- data/lib/SVG/Graph/BarHorizontal.rb +149 -0
- data/lib/SVG/Graph/Graph.rb +978 -0
- data/lib/SVG/Graph/Line.rb +444 -0
- data/lib/SVG/Graph/Pie.rb +347 -0
- data/lib/SVG/Graph/Plot.rb +500 -0
- data/lib/SVG/Graph/Schedule.rb +373 -0
- data/lib/SVG/Graph/TimeSeries.rb +241 -0
- data/lib/git_pm.rb +30 -0
- data/lib/git_pm/commits_by_dev.rb +81 -0
- data/lib/git_pm/commits_per_month.rb +76 -0
- data/lib/git_pm/dir_lines_by_author.rb +99 -0
- data/lib/git_pm/git.rb +67 -0
- data/lib/git_pm/git_data.rb +86 -0
- data/lib/git_pm/lines_by_author.rb +47 -0
- data/spec/git_pm_spec.rb +7 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +9 -0
- metadata +94 -0
@@ -0,0 +1,444 @@
|
|
1
|
+
require 'SVG/Graph/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
|
+
# http://www.germane-software/repositories/public/SVG/test/single.rb
|
46
|
+
#
|
47
|
+
# = Notes
|
48
|
+
#
|
49
|
+
# The default stylesheet handles upto 10 data sets, if you
|
50
|
+
# use more you must create your own stylesheet and add the
|
51
|
+
# additional settings for the extra data sets. You will know
|
52
|
+
# if you go over 10 data sets as they will have no style and
|
53
|
+
# be in black.
|
54
|
+
#
|
55
|
+
# = See also
|
56
|
+
#
|
57
|
+
# * SVG::Graph::Graph
|
58
|
+
# * SVG::Graph::BarHorizontal
|
59
|
+
# * SVG::Graph::Bar
|
60
|
+
# * SVG::Graph::Pie
|
61
|
+
# * SVG::Graph::Plot
|
62
|
+
# * SVG::Graph::TimeSeries
|
63
|
+
#
|
64
|
+
# == Author
|
65
|
+
#
|
66
|
+
# Sean E. Russell <serATgermaneHYPHENsoftwareDOTcom>
|
67
|
+
#
|
68
|
+
# Copyright 2004 Sean E. Russell
|
69
|
+
# This software is available under the Ruby license[LICENSE.txt]
|
70
|
+
#
|
71
|
+
class Line < SVG::Graph::Graph
|
72
|
+
# Show a small circle on the graph where the line
|
73
|
+
# goes from one point to the next.
|
74
|
+
attr_accessor :show_data_points
|
75
|
+
# Accumulates each data set. (i.e. Each point increased by sum of
|
76
|
+
# all previous series at same point). Default is 0, set to '1' to show.
|
77
|
+
attr_accessor :stacked
|
78
|
+
# Fill in the area under the plot if true
|
79
|
+
attr_accessor :area_fill
|
80
|
+
|
81
|
+
# The constructor takes a hash reference, fields (the names for each
|
82
|
+
# field on the X axis) MUST be set, all other values are defaulted to
|
83
|
+
# those shown above - with the exception of style_sheet which defaults
|
84
|
+
# to using the internal style sheet.
|
85
|
+
def initialize config
|
86
|
+
raise "fields was not supplied or is empty" unless config[:fields] &&
|
87
|
+
config[:fields].kind_of?(Array) &&
|
88
|
+
config[:fields].length > 0
|
89
|
+
super
|
90
|
+
end
|
91
|
+
|
92
|
+
# In addition to the defaults set in Graph::initialize, sets
|
93
|
+
# [show_data_points] true
|
94
|
+
# [show_data_values] true
|
95
|
+
# [stacked] false
|
96
|
+
# [area_fill] false
|
97
|
+
def set_defaults
|
98
|
+
init_with(
|
99
|
+
:show_data_points => true,
|
100
|
+
:show_data_values => true,
|
101
|
+
:stacked => false,
|
102
|
+
:area_fill => false
|
103
|
+
)
|
104
|
+
|
105
|
+
self.top_align = self.top_font = self.right_align = self.right_font = 1
|
106
|
+
end
|
107
|
+
|
108
|
+
protected
|
109
|
+
|
110
|
+
def max_value
|
111
|
+
max = 0
|
112
|
+
|
113
|
+
if (stacked == true) then
|
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
|
+
|
122
|
+
max = sums.max
|
123
|
+
else
|
124
|
+
max = @data.collect{|x| x[:data].max}.max
|
125
|
+
end
|
126
|
+
|
127
|
+
return max
|
128
|
+
end
|
129
|
+
|
130
|
+
def min_value
|
131
|
+
min = 0
|
132
|
+
|
133
|
+
if (min_scale_value.nil? == false) then
|
134
|
+
min = min_scale_value
|
135
|
+
elsif (stacked == true) then
|
136
|
+
min = @data[-1][:data].min
|
137
|
+
else
|
138
|
+
min = @data.collect{|x| x[:data].min}.min
|
139
|
+
end
|
140
|
+
|
141
|
+
return min
|
142
|
+
end
|
143
|
+
|
144
|
+
def get_x_labels
|
145
|
+
@config[:fields]
|
146
|
+
end
|
147
|
+
|
148
|
+
def calculate_left_margin
|
149
|
+
super
|
150
|
+
label_left = @config[:fields][0].length / 2 * font_size * 0.6
|
151
|
+
@border_left = label_left if label_left > @border_left
|
152
|
+
end
|
153
|
+
|
154
|
+
def get_y_labels
|
155
|
+
maxvalue = max_value
|
156
|
+
minvalue = min_value
|
157
|
+
range = maxvalue - minvalue
|
158
|
+
top_pad = range == 0 ? 10 : range / 20.0
|
159
|
+
scale_range = (maxvalue + top_pad) - minvalue
|
160
|
+
|
161
|
+
scale_division = scale_divisions || (scale_range / 10.0)
|
162
|
+
|
163
|
+
if scale_integers
|
164
|
+
scale_division = scale_division < 1 ? 1 : scale_division.round
|
165
|
+
end
|
166
|
+
|
167
|
+
rv = []
|
168
|
+
maxvalue = maxvalue%scale_division == 0 ?
|
169
|
+
maxvalue : maxvalue + scale_division
|
170
|
+
minvalue.step( maxvalue, scale_division ) {|v| rv << v}
|
171
|
+
return rv
|
172
|
+
end
|
173
|
+
|
174
|
+
def calc_coords(field, value, width = field_width, height = field_height)
|
175
|
+
coords = {:x => 0, :y => 0}
|
176
|
+
coords[:x] = width * field
|
177
|
+
coords[:y] = @graph_height - value * height
|
178
|
+
|
179
|
+
return coords
|
180
|
+
end
|
181
|
+
|
182
|
+
def draw_data
|
183
|
+
minvalue = min_value
|
184
|
+
fieldheight = (@graph_height.to_f - font_size*2*top_font) /
|
185
|
+
(get_y_labels.max - get_y_labels.min)
|
186
|
+
fieldwidth = field_width
|
187
|
+
line = @data.length
|
188
|
+
|
189
|
+
prev_sum = Array.new(@config[:fields].length).fill(0)
|
190
|
+
cum_sum = Array.new(@config[:fields].length).fill(-minvalue)
|
191
|
+
|
192
|
+
for data in @data.reverse
|
193
|
+
lpath = ""
|
194
|
+
apath = ""
|
195
|
+
|
196
|
+
if not stacked then cum_sum.fill(-minvalue) end
|
197
|
+
|
198
|
+
data[:data].each_index do |i|
|
199
|
+
cum_sum[i] += data[:data][i]
|
200
|
+
|
201
|
+
c = calc_coords(i, cum_sum[i], fieldwidth, fieldheight)
|
202
|
+
|
203
|
+
lpath << "#{c[:x]} #{c[:y]} "
|
204
|
+
end
|
205
|
+
|
206
|
+
if area_fill
|
207
|
+
if stacked then
|
208
|
+
(prev_sum.length - 1).downto 0 do |i|
|
209
|
+
c = calc_coords(i, prev_sum[i], fieldwidth, fieldheight)
|
210
|
+
|
211
|
+
apath << "#{c[:x]} #{c[:y]} "
|
212
|
+
end
|
213
|
+
|
214
|
+
c = calc_coords(0, prev_sum[0], fieldwidth, fieldheight)
|
215
|
+
else
|
216
|
+
apath = "V#@graph_height"
|
217
|
+
c = calc_coords(0, 0, fieldwidth, fieldheight)
|
218
|
+
end
|
219
|
+
|
220
|
+
@graph.add_element("path", {
|
221
|
+
"d" => "M#{c[:x]} #{c[:y]} L" + lpath + apath + "Z",
|
222
|
+
"class" => "fill#{line}"
|
223
|
+
})
|
224
|
+
end
|
225
|
+
|
226
|
+
@graph.add_element("path", {
|
227
|
+
"d" => "M0 #@graph_height L" + lpath,
|
228
|
+
"class" => "line#{line}"
|
229
|
+
})
|
230
|
+
|
231
|
+
if show_data_points || show_data_values
|
232
|
+
cum_sum.each_index do |i|
|
233
|
+
if show_data_points
|
234
|
+
@graph.add_element( "circle", {
|
235
|
+
"cx" => (fieldwidth * i).to_s,
|
236
|
+
"cy" => (@graph_height - cum_sum[i] * fieldheight).to_s,
|
237
|
+
"r" => "2.5",
|
238
|
+
"class" => "dataPoint#{line}"
|
239
|
+
})
|
240
|
+
end
|
241
|
+
make_datapoint_text(
|
242
|
+
fieldwidth * i,
|
243
|
+
@graph_height - cum_sum[i] * fieldheight - 6,
|
244
|
+
cum_sum[i] + minvalue
|
245
|
+
)
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
prev_sum = cum_sum.dup
|
250
|
+
line -= 1
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
|
255
|
+
def get_css
|
256
|
+
return <<EOL
|
257
|
+
/* default line styles */
|
258
|
+
.line1{
|
259
|
+
fill: none;
|
260
|
+
stroke: #ff0000;
|
261
|
+
stroke-width: 1px;
|
262
|
+
}
|
263
|
+
.line2{
|
264
|
+
fill: none;
|
265
|
+
stroke: #0000ff;
|
266
|
+
stroke-width: 1px;
|
267
|
+
}
|
268
|
+
.line3{
|
269
|
+
fill: none;
|
270
|
+
stroke: #00ff00;
|
271
|
+
stroke-width: 1px;
|
272
|
+
}
|
273
|
+
.line4{
|
274
|
+
fill: none;
|
275
|
+
stroke: #ffcc00;
|
276
|
+
stroke-width: 1px;
|
277
|
+
}
|
278
|
+
.line5{
|
279
|
+
fill: none;
|
280
|
+
stroke: #00ccff;
|
281
|
+
stroke-width: 1px;
|
282
|
+
}
|
283
|
+
.line6{
|
284
|
+
fill: none;
|
285
|
+
stroke: #ff00ff;
|
286
|
+
stroke-width: 1px;
|
287
|
+
}
|
288
|
+
.line7{
|
289
|
+
fill: none;
|
290
|
+
stroke: #00ffff;
|
291
|
+
stroke-width: 1px;
|
292
|
+
}
|
293
|
+
.line8{
|
294
|
+
fill: none;
|
295
|
+
stroke: #ffff00;
|
296
|
+
stroke-width: 1px;
|
297
|
+
}
|
298
|
+
.line9{
|
299
|
+
fill: none;
|
300
|
+
stroke: #ccc6666;
|
301
|
+
stroke-width: 1px;
|
302
|
+
}
|
303
|
+
.line10{
|
304
|
+
fill: none;
|
305
|
+
stroke: #663399;
|
306
|
+
stroke-width: 1px;
|
307
|
+
}
|
308
|
+
.line11{
|
309
|
+
fill: none;
|
310
|
+
stroke: #339900;
|
311
|
+
stroke-width: 1px;
|
312
|
+
}
|
313
|
+
.line12{
|
314
|
+
fill: none;
|
315
|
+
stroke: #9966FF;
|
316
|
+
stroke-width: 1px;
|
317
|
+
}
|
318
|
+
/* default fill styles */
|
319
|
+
.fill1{
|
320
|
+
fill: #cc0000;
|
321
|
+
fill-opacity: 0.2;
|
322
|
+
stroke: none;
|
323
|
+
}
|
324
|
+
.fill2{
|
325
|
+
fill: #0000cc;
|
326
|
+
fill-opacity: 0.2;
|
327
|
+
stroke: none;
|
328
|
+
}
|
329
|
+
.fill3{
|
330
|
+
fill: #00cc00;
|
331
|
+
fill-opacity: 0.2;
|
332
|
+
stroke: none;
|
333
|
+
}
|
334
|
+
.fill4{
|
335
|
+
fill: #ffcc00;
|
336
|
+
fill-opacity: 0.2;
|
337
|
+
stroke: none;
|
338
|
+
}
|
339
|
+
.fill5{
|
340
|
+
fill: #00ccff;
|
341
|
+
fill-opacity: 0.2;
|
342
|
+
stroke: none;
|
343
|
+
}
|
344
|
+
.fill6{
|
345
|
+
fill: #ff00ff;
|
346
|
+
fill-opacity: 0.2;
|
347
|
+
stroke: none;
|
348
|
+
}
|
349
|
+
.fill7{
|
350
|
+
fill: #00ffff;
|
351
|
+
fill-opacity: 0.2;
|
352
|
+
stroke: none;
|
353
|
+
}
|
354
|
+
.fill8{
|
355
|
+
fill: #ffff00;
|
356
|
+
fill-opacity: 0.2;
|
357
|
+
stroke: none;
|
358
|
+
}
|
359
|
+
.fill9{
|
360
|
+
fill: #cc6666;
|
361
|
+
fill-opacity: 0.2;
|
362
|
+
stroke: none;
|
363
|
+
}
|
364
|
+
.fill10{
|
365
|
+
fill: #663399;
|
366
|
+
fill-opacity: 0.2;
|
367
|
+
stroke: none;
|
368
|
+
}
|
369
|
+
.fill11{
|
370
|
+
fill: #339900;
|
371
|
+
fill-opacity: 0.2;
|
372
|
+
stroke: none;
|
373
|
+
}
|
374
|
+
.fill12{
|
375
|
+
fill: #9966FF;
|
376
|
+
fill-opacity: 0.2;
|
377
|
+
stroke: none;
|
378
|
+
}
|
379
|
+
/* default line styles */
|
380
|
+
.key1,.dataPoint1{
|
381
|
+
fill: #ff0000;
|
382
|
+
stroke: none;
|
383
|
+
stroke-width: 1px;
|
384
|
+
}
|
385
|
+
.key2,.dataPoint2{
|
386
|
+
fill: #0000ff;
|
387
|
+
stroke: none;
|
388
|
+
stroke-width: 1px;
|
389
|
+
}
|
390
|
+
.key3,.dataPoint3{
|
391
|
+
fill: #00ff00;
|
392
|
+
stroke: none;
|
393
|
+
stroke-width: 1px;
|
394
|
+
}
|
395
|
+
.key4,.dataPoint4{
|
396
|
+
fill: #ffcc00;
|
397
|
+
stroke: none;
|
398
|
+
stroke-width: 1px;
|
399
|
+
}
|
400
|
+
.key5,.dataPoint5{
|
401
|
+
fill: #00ccff;
|
402
|
+
stroke: none;
|
403
|
+
stroke-width: 1px;
|
404
|
+
}
|
405
|
+
.key6,.dataPoint6{
|
406
|
+
fill: #ff00ff;
|
407
|
+
stroke: none;
|
408
|
+
stroke-width: 1px;
|
409
|
+
}
|
410
|
+
.key7,.dataPoint7{
|
411
|
+
fill: #00ffff;
|
412
|
+
stroke: none;
|
413
|
+
stroke-width: 1px;
|
414
|
+
}
|
415
|
+
.key8,.dataPoint8{
|
416
|
+
fill: #ffff00;
|
417
|
+
stroke: none;
|
418
|
+
stroke-width: 1px;
|
419
|
+
}
|
420
|
+
.key9,.dataPoint9{
|
421
|
+
fill: #cc6666;
|
422
|
+
stroke: none;
|
423
|
+
stroke-width: 1px;
|
424
|
+
}
|
425
|
+
.key10,.dataPoint10{
|
426
|
+
fill: #663399;
|
427
|
+
stroke: none;
|
428
|
+
stroke-width: 1px;
|
429
|
+
}
|
430
|
+
.key11,.dataPoint11{
|
431
|
+
fill: #339900;
|
432
|
+
stroke: none;
|
433
|
+
stroke-width: 1px;
|
434
|
+
}
|
435
|
+
.key12,.dataPoint12{
|
436
|
+
fill: #9966FF;
|
437
|
+
stroke: none;
|
438
|
+
stroke-width: 1px;
|
439
|
+
}
|
440
|
+
EOL
|
441
|
+
end
|
442
|
+
end
|
443
|
+
end
|
444
|
+
end
|
@@ -0,0 +1,347 @@
|
|
1
|
+
require 'SVG/Graph/Graph'
|
2
|
+
|
3
|
+
module SVG
|
4
|
+
module Graph
|
5
|
+
# === Create presentation quality SVG pie graphs easily
|
6
|
+
#
|
7
|
+
# == Synopsis
|
8
|
+
#
|
9
|
+
# require 'SVG/Graph/Pie'
|
10
|
+
#
|
11
|
+
# fields = %w(Jan Feb Mar)
|
12
|
+
# data_sales_02 = [12, 45, 21]
|
13
|
+
#
|
14
|
+
# graph = SVG::Graph::Pie.new({
|
15
|
+
# :height => 500,
|
16
|
+
# :width => 300,
|
17
|
+
# :fields => fields,
|
18
|
+
# })
|
19
|
+
#
|
20
|
+
# graph.add_data({
|
21
|
+
# :data => data_sales_02,
|
22
|
+
# :title => 'Sales 2002',
|
23
|
+
# })
|
24
|
+
#
|
25
|
+
# print "Content-type: image/svg+xml\r\n\r\n"
|
26
|
+
# print graph.burn();
|
27
|
+
#
|
28
|
+
# == Description
|
29
|
+
#
|
30
|
+
# This object aims to allow you to easily create high quality
|
31
|
+
# SVG pie graphs. You can either use the default style sheet
|
32
|
+
# or supply your own. Either way there are many options which can
|
33
|
+
# be configured to give you control over how the graph is
|
34
|
+
# generated - with or without a key, display percent on pie chart,
|
35
|
+
# title, subtitle etc.
|
36
|
+
#
|
37
|
+
# = Examples
|
38
|
+
#
|
39
|
+
# http://www.germane-software/repositories/public/SVG/test/single.rb
|
40
|
+
#
|
41
|
+
# == See also
|
42
|
+
#
|
43
|
+
# * SVG::Graph::Graph
|
44
|
+
# * SVG::Graph::BarHorizontal
|
45
|
+
# * SVG::Graph::Bar
|
46
|
+
# * SVG::Graph::Line
|
47
|
+
# * SVG::Graph::Plot
|
48
|
+
# * SVG::Graph::TimeSeries
|
49
|
+
#
|
50
|
+
# == Author
|
51
|
+
#
|
52
|
+
# Sean E. Russell <serATgermaneHYPHENsoftwareDOTcom>
|
53
|
+
#
|
54
|
+
# Copyright 2004 Sean E. Russell
|
55
|
+
# This software is available under the Ruby license[LICENSE.txt]
|
56
|
+
#
|
57
|
+
class Pie < Graph
|
58
|
+
# Defaults are those set by Graph::initialize, and
|
59
|
+
# [show_shadow] true
|
60
|
+
# [shadow_offset] 10
|
61
|
+
# [show_data_labels] false
|
62
|
+
# [show_actual_values] false
|
63
|
+
# [show_percent] true
|
64
|
+
# [show_key_data_labels] true
|
65
|
+
# [show_key_actual_values] true
|
66
|
+
# [show_key_percent] false
|
67
|
+
# [expanded] false
|
68
|
+
# [expand_greatest] false
|
69
|
+
# [expand_gap] 10
|
70
|
+
# [show_x_labels] false
|
71
|
+
# [show_y_labels] false
|
72
|
+
# [datapoint_font_size] 12
|
73
|
+
def set_defaults
|
74
|
+
init_with(
|
75
|
+
:show_shadow => true,
|
76
|
+
:shadow_offset => 10,
|
77
|
+
|
78
|
+
:show_data_labels => false,
|
79
|
+
:show_actual_values => false,
|
80
|
+
:show_percent => true,
|
81
|
+
|
82
|
+
:show_key_data_labels => true,
|
83
|
+
:show_key_actual_values => true,
|
84
|
+
:show_key_percent => false,
|
85
|
+
|
86
|
+
:expanded => false,
|
87
|
+
:expand_greatest => false,
|
88
|
+
:expand_gap => 10,
|
89
|
+
|
90
|
+
:show_x_labels => false,
|
91
|
+
:show_y_labels => false,
|
92
|
+
:datapoint_font_size => 12
|
93
|
+
)
|
94
|
+
@data = []
|
95
|
+
end
|
96
|
+
|
97
|
+
# Adds a data set to the graph.
|
98
|
+
#
|
99
|
+
# graph.add_data( { :data => [1,2,3,4] } )
|
100
|
+
#
|
101
|
+
# Note that the :title is not necessary. If multiple
|
102
|
+
# data sets are added to the graph, the pie chart will
|
103
|
+
# display the +sums+ of the data. EG:
|
104
|
+
#
|
105
|
+
# graph.add_data( { :data => [1,2,3,4] } )
|
106
|
+
# graph.add_data( { :data => [2,3,5,9] } )
|
107
|
+
#
|
108
|
+
# is the same as:
|
109
|
+
#
|
110
|
+
# graph.add_data( { :data => [3,5,8,13] } )
|
111
|
+
def add_data arg
|
112
|
+
arg[:data].each_index {|idx|
|
113
|
+
@data[idx] = 0 unless @data[idx]
|
114
|
+
@data[idx] += arg[:data][idx]
|
115
|
+
}
|
116
|
+
end
|
117
|
+
|
118
|
+
# If true, displays a drop shadow for the chart
|
119
|
+
attr_accessor :show_shadow
|
120
|
+
# Sets the offset of the shadow from the pie chart
|
121
|
+
attr_accessor :shadow_offset
|
122
|
+
# If true, display the data labels on the chart
|
123
|
+
attr_accessor :show_data_labels
|
124
|
+
# If true, display the actual field values in the data labels
|
125
|
+
attr_accessor :show_actual_values
|
126
|
+
# If true, display the percentage value of each pie wedge in the data
|
127
|
+
# labels
|
128
|
+
attr_accessor :show_percent
|
129
|
+
# If true, display the labels in the key
|
130
|
+
attr_accessor :show_key_data_labels
|
131
|
+
# If true, display the actual value of the field in the key
|
132
|
+
attr_accessor :show_key_actual_values
|
133
|
+
# If true, display the percentage value of the wedges in the key
|
134
|
+
attr_accessor :show_key_percent
|
135
|
+
# If true, "explode" the pie (put space between the wedges)
|
136
|
+
attr_accessor :expanded
|
137
|
+
# If true, expand the largest pie wedge
|
138
|
+
attr_accessor :expand_greatest
|
139
|
+
# The amount of space between expanded wedges
|
140
|
+
attr_accessor :expand_gap
|
141
|
+
# The font size of the data point labels
|
142
|
+
attr_accessor :datapoint_font_size
|
143
|
+
|
144
|
+
|
145
|
+
protected
|
146
|
+
|
147
|
+
def add_defs defs
|
148
|
+
gradient = defs.add_element( "filter", {
|
149
|
+
"id"=>"dropshadow",
|
150
|
+
"width" => "1.2",
|
151
|
+
"height" => "1.2",
|
152
|
+
} )
|
153
|
+
gradient.add_element( "feGaussianBlur", {
|
154
|
+
"stdDeviation" => "4",
|
155
|
+
"result" => "blur"
|
156
|
+
})
|
157
|
+
end
|
158
|
+
|
159
|
+
# We don't need the graph
|
160
|
+
def draw_graph
|
161
|
+
end
|
162
|
+
|
163
|
+
def get_y_labels
|
164
|
+
[""]
|
165
|
+
end
|
166
|
+
|
167
|
+
def get_x_labels
|
168
|
+
[""]
|
169
|
+
end
|
170
|
+
|
171
|
+
def keys
|
172
|
+
total = 0
|
173
|
+
max_value = 0
|
174
|
+
@data.each {|x| total += x }
|
175
|
+
percent_scale = 100.0 / total
|
176
|
+
count = -1
|
177
|
+
a = @config[:fields].collect{ |x|
|
178
|
+
count += 1
|
179
|
+
v = @data[count]
|
180
|
+
perc = show_key_percent ? " "+(v * percent_scale).round.to_s+"%" : ""
|
181
|
+
x + " [" + v.to_s + "]" + perc
|
182
|
+
}
|
183
|
+
end
|
184
|
+
|
185
|
+
RADIANS = Math::PI/180
|
186
|
+
|
187
|
+
def draw_data
|
188
|
+
@graph = @root.add_element( "g" )
|
189
|
+
background = @graph.add_element("g")
|
190
|
+
midground = @graph.add_element("g")
|
191
|
+
|
192
|
+
diameter = @graph_height > @graph_width ? @graph_width : @graph_height
|
193
|
+
diameter -= expand_gap if expanded or expand_greatest
|
194
|
+
diameter -= datapoint_font_size if show_data_labels
|
195
|
+
diameter -= 10 if show_shadow
|
196
|
+
radius = diameter / 2.0
|
197
|
+
|
198
|
+
xoff = (width - diameter) / 2
|
199
|
+
yoff = (height - @border_bottom - diameter)
|
200
|
+
yoff -= 10 if show_shadow
|
201
|
+
@graph.attributes['transform'] = "translate( #{xoff} #{yoff} )"
|
202
|
+
|
203
|
+
wedge_text_pad = 5
|
204
|
+
wedge_text_pad = 20 if show_percent and show_data_labels
|
205
|
+
|
206
|
+
total = 0
|
207
|
+
max_value = 0
|
208
|
+
@data.each {|x|
|
209
|
+
max_value = max_value < x ? x : max_value
|
210
|
+
total += x
|
211
|
+
}
|
212
|
+
percent_scale = 100.0 / total
|
213
|
+
|
214
|
+
prev_percent = 0
|
215
|
+
rad_mult = 3.6 * RADIANS
|
216
|
+
@config[:fields].each_index { |count|
|
217
|
+
value = @data[count]
|
218
|
+
percent = percent_scale * value
|
219
|
+
|
220
|
+
radians = prev_percent * rad_mult
|
221
|
+
x_start = radius+(Math.sin(radians) * radius)
|
222
|
+
y_start = radius-(Math.cos(radians) * radius)
|
223
|
+
radians = (prev_percent+percent) * rad_mult
|
224
|
+
x_end = radius+(Math.sin(radians) * radius)
|
225
|
+
x_end -= 0.00001 if @data.length == 1
|
226
|
+
y_end = radius-(Math.cos(radians) * radius)
|
227
|
+
path = "M#{radius},#{radius} L#{x_start},#{y_start} "+
|
228
|
+
"A#{radius},#{radius} "+
|
229
|
+
"0, #{percent >= 50 ? '1' : '0'},1, "+
|
230
|
+
"#{x_end} #{y_end} Z"
|
231
|
+
|
232
|
+
|
233
|
+
wedge = @foreground.add_element( "path", {
|
234
|
+
"d" => path,
|
235
|
+
"class" => "fill#{count+1}"
|
236
|
+
})
|
237
|
+
|
238
|
+
translate = nil
|
239
|
+
tx = 0
|
240
|
+
ty = 0
|
241
|
+
half_percent = prev_percent + percent / 2
|
242
|
+
radians = half_percent * rad_mult
|
243
|
+
|
244
|
+
if show_shadow
|
245
|
+
shadow = background.add_element( "path", {
|
246
|
+
"d" => path,
|
247
|
+
"filter" => "url(#dropshadow)",
|
248
|
+
"style" => "fill: #ccc; stroke: none;"
|
249
|
+
})
|
250
|
+
clear = midground.add_element( "path", {
|
251
|
+
"d" => path,
|
252
|
+
"style" => "fill: #fff; stroke: none;"
|
253
|
+
})
|
254
|
+
end
|
255
|
+
|
256
|
+
if expanded or (expand_greatest && value == max_value)
|
257
|
+
tx = (Math.sin(radians) * expand_gap)
|
258
|
+
ty = -(Math.cos(radians) * expand_gap)
|
259
|
+
translate = "translate( #{tx} #{ty} )"
|
260
|
+
wedge.attributes["transform"] = translate
|
261
|
+
clear.attributes["transform"] = translate if clear
|
262
|
+
end
|
263
|
+
|
264
|
+
if show_shadow
|
265
|
+
shadow.attributes["transform"] =
|
266
|
+
"translate( #{tx+shadow_offset} #{ty+shadow_offset} )"
|
267
|
+
end
|
268
|
+
|
269
|
+
if show_data_labels and value != 0
|
270
|
+
label = ""
|
271
|
+
label += @config[:fields][count] if show_key_data_labels
|
272
|
+
label += " ["+value.to_s+"]" if show_actual_values
|
273
|
+
label += " "+percent.round.to_s+"%" if show_percent
|
274
|
+
|
275
|
+
msr = Math.sin(radians)
|
276
|
+
mcr = Math.cos(radians)
|
277
|
+
tx = radius + (msr * radius)
|
278
|
+
ty = radius -(mcr * radius)
|
279
|
+
|
280
|
+
if expanded or (expand_greatest && value == max_value)
|
281
|
+
tx += (msr * expand_gap)
|
282
|
+
ty -= (mcr * expand_gap)
|
283
|
+
end
|
284
|
+
@foreground.add_element( "text", {
|
285
|
+
"x" => tx.to_s,
|
286
|
+
"y" => ty.to_s,
|
287
|
+
"class" => "dataPointLabel",
|
288
|
+
"style" => "stroke: #fff; stroke-width: 2;"
|
289
|
+
}).text = label.to_s
|
290
|
+
@foreground.add_element( "text", {
|
291
|
+
"x" => tx.to_s,
|
292
|
+
"y" => ty.to_s,
|
293
|
+
"class" => "dataPointLabel",
|
294
|
+
}).text = label.to_s
|
295
|
+
end
|
296
|
+
|
297
|
+
prev_percent += percent
|
298
|
+
}
|
299
|
+
end
|
300
|
+
|
301
|
+
|
302
|
+
def round val, to
|
303
|
+
up = 10**to.to_f
|
304
|
+
(val * up).to_i / up
|
305
|
+
end
|
306
|
+
|
307
|
+
|
308
|
+
def get_css(keys = 255)
|
309
|
+
return <<EOL
|
310
|
+
.dataPointLabel{
|
311
|
+
fill: #000000;
|
312
|
+
text-anchor:middle;
|
313
|
+
font-size: #{datapoint_font_size}px;
|
314
|
+
font-family: "Arial", sans-serif;
|
315
|
+
font-weight: normal;
|
316
|
+
}
|
317
|
+
|
318
|
+
/* key - MUST match fill styles */
|
319
|
+
#{
|
320
|
+
str = ""
|
321
|
+
|
322
|
+
sp = (keys ** (1 / 3.0)).round
|
323
|
+
i = 0
|
324
|
+
sp.times do |r|
|
325
|
+
sp.times do |g|
|
326
|
+
sp.times do |b|
|
327
|
+
str += %@\n.key#{i},.fill#{i} {
|
328
|
+
fill: rgb(#{rand(255) % 255}, #{rand(255) % 255}, #{rand(255) % 255});
|
329
|
+
fill-opacity: 0.7;
|
330
|
+
stroke: none;
|
331
|
+
stroke-width: 1px;
|
332
|
+
}
|
333
|
+
@
|
334
|
+
|
335
|
+
i += 1
|
336
|
+
end
|
337
|
+
end
|
338
|
+
end
|
339
|
+
|
340
|
+
str
|
341
|
+
}
|
342
|
+
|
343
|
+
EOL
|
344
|
+
end
|
345
|
+
end
|
346
|
+
end
|
347
|
+
end
|