svg-graph-test 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,442 @@
1
+ require_relative '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
+ # https://github.com/lumean/svg-graph2/blob/master/examples/pie.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
+ #
112
+ # nil values in the array will be replaced by 0
113
+ #
114
+ # graph.add_data( { :data => [3,nil,nil,2] } ) is equivalent to graph.add_data( { :data => [3,0,0,2] } )
115
+ #
116
+ def add_data arg
117
+ arg[:data].each_index {|idx|
118
+ @data[idx] = 0 unless @data[idx]
119
+ if !arg[:data][idx].nil?
120
+ @data[idx] += arg[:data][idx]
121
+ end
122
+ }
123
+ end
124
+
125
+ # If true, displays a drop shadow for the chart
126
+ attr_accessor :show_shadow
127
+ # Sets the offset of the shadow from the pie chart
128
+ attr_accessor :shadow_offset
129
+ # If true, display the data labels on the chart
130
+ attr_accessor :show_data_labels
131
+ # If true, display the actual field values in the data labels
132
+ attr_accessor :show_actual_values
133
+ # If true, display the percentage value of each pie wedge in the data
134
+ # labels
135
+ attr_accessor :show_percent
136
+ # If true, display the labels in the key
137
+ attr_accessor :show_key_data_labels
138
+ # If true, display the actual value of the field in the key
139
+ attr_accessor :show_key_actual_values
140
+ # If true, display the percentage value of the wedges in the key
141
+ attr_accessor :show_key_percent
142
+ # If true, "explode" the pie (put space between the wedges)
143
+ attr_accessor :expanded
144
+ # If true, expand the largest pie wedge
145
+ attr_accessor :expand_greatest
146
+ # The amount of space between expanded wedges
147
+ attr_accessor :expand_gap
148
+ # The font size of the data point labels
149
+ attr_accessor :datapoint_font_size
150
+
151
+
152
+ protected
153
+
154
+ def add_defs defs
155
+ gradient = defs.add_element( "filter", {
156
+ "id"=>"dropshadow",
157
+ "width" => "1.2",
158
+ "height" => "1.2",
159
+ } )
160
+ gradient.add_element( "feGaussianBlur", {
161
+ "stdDeviation" => "4",
162
+ "result" => "blur"
163
+ })
164
+ end
165
+
166
+ # We don't need the graph
167
+ def draw_graph
168
+ end
169
+
170
+ # We don't have axis labels
171
+ def get_y_labels
172
+ [""]
173
+ end
174
+
175
+ # We don't have axis labels
176
+ def get_x_labels
177
+ [""]
178
+ end
179
+
180
+ def keys
181
+ total = 0
182
+ #max_value = 0
183
+ @data.each {|x| total += x }
184
+ percent_scale = 100.0 / total
185
+ count = -1
186
+ @config[:fields].collect{ |x|
187
+ count += 1
188
+ v = @data[count]
189
+ perc = show_key_percent ? " "+(v * percent_scale).round.to_s+"%" : ""
190
+ val = show_key_actual_values ? " [" + v.to_s + "]" : ""
191
+ x + val + perc
192
+ }
193
+ end
194
+
195
+ RADIANS = Math::PI/180
196
+
197
+ def draw_data
198
+ @graph = @root.add_element( "g" )
199
+ background = @graph.add_element("g")
200
+ midground = @graph.add_element("g")
201
+
202
+ diameter = @graph_height > @graph_width ? @graph_width : @graph_height
203
+ diameter -= expand_gap if expanded or expand_greatest
204
+ diameter -= datapoint_font_size if show_data_labels
205
+ diameter -= 10 if show_shadow
206
+ radius = diameter / 2.0
207
+
208
+ xoff = (@graph_width - diameter) / 2
209
+ yoff = (height - @border_bottom - diameter)
210
+ yoff -= 10 if show_shadow
211
+ @graph.attributes['transform'] = "translate( #{xoff} #{yoff} )"
212
+
213
+ wedge_text_pad = 5
214
+ wedge_text_pad = 20 if show_percent and show_data_labels
215
+
216
+ total = 0
217
+ max_value = 0
218
+ @data.each {|x|
219
+ max_value = max_value < x ? x : max_value
220
+ total += x
221
+ }
222
+
223
+ prev_percent = 0
224
+ rad_mult = 3.6 * RADIANS
225
+ @config[:fields].each_index { |count|
226
+ value = @data[count].to_f
227
+ percent = total.zero? ? 0.0 : 100.0 * value / total
228
+ radians = prev_percent * rad_mult
229
+
230
+ if percent.rationalize(0.001) == 100.0
231
+ @foreground.add_element( "circle", {
232
+ "cx" => radius.to_s,
233
+ "cy" => radius.to_s,
234
+ "r" => radius.to_s,
235
+ "class" => "fill#{count+1}"
236
+ })
237
+
238
+ if show_shadow
239
+ shadow = background.add_element( "circle", {
240
+ "cx" => radius.to_s,
241
+ "cy" => radius.to_s,
242
+ "r" => radius.to_s,
243
+ "filter" => "url(#dropshadow)",
244
+ "style" => "fill: #ccc; stroke: none;"
245
+ })
246
+ clear = midground.add_element( "circle", {
247
+ "cx" => radius.to_s,
248
+ "cy" => radius.to_s,
249
+ "r" => radius.to_s,
250
+ "style" => "fill: #fff; stroke: none;"
251
+ })
252
+ shadow.attributes["transform"] =
253
+ "translate( #{shadow_offset} #{shadow_offset} )"
254
+ end
255
+ else
256
+ x_start = radius+(Math.sin(radians) * radius)
257
+ y_start = radius-(Math.cos(radians) * radius)
258
+ radians = (prev_percent+percent) * rad_mult
259
+ x_end = radius+(Math.sin(radians) * radius)
260
+ x_end -= 0.00001 if @data.length == 1
261
+ y_end = radius-(Math.cos(radians) * radius)
262
+ path = "M#{radius},#{radius} L#{x_start},#{y_start} "+
263
+ "A#{radius},#{radius} "+
264
+ "0, #{percent >= 50 ? '1' : '0'},1, "+
265
+ "#{x_end} #{y_end} Z"
266
+
267
+
268
+ wedge = @foreground.add_element( "path", {
269
+ "d" => path,
270
+ "class" => "fill#{count+1}"
271
+ })
272
+
273
+ translate = nil
274
+ tx = 0
275
+ ty = 0
276
+ half_percent = prev_percent + percent / 2
277
+ radians = half_percent * rad_mult
278
+
279
+ if show_shadow
280
+ shadow = background.add_element( "path", {
281
+ "d" => path,
282
+ "filter" => "url(#dropshadow)",
283
+ "style" => "fill: #ccc; stroke: none;"
284
+ })
285
+ clear = midground.add_element( "path", {
286
+ "d" => path,
287
+ "style" => "fill: #fff; stroke: none;"
288
+ })
289
+ end
290
+
291
+ if expanded or (expand_greatest && value == max_value)
292
+ tx = (Math.sin(radians) * expand_gap)
293
+ ty = -(Math.cos(radians) * expand_gap)
294
+ translate = "translate( #{tx} #{ty} )"
295
+ wedge.attributes["transform"] = translate
296
+ clear.attributes["transform"] = translate if clear
297
+ end
298
+
299
+ if show_shadow
300
+ shadow.attributes["transform"] =
301
+ "translate( #{tx+shadow_offset} #{ty+shadow_offset} )"
302
+ end
303
+
304
+ prev_percent += percent
305
+ end
306
+
307
+ if show_data_labels and value != 0
308
+ label = ""
309
+ label += @config[:fields][count] if show_key_data_labels
310
+ label += " ["+value.to_s+"]" if show_actual_values
311
+ label += " "+percent.round.to_s+"%" if show_percent
312
+
313
+ msr = Math.sin(radians)
314
+ mcr = Math.cos(radians)
315
+ tx = radius + (msr * radius)
316
+ ty = radius - (mcr * radius)
317
+
318
+ if expanded or (expand_greatest && value == max_value)
319
+ tx += (msr * expand_gap)
320
+ ty -= (mcr * expand_gap)
321
+ end
322
+ @foreground.add_element( "text", {
323
+ "x" => tx.to_s,
324
+ "y" => ty.to_s,
325
+ "class" => "dataPointLabel",
326
+ "style" => "stroke: #fff; stroke-width: 2;"
327
+ }).text = label.to_s
328
+ @foreground.add_element( "text", {
329
+ "x" => tx.to_s,
330
+ "y" => ty.to_s,
331
+ "class" => "dataPointLabel",
332
+ }).text = label.to_s
333
+ end
334
+ }
335
+ end
336
+
337
+
338
+ def round val, to
339
+ up = 10**to.to_f
340
+ (val * up).to_i / up
341
+ end
342
+
343
+
344
+ def get_css
345
+ return <<EOL
346
+ .dataPointLabel, .dataPointLabelBackground, .dataPointPopup{
347
+ fill: #000000;
348
+ text-anchor:middle;
349
+ font-size: #{datapoint_font_size}px;
350
+ font-family: "Arial", sans-serif;
351
+ font-weight: normal;
352
+ }
353
+
354
+ .dataPointLabelBackground{
355
+ stroke: #ffffff;
356
+ stroke-width: 2;
357
+ }
358
+
359
+ .dataPointPopup{
360
+ fill: #000000;
361
+ visibility: hidden;
362
+ stroke-width: 2;
363
+ }
364
+
365
+ /* key - MUST match fill styles */
366
+ .key1,.fill1{
367
+ fill: #ff0000;
368
+ fill-opacity: 0.7;
369
+ stroke: none;
370
+ stroke-width: 1px;
371
+ }
372
+ .key2,.fill2{
373
+ fill: #0000ff;
374
+ fill-opacity: 0.7;
375
+ stroke: none;
376
+ stroke-width: 1px;
377
+ }
378
+ .key3,.fill3{
379
+ fill-opacity: 0.7;
380
+ fill: #00ff00;
381
+ stroke: none;
382
+ stroke-width: 1px;
383
+ }
384
+ .key4,.fill4{
385
+ fill-opacity: 0.7;
386
+ fill: #ffcc00;
387
+ stroke: none;
388
+ stroke-width: 1px;
389
+ }
390
+ .key5,.fill5{
391
+ fill-opacity: 0.7;
392
+ fill: #00ccff;
393
+ stroke: none;
394
+ stroke-width: 1px;
395
+ }
396
+ .key6,.fill6{
397
+ fill-opacity: 0.7;
398
+ fill: #ff00ff;
399
+ stroke: none;
400
+ stroke-width: 1px;
401
+ }
402
+ .key7,.fill7{
403
+ fill-opacity: 0.7;
404
+ fill: #00ff99;
405
+ stroke: none;
406
+ stroke-width: 1px;
407
+ }
408
+ .key8,.fill8{
409
+ fill-opacity: 0.7;
410
+ fill: #ffff00;
411
+ stroke: none;
412
+ stroke-width: 1px;
413
+ }
414
+ .key9,.fill9{
415
+ fill-opacity: 0.7;
416
+ fill: #cc6666;
417
+ stroke: none;
418
+ stroke-width: 1px;
419
+ }
420
+ .key10,.fill10{
421
+ fill-opacity: 0.7;
422
+ fill: #663399;
423
+ stroke: none;
424
+ stroke-width: 1px;
425
+ }
426
+ .key11,.fill11{
427
+ fill-opacity: 0.7;
428
+ fill: #339900;
429
+ stroke: none;
430
+ stroke-width: 1px;
431
+ }
432
+ .key12,.fill12{
433
+ fill-opacity: 0.7;
434
+ fill: #9966FF;
435
+ stroke: none;
436
+ stroke-width: 1px;
437
+ }
438
+ EOL
439
+ end
440
+ end
441
+ end
442
+ end