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