svg-graph 2.1.1 → 2.2.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 +5 -5
- data/History.txt +35 -1
- data/{README.markdown → README.md} +27 -6
- data/Rakefile +16 -5
- data/lib/SVG/Graph/Bar.rb +10 -3
- data/lib/SVG/Graph/BarBase.rb +21 -17
- data/lib/SVG/Graph/BarHorizontal.rb +12 -5
- data/lib/SVG/Graph/C3js.rb +274 -0
- data/lib/SVG/Graph/DataPoint.rb +28 -16
- data/lib/SVG/Graph/Graph.rb +211 -104
- data/lib/SVG/Graph/Line.rb +12 -7
- data/lib/SVG/Graph/Pie.rb +52 -41
- data/lib/SVG/Graph/Plot.rb +105 -94
- data/lib/SVG/Graph/TimeSeries.rb +27 -27
- data/lib/svggraph.rb +4 -2
- data/test/test_svg_graph.rb +7 -7
- metadata +14 -14
data/lib/SVG/Graph/Line.rb
CHANGED
@@ -249,17 +249,22 @@ module SVG
|
|
249
249
|
next if cum_sum[i].nil?
|
250
250
|
c = calc_coords(i, cum_sum[i], fieldwidth, fieldheight)
|
251
251
|
if show_data_points
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
252
|
+
shape_selection_string = data[:description][i].to_s
|
253
|
+
if !data[:shape][i].nil?
|
254
|
+
shape_selection_string = data[:shape][i].to_s
|
255
|
+
end
|
256
|
+
DataPoint.new(c[:x], c[:y], line).shape(shape_selection_string).each{|s|
|
257
|
+
@graph.add_element( *s )
|
258
|
+
}
|
258
259
|
end
|
259
260
|
|
260
261
|
make_datapoint_text( c[:x], c[:y] - font_size/2, cum_sum[i] + minvalue)
|
261
262
|
# number format shall not apply to popup (use .to_s conversion)
|
262
|
-
|
263
|
+
descr = ""
|
264
|
+
if !data[:description][i].to_s.empty?
|
265
|
+
descr = ", #{data[:description][i].to_s}"
|
266
|
+
end
|
267
|
+
add_popup(c[:x], c[:y], (cum_sum[i] + minvalue).to_s + descr, "", data[:url][i].to_s)
|
263
268
|
end
|
264
269
|
end
|
265
270
|
|
data/lib/SVG/Graph/Pie.rb
CHANGED
@@ -3,30 +3,30 @@ require_relative 'Graph'
|
|
3
3
|
module SVG
|
4
4
|
module Graph
|
5
5
|
# === Create presentation quality SVG pie graphs easily
|
6
|
-
#
|
6
|
+
#
|
7
7
|
# == Synopsis
|
8
|
-
#
|
8
|
+
#
|
9
9
|
# require 'SVG/Graph/Pie'
|
10
|
-
#
|
10
|
+
#
|
11
11
|
# fields = %w(Jan Feb Mar)
|
12
12
|
# data_sales_02 = [12, 45, 21]
|
13
|
-
#
|
13
|
+
#
|
14
14
|
# graph = SVG::Graph::Pie.new({
|
15
15
|
# :height => 500,
|
16
16
|
# :width => 300,
|
17
17
|
# :fields => fields,
|
18
18
|
# })
|
19
|
-
#
|
19
|
+
#
|
20
20
|
# graph.add_data({
|
21
21
|
# :data => data_sales_02,
|
22
22
|
# :title => 'Sales 2002',
|
23
23
|
# })
|
24
|
-
#
|
24
|
+
#
|
25
25
|
# print "Content-type: image/svg+xml\r\n\r\n"
|
26
26
|
# print graph.burn();
|
27
|
-
#
|
27
|
+
#
|
28
28
|
# == Description
|
29
|
-
#
|
29
|
+
#
|
30
30
|
# This object aims to allow you to easily create high quality
|
31
31
|
# SVG pie graphs. You can either use the default style sheet
|
32
32
|
# or supply your own. Either way there are many options which can
|
@@ -35,9 +35,9 @@ module SVG
|
|
35
35
|
# title, subtitle etc.
|
36
36
|
#
|
37
37
|
# = Examples
|
38
|
-
#
|
38
|
+
#
|
39
39
|
# http://www.germane-software/repositories/public/SVG/test/single.rb
|
40
|
-
#
|
40
|
+
#
|
41
41
|
# == See also
|
42
42
|
#
|
43
43
|
# * SVG::Graph::Graph
|
@@ -73,8 +73,8 @@ module SVG
|
|
73
73
|
def set_defaults
|
74
74
|
init_with(
|
75
75
|
:show_shadow => true,
|
76
|
-
:shadow_offset => 10,
|
77
|
-
|
76
|
+
:shadow_offset => 10,
|
77
|
+
|
78
78
|
:show_data_labels => false,
|
79
79
|
:show_actual_values => false,
|
80
80
|
:show_percent => true,
|
@@ -82,11 +82,11 @@ module SVG
|
|
82
82
|
:show_key_data_labels => true,
|
83
83
|
:show_key_actual_values => true,
|
84
84
|
:show_key_percent => false,
|
85
|
-
|
85
|
+
|
86
86
|
:expanded => false,
|
87
87
|
:expand_greatest => false,
|
88
88
|
:expand_gap => 10,
|
89
|
-
|
89
|
+
|
90
90
|
:show_x_labels => false,
|
91
91
|
:show_y_labels => false,
|
92
92
|
:datapoint_font_size => 12
|
@@ -110,7 +110,7 @@ module SVG
|
|
110
110
|
# graph.add_data( { :data => [3,5,8,13] } )
|
111
111
|
#
|
112
112
|
# nil values in the array will be replaced by 0
|
113
|
-
#
|
113
|
+
#
|
114
114
|
# graph.add_data( { :data => [3,nil,nil,2] } ) is equivalent to graph.add_data( { :data => [3,0,0,2] } )
|
115
115
|
#
|
116
116
|
def add_data arg
|
@@ -123,28 +123,28 @@ module SVG
|
|
123
123
|
end
|
124
124
|
|
125
125
|
# If true, displays a drop shadow for the chart
|
126
|
-
attr_accessor :show_shadow
|
126
|
+
attr_accessor :show_shadow
|
127
127
|
# Sets the offset of the shadow from the pie chart
|
128
128
|
attr_accessor :shadow_offset
|
129
129
|
# If true, display the data labels on the chart
|
130
|
-
attr_accessor :show_data_labels
|
130
|
+
attr_accessor :show_data_labels
|
131
131
|
# If true, display the actual field values in the data labels
|
132
|
-
attr_accessor :show_actual_values
|
132
|
+
attr_accessor :show_actual_values
|
133
133
|
# If true, display the percentage value of each pie wedge in the data
|
134
134
|
# labels
|
135
135
|
attr_accessor :show_percent
|
136
136
|
# If true, display the labels in the key
|
137
|
-
attr_accessor :show_key_data_labels
|
137
|
+
attr_accessor :show_key_data_labels
|
138
138
|
# If true, display the actual value of the field in the key
|
139
|
-
attr_accessor :show_key_actual_values
|
139
|
+
attr_accessor :show_key_actual_values
|
140
140
|
# If true, display the percentage value of the wedges in the key
|
141
141
|
attr_accessor :show_key_percent
|
142
142
|
# If true, "explode" the pie (put space between the wedges)
|
143
|
-
attr_accessor :expanded
|
143
|
+
attr_accessor :expanded
|
144
144
|
# If true, expand the largest pie wedge
|
145
|
-
attr_accessor :expand_greatest
|
145
|
+
attr_accessor :expand_greatest
|
146
146
|
# The amount of space between expanded wedges
|
147
|
-
attr_accessor :expand_gap
|
147
|
+
attr_accessor :expand_gap
|
148
148
|
# The font size of the data point labels
|
149
149
|
attr_accessor :datapoint_font_size
|
150
150
|
|
@@ -187,7 +187,8 @@ module SVG
|
|
187
187
|
count += 1
|
188
188
|
v = @data[count]
|
189
189
|
perc = show_key_percent ? " "+(v * percent_scale).round.to_s+"%" : ""
|
190
|
-
|
190
|
+
val = show_key_actual_values ? " [" + v.to_s + "]" : ""
|
191
|
+
x + val + perc
|
191
192
|
}
|
192
193
|
end
|
193
194
|
|
@@ -218,21 +219,20 @@ module SVG
|
|
218
219
|
max_value = max_value < x ? x : max_value
|
219
220
|
total += x
|
220
221
|
}
|
221
|
-
percent_scale = 100.0 / total
|
222
222
|
|
223
223
|
prev_percent = 0
|
224
224
|
rad_mult = 3.6 * RADIANS
|
225
225
|
@config[:fields].each_index { |count|
|
226
226
|
value = @data[count].to_f
|
227
|
-
percent =
|
227
|
+
percent = total.zero? ? 0.0 : 100.0 * value / total
|
228
228
|
radians = prev_percent * rad_mult
|
229
229
|
|
230
|
-
if percent == 100.0
|
230
|
+
if percent.rationalize(0.001) == 100.0
|
231
231
|
@foreground.add_element( "circle", {
|
232
232
|
"cx" => radius.to_s,
|
233
233
|
"cy" => radius.to_s,
|
234
234
|
"r" => radius.to_s,
|
235
|
-
"class" => "
|
235
|
+
"class" => "fill#{count+1}"
|
236
236
|
})
|
237
237
|
|
238
238
|
if show_shadow
|
@@ -343,7 +343,7 @@ module SVG
|
|
343
343
|
|
344
344
|
def get_css
|
345
345
|
return <<EOL
|
346
|
-
.dataPointLabel{
|
346
|
+
.dataPointLabel, .dataPointLabelBackground, .dataPointPopup{
|
347
347
|
fill: #000000;
|
348
348
|
text-anchor:middle;
|
349
349
|
font-size: #{datapoint_font_size}px;
|
@@ -351,78 +351,89 @@ module SVG
|
|
351
351
|
font-weight: normal;
|
352
352
|
}
|
353
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
|
+
|
354
365
|
/* key - MUST match fill styles */
|
355
366
|
.key1,.fill1{
|
356
367
|
fill: #ff0000;
|
357
368
|
fill-opacity: 0.7;
|
358
369
|
stroke: none;
|
359
|
-
stroke-width: 1px;
|
370
|
+
stroke-width: 1px;
|
360
371
|
}
|
361
372
|
.key2,.fill2{
|
362
373
|
fill: #0000ff;
|
363
374
|
fill-opacity: 0.7;
|
364
375
|
stroke: none;
|
365
|
-
stroke-width: 1px;
|
376
|
+
stroke-width: 1px;
|
366
377
|
}
|
367
378
|
.key3,.fill3{
|
368
379
|
fill-opacity: 0.7;
|
369
380
|
fill: #00ff00;
|
370
381
|
stroke: none;
|
371
|
-
stroke-width: 1px;
|
382
|
+
stroke-width: 1px;
|
372
383
|
}
|
373
384
|
.key4,.fill4{
|
374
385
|
fill-opacity: 0.7;
|
375
386
|
fill: #ffcc00;
|
376
387
|
stroke: none;
|
377
|
-
stroke-width: 1px;
|
388
|
+
stroke-width: 1px;
|
378
389
|
}
|
379
390
|
.key5,.fill5{
|
380
391
|
fill-opacity: 0.7;
|
381
392
|
fill: #00ccff;
|
382
393
|
stroke: none;
|
383
|
-
stroke-width: 1px;
|
394
|
+
stroke-width: 1px;
|
384
395
|
}
|
385
396
|
.key6,.fill6{
|
386
397
|
fill-opacity: 0.7;
|
387
398
|
fill: #ff00ff;
|
388
399
|
stroke: none;
|
389
|
-
stroke-width: 1px;
|
400
|
+
stroke-width: 1px;
|
390
401
|
}
|
391
402
|
.key7,.fill7{
|
392
403
|
fill-opacity: 0.7;
|
393
404
|
fill: #00ff99;
|
394
405
|
stroke: none;
|
395
|
-
stroke-width: 1px;
|
406
|
+
stroke-width: 1px;
|
396
407
|
}
|
397
408
|
.key8,.fill8{
|
398
409
|
fill-opacity: 0.7;
|
399
410
|
fill: #ffff00;
|
400
411
|
stroke: none;
|
401
|
-
stroke-width: 1px;
|
412
|
+
stroke-width: 1px;
|
402
413
|
}
|
403
414
|
.key9,.fill9{
|
404
415
|
fill-opacity: 0.7;
|
405
416
|
fill: #cc6666;
|
406
417
|
stroke: none;
|
407
|
-
stroke-width: 1px;
|
418
|
+
stroke-width: 1px;
|
408
419
|
}
|
409
420
|
.key10,.fill10{
|
410
421
|
fill-opacity: 0.7;
|
411
422
|
fill: #663399;
|
412
423
|
stroke: none;
|
413
|
-
stroke-width: 1px;
|
424
|
+
stroke-width: 1px;
|
414
425
|
}
|
415
426
|
.key11,.fill11{
|
416
427
|
fill-opacity: 0.7;
|
417
428
|
fill: #339900;
|
418
429
|
stroke: none;
|
419
|
-
stroke-width: 1px;
|
430
|
+
stroke-width: 1px;
|
420
431
|
}
|
421
432
|
.key12,.fill12{
|
422
433
|
fill-opacity: 0.7;
|
423
434
|
fill: #9966FF;
|
424
435
|
stroke: none;
|
425
|
-
stroke-width: 1px;
|
436
|
+
stroke-width: 1px;
|
426
437
|
}
|
427
438
|
EOL
|
428
439
|
end
|
data/lib/SVG/Graph/Plot.rb
CHANGED
@@ -4,48 +4,48 @@ require_relative 'DataPoint'
|
|
4
4
|
module SVG
|
5
5
|
module Graph
|
6
6
|
# === For creating SVG plots of scalar data
|
7
|
-
#
|
7
|
+
#
|
8
8
|
# = Synopsis
|
9
|
-
#
|
9
|
+
#
|
10
10
|
# require 'SVG/Graph/Plot'
|
11
|
-
#
|
11
|
+
#
|
12
12
|
# # Data sets are x,y pairs
|
13
13
|
# # Note that multiple data sets can differ in length, and that the
|
14
14
|
# # data in the datasets needn't be in order; they will be ordered
|
15
15
|
# # by the plot along the X-axis.
|
16
16
|
# projection = [
|
17
17
|
# 6, 11, 0, 5, 18, 7, 1, 11, 13, 9, 1, 2, 19, 0, 3, 13,
|
18
|
-
# 7, 9
|
18
|
+
# 7, 9
|
19
19
|
# ]
|
20
20
|
# actual = [
|
21
|
-
# 0, 18, 8, 15, 9, 4, 18, 14, 10, 2, 11, 6, 14, 12,
|
21
|
+
# 0, 18, 8, 15, 9, 4, 18, 14, 10, 2, 11, 6, 14, 12,
|
22
22
|
# 15, 6, 4, 17, 2, 12
|
23
23
|
# ]
|
24
|
-
#
|
24
|
+
#
|
25
25
|
# graph = SVG::Graph::Plot.new({
|
26
26
|
# :height => 500,
|
27
27
|
# :width => 300,
|
28
28
|
# :key => true,
|
29
29
|
# :scale_x_integers => true,
|
30
|
-
# :
|
30
|
+
# :scale_y_integers => true,
|
31
31
|
# })
|
32
|
-
#
|
32
|
+
#
|
33
33
|
# graph.add_data({
|
34
34
|
# :data => projection
|
35
35
|
# :title => 'Projected',
|
36
36
|
# })
|
37
|
-
#
|
37
|
+
#
|
38
38
|
# graph.add_data({
|
39
39
|
# :data => actual,
|
40
40
|
# :title => 'Actual',
|
41
41
|
# })
|
42
|
-
#
|
42
|
+
#
|
43
43
|
# print graph.burn()
|
44
|
-
#
|
44
|
+
#
|
45
45
|
# = Description
|
46
|
-
#
|
46
|
+
#
|
47
47
|
# Produces a graph of scalar data.
|
48
|
-
#
|
48
|
+
#
|
49
49
|
# This object aims to allow you to easily create high quality
|
50
50
|
# SVG[http://www.w3c.org/tr/svg] scalar plots. You can either use the
|
51
51
|
# default style sheet or supply your own. Either way there are many options
|
@@ -54,15 +54,15 @@ module SVG
|
|
54
54
|
# subtitle etc.
|
55
55
|
#
|
56
56
|
# = Examples
|
57
|
-
#
|
57
|
+
#
|
58
58
|
# http://www.germane-software/repositories/public/SVG/test/plot.rb
|
59
|
-
#
|
59
|
+
#
|
60
60
|
# = Notes
|
61
|
-
#
|
62
|
-
# The default stylesheet handles upto
|
61
|
+
#
|
62
|
+
# The default stylesheet handles upto 12 data sets, if you
|
63
63
|
# use more you must create your own stylesheet and add the
|
64
64
|
# additional settings for the extra data sets. You will know
|
65
|
-
# if you go over
|
65
|
+
# if you go over 12 data sets as they will have no style and
|
66
66
|
# be in black.
|
67
67
|
#
|
68
68
|
# Unlike the other types of charts, data sets must contain x,y pairs:
|
@@ -72,9 +72,9 @@ module SVG
|
|
72
72
|
# Additional possible notation
|
73
73
|
# [ [1,2], 5,6] # A data set with 2 points: (1,2) and (5,6), mixed notation
|
74
74
|
# [ [1,2], [5,6]] # A data set with 2 points: (1,2) and (5,6), nested array
|
75
|
-
#
|
75
|
+
#
|
76
76
|
# = See also
|
77
|
-
#
|
77
|
+
#
|
78
78
|
# * SVG::Graph::Graph
|
79
79
|
# * SVG::Graph::BarHorizontal
|
80
80
|
# * SVG::Graph::Bar
|
@@ -105,7 +105,7 @@ module SVG
|
|
105
105
|
:show_lines => true,
|
106
106
|
:round_popups => true,
|
107
107
|
:scale_x_integers => false,
|
108
|
-
:
|
108
|
+
:scale_y_integers => false,
|
109
109
|
)
|
110
110
|
end
|
111
111
|
|
@@ -124,20 +124,20 @@ module SVG
|
|
124
124
|
# would cause the graph to attempt to generate labels stepped by 0.5; EG:
|
125
125
|
# 0, 0.5, 1, 1.5, 2, ...
|
126
126
|
# default is automatic such that there are 10 labels
|
127
|
-
attr_accessor :scale_y_divisions
|
127
|
+
attr_accessor :scale_y_divisions
|
128
128
|
# Make the X axis labels integers, default: false
|
129
|
-
attr_accessor :scale_x_integers
|
129
|
+
attr_accessor :scale_x_integers
|
130
130
|
# Make the Y axis labels integers, default: false
|
131
|
-
attr_accessor :scale_y_integers
|
131
|
+
attr_accessor :scale_y_integers
|
132
132
|
# Fill the area under the line, default: false
|
133
|
-
attr_accessor :area_fill
|
133
|
+
attr_accessor :area_fill
|
134
134
|
# Show a small circle on the graph where the line
|
135
135
|
# goes from one point to the next. default: true
|
136
136
|
attr_accessor :show_data_points
|
137
137
|
# Set the minimum value of the X axis, if nil the minimum from data is chosen, default: nil
|
138
|
-
attr_accessor :min_x_value
|
138
|
+
attr_accessor :min_x_value
|
139
139
|
# Set the maximum value of the X axis, if nil the maximum from data is chosen, default: nil
|
140
|
-
attr_accessor :max_x_value
|
140
|
+
attr_accessor :max_x_value
|
141
141
|
# Set the minimum value of the Y axis, if nil the minimum from data is chosen, default: nil
|
142
142
|
attr_accessor :min_y_value
|
143
143
|
# Set the maximum value of the Y axis, if nil the maximum from data is chosen, default: nil
|
@@ -155,43 +155,45 @@ module SVG
|
|
155
155
|
# data_set2 = [[1,2], 5,6]
|
156
156
|
# or
|
157
157
|
# data_set2 = [[1,2], [5,6]]
|
158
|
-
#
|
158
|
+
#
|
159
159
|
# graph.add_data({
|
160
160
|
# :data => data_set1,
|
161
161
|
# :title => 'single point'
|
162
|
-
# })
|
162
|
+
# })
|
163
163
|
# graph.add_data({
|
164
164
|
# :data => data_set2,
|
165
165
|
# :title => 'two points'
|
166
|
-
# })
|
166
|
+
# })
|
167
|
+
# @param conf [Hash] with keys
|
168
|
+
# :data [Array] of x,y pairs, one pair for each point
|
169
|
+
# :title [String] mandatory name of data series for legend of graph
|
170
|
+
# :description [Array<String>] (optional) if given, description for each datapoint (shown in popups)
|
171
|
+
# :shape [Array<String>] (optional) if given, DataPoint shape is chosen based on this string instead of description
|
172
|
+
# :url [Array<String>] (optional) if given, link will be added to each datapoint
|
167
173
|
def add_data(conf)
|
168
|
-
|
169
|
-
raise "No data provided by #{conf.inspect}" unless conf[:data]
|
170
|
-
conf[:data].kind_of? Array
|
174
|
+
@data ||= []
|
175
|
+
raise "No data provided by #{conf.inspect}" unless conf[:data].is_a?(Array)
|
171
176
|
# support array of arrays and flatten it
|
172
177
|
conf[:data] = conf[:data].flatten
|
173
178
|
# check that we have pairs of values
|
174
179
|
raise "Data supplied must be x,y pairs! "+
|
175
180
|
"The data provided contained an odd set of "+
|
176
181
|
"data points" unless conf[:data].length % 2 == 0
|
177
|
-
|
182
|
+
|
178
183
|
# remove nil values
|
179
184
|
conf[:data] = conf[:data].compact
|
180
|
-
|
181
|
-
return if conf[:data].length == 0
|
182
185
|
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
end
|
186
|
+
return if conf[:data].length.zero?
|
187
|
+
|
188
|
+
add_data_init_or_check_optional_keys(conf, conf[:data].size / 2)
|
187
189
|
|
188
190
|
x = []
|
189
191
|
y = []
|
190
192
|
conf[:data].each_index {|i|
|
191
193
|
(i%2 == 0 ? x : y) << conf[:data][i]
|
192
194
|
}
|
193
|
-
sort(
|
194
|
-
conf[:data] = [x,y]
|
195
|
+
sort(x, y, conf[:description], conf[:shape], conf[:url])
|
196
|
+
conf[:data] = [x, y]
|
195
197
|
# at the end data looks like:
|
196
198
|
# [
|
197
199
|
# [all x values],
|
@@ -218,20 +220,25 @@ module SVG
|
|
218
220
|
@border_right = label_right if label_right > @border_right
|
219
221
|
end
|
220
222
|
|
221
|
-
|
222
223
|
X = 0
|
223
224
|
Y = 1
|
224
225
|
|
225
226
|
def max_x_range
|
227
|
+
# needs to be computed fresh when called, to cover the use-case:
|
228
|
+
# add_data -> burn -> add_data -> burn
|
229
|
+
# when values would be cached, the graph is not updated for second burning
|
226
230
|
max_value = @data.collect{|x| x[:data][X][-1] }.max
|
227
231
|
max_value = max_value > max_x_value ? max_value : max_x_value if max_x_value
|
228
|
-
max_value
|
232
|
+
return max_value
|
229
233
|
end
|
230
234
|
|
231
235
|
def min_x_range
|
236
|
+
# needs to be computed fresh when called, to cover the use-case:
|
237
|
+
# add_data -> burn -> add_data -> burn
|
238
|
+
# when values would be cached, the graph is not updated for second burning
|
232
239
|
min_value = @data.collect{|x| x[:data][X][0] }.min
|
233
240
|
min_value = min_value < min_x_value ? min_value : min_x_value if min_x_value
|
234
|
-
min_value
|
241
|
+
return min_value
|
235
242
|
end
|
236
243
|
|
237
244
|
def x_label_range
|
@@ -246,11 +253,11 @@ module SVG
|
|
246
253
|
end
|
247
254
|
scale_range = max_value - min_value
|
248
255
|
|
249
|
-
scale_division = scale_x_divisions || (scale_range /
|
256
|
+
scale_division = scale_x_divisions || (scale_range / 9.0)
|
250
257
|
@x_offset = 0
|
251
|
-
|
258
|
+
|
252
259
|
if scale_x_integers
|
253
|
-
scale_division = scale_division < 1 ? 1 : scale_division.
|
260
|
+
scale_division = scale_division < 1 ? 1 : scale_division.ceil
|
254
261
|
@x_offset = min_value.to_f - min_value.floor
|
255
262
|
min_value = min_value.floor
|
256
263
|
end
|
@@ -261,7 +268,7 @@ module SVG
|
|
261
268
|
def get_x_values
|
262
269
|
min_value, max_value, @x_scale_division = x_label_range
|
263
270
|
rv = []
|
264
|
-
min_value.step( max_value, @x_scale_division ) {|v| rv << v}
|
271
|
+
min_value.step( max_value + @x_scale_division , @x_scale_division ) {|v| rv << v}
|
265
272
|
return rv
|
266
273
|
end
|
267
274
|
alias :get_x_labels :get_x_values
|
@@ -270,20 +277,22 @@ module SVG
|
|
270
277
|
# exclude values which are outside max_x_range
|
271
278
|
values = get_x_values
|
272
279
|
@graph_width.to_f / (values.length - 1 ) # -1 is to use entire x-axis
|
273
|
-
# otherwise there is always 1 division unused
|
280
|
+
# otherwise there is always 1 division unused
|
274
281
|
end
|
275
282
|
|
276
|
-
|
277
283
|
def max_y_range
|
278
284
|
max_value = @data.collect{|x| x[:data][Y].max }.max
|
279
285
|
max_value = max_value > max_y_value ? max_value : max_y_value if max_y_value
|
280
|
-
max_value
|
286
|
+
return max_value
|
281
287
|
end
|
282
288
|
|
283
289
|
def min_y_range
|
290
|
+
# needs to be computed fresh when called, to cover the use-case:
|
291
|
+
# add_data -> burn -> add_data -> burn
|
292
|
+
# when values would be cached, the graph is not updated for second burning
|
284
293
|
min_value = @data.collect{|x| x[:data][Y].min }.min
|
285
294
|
min_value = min_value < min_y_value ? min_value : min_y_value if min_y_value
|
286
|
-
min_value
|
295
|
+
return min_value
|
287
296
|
end
|
288
297
|
|
289
298
|
def y_label_range
|
@@ -298,11 +307,11 @@ module SVG
|
|
298
307
|
end
|
299
308
|
scale_range = max_value - min_value
|
300
309
|
|
301
|
-
scale_division = scale_y_divisions || (scale_range /
|
310
|
+
scale_division = scale_y_divisions || (scale_range / 9.0)
|
302
311
|
@y_offset = 0
|
303
|
-
|
312
|
+
|
304
313
|
if scale_y_integers
|
305
|
-
scale_division = scale_division < 1 ? 1 : scale_division.
|
314
|
+
scale_division = scale_division < 1 ? 1 : scale_division.ceil
|
306
315
|
@y_offset = (min_value.to_f - min_value.floor).to_f
|
307
316
|
min_value = min_value.floor
|
308
317
|
end
|
@@ -314,11 +323,11 @@ module SVG
|
|
314
323
|
min_value, max_value, @y_scale_division = y_label_range
|
315
324
|
if max_value != min_value
|
316
325
|
while (max_value - min_value) < @y_scale_division
|
317
|
-
@y_scale_division /=
|
326
|
+
@y_scale_division /= 9.0
|
318
327
|
end
|
319
328
|
end
|
320
329
|
rv = []
|
321
|
-
min_value.step( max_value, @y_scale_division ) {|v| rv << v}
|
330
|
+
min_value.step( max_value + @y_scale_division, @y_scale_division ) {|v| rv << v}
|
322
331
|
rv << rv[0] + 1 if rv.length == 1
|
323
332
|
return rv
|
324
333
|
end
|
@@ -333,25 +342,23 @@ module SVG
|
|
333
342
|
else
|
334
343
|
dx = (max - values[-1]).to_f / (values[-1] - values[-2])
|
335
344
|
end
|
336
|
-
@graph_height.to_f / values.length
|
345
|
+
@graph_height.to_f / (values.length - 1)
|
337
346
|
end
|
338
|
-
|
347
|
+
|
339
348
|
def calc_coords(x, y)
|
340
349
|
coords = {:x => 0, :y => 0}
|
341
350
|
# scale the coordinates, use float division / multiplication
|
342
351
|
# otherwise the point will be place inaccurate
|
343
|
-
coords[:x] = (x + @x_offset)/@x_scale_division.to_f * field_width
|
352
|
+
coords[:x] = (x + @x_offset)/@x_scale_division.to_f * field_width
|
344
353
|
coords[:y] = @graph_height - (y + @y_offset)/@y_scale_division.to_f * field_height
|
345
354
|
return coords
|
346
355
|
end
|
347
356
|
|
348
357
|
def draw_data
|
349
358
|
line = 1
|
350
|
-
|
359
|
+
|
351
360
|
x_min = min_x_range
|
352
|
-
x_max = max_x_range
|
353
361
|
y_min = min_y_range
|
354
|
-
y_max = max_y_range
|
355
362
|
|
356
363
|
for data in @data
|
357
364
|
x_points = data[:data][X]
|
@@ -384,89 +391,93 @@ module SVG
|
|
384
391
|
x_points.each_index { |idx|
|
385
392
|
c = calc_coords(x_points[idx] - x_min, y_points[idx] - y_min)
|
386
393
|
if show_data_points
|
387
|
-
|
394
|
+
shape_selection_string = data[:description][idx].to_s
|
395
|
+
if !data[:shape][idx].nil?
|
396
|
+
shape_selection_string = data[:shape][idx].to_s
|
397
|
+
end
|
398
|
+
DataPoint.new(c[:x], c[:y], line).shape(shape_selection_string).each{|s|
|
388
399
|
@graph.add_element( *s )
|
389
400
|
}
|
390
401
|
end
|
391
402
|
make_datapoint_text( c[:x], c[:y]-6, y_points[idx] )
|
392
|
-
add_popup(c[:x], c[:y], format( x_points[idx], y_points[idx], data[:description][idx]))
|
403
|
+
add_popup(c[:x], c[:y], format( x_points[idx], y_points[idx], data[:description][idx].to_s), "", data[:url][idx].to_s)
|
393
404
|
}
|
394
405
|
end
|
395
406
|
line += 1
|
396
407
|
end
|
397
408
|
end
|
398
|
-
|
409
|
+
|
399
410
|
# returns the formatted string which is added as popup information
|
400
411
|
def format x, y, desc
|
401
412
|
info = []
|
402
413
|
info << (round_popups ? x.round : @number_format % x )
|
403
414
|
info << (round_popups ? y.round : @number_format % y )
|
404
|
-
info << desc
|
415
|
+
info << desc if !desc.empty?
|
405
416
|
"(#{info.compact.join(', ')})"
|
406
417
|
end
|
407
|
-
|
418
|
+
|
408
419
|
def get_css
|
409
420
|
return <<EOL
|
410
421
|
/* default line styles */
|
411
422
|
.line1{
|
412
423
|
fill: none;
|
413
424
|
stroke: #ff0000;
|
414
|
-
stroke-width: 1px;
|
425
|
+
stroke-width: 1px;
|
415
426
|
}
|
416
427
|
.line2{
|
417
428
|
fill: none;
|
418
429
|
stroke: #0000ff;
|
419
|
-
stroke-width: 1px;
|
430
|
+
stroke-width: 1px;
|
420
431
|
}
|
421
432
|
.line3{
|
422
433
|
fill: none;
|
423
434
|
stroke: #00ff00;
|
424
|
-
stroke-width: 1px;
|
435
|
+
stroke-width: 1px;
|
425
436
|
}
|
426
437
|
.line4{
|
427
438
|
fill: none;
|
428
439
|
stroke: #ffcc00;
|
429
|
-
stroke-width: 1px;
|
440
|
+
stroke-width: 1px;
|
430
441
|
}
|
431
442
|
.line5{
|
432
443
|
fill: none;
|
433
444
|
stroke: #00ccff;
|
434
|
-
stroke-width: 1px;
|
445
|
+
stroke-width: 1px;
|
435
446
|
}
|
436
447
|
.line6{
|
437
448
|
fill: none;
|
438
449
|
stroke: #ff00ff;
|
439
|
-
stroke-width: 1px;
|
450
|
+
stroke-width: 1px;
|
440
451
|
}
|
441
452
|
.line7{
|
442
453
|
fill: none;
|
443
454
|
stroke: #00ffff;
|
444
|
-
stroke-width: 1px;
|
455
|
+
stroke-width: 1px;
|
445
456
|
}
|
446
457
|
.line8{
|
447
458
|
fill: none;
|
448
459
|
stroke: #ffff00;
|
449
|
-
stroke-width: 1px;
|
460
|
+
stroke-width: 1px;
|
450
461
|
}
|
451
462
|
.line9{
|
452
463
|
fill: none;
|
453
|
-
stroke: #
|
454
|
-
stroke-width: 1px;
|
464
|
+
stroke: #cc6666;
|
465
|
+
stroke-width: 1px;
|
455
466
|
}
|
456
467
|
.line10{
|
457
468
|
fill: none;
|
458
469
|
stroke: #663399;
|
459
|
-
stroke-width: 1px;
|
470
|
+
stroke-width: 1px;
|
460
471
|
}
|
461
472
|
.line11{
|
462
473
|
fill: none;
|
463
474
|
stroke: #339900;
|
464
|
-
stroke-width: 1px;
|
475
|
+
stroke-width: 1px;
|
465
476
|
}
|
466
477
|
.line12{
|
467
478
|
fill: none;
|
468
479
|
stroke: #9966FF;
|
469
|
-
stroke-width: 1px;
|
480
|
+
stroke-width: 1px;
|
470
481
|
}
|
471
482
|
/* default fill styles */
|
472
483
|
.fill1{
|
@@ -533,62 +544,62 @@ module SVG
|
|
533
544
|
.key1,.dataPoint1{
|
534
545
|
fill: #ff0000;
|
535
546
|
stroke: none;
|
536
|
-
stroke-width: 1px;
|
547
|
+
stroke-width: 1px;
|
537
548
|
}
|
538
549
|
.key2,.dataPoint2{
|
539
550
|
fill: #0000ff;
|
540
551
|
stroke: none;
|
541
|
-
stroke-width: 1px;
|
552
|
+
stroke-width: 1px;
|
542
553
|
}
|
543
554
|
.key3,.dataPoint3{
|
544
555
|
fill: #00ff00;
|
545
556
|
stroke: none;
|
546
|
-
stroke-width: 1px;
|
557
|
+
stroke-width: 1px;
|
547
558
|
}
|
548
559
|
.key4,.dataPoint4{
|
549
560
|
fill: #ffcc00;
|
550
561
|
stroke: none;
|
551
|
-
stroke-width: 1px;
|
562
|
+
stroke-width: 1px;
|
552
563
|
}
|
553
564
|
.key5,.dataPoint5{
|
554
565
|
fill: #00ccff;
|
555
566
|
stroke: none;
|
556
|
-
stroke-width: 1px;
|
567
|
+
stroke-width: 1px;
|
557
568
|
}
|
558
569
|
.key6,.dataPoint6{
|
559
570
|
fill: #ff00ff;
|
560
571
|
stroke: none;
|
561
|
-
stroke-width: 1px;
|
572
|
+
stroke-width: 1px;
|
562
573
|
}
|
563
574
|
.key7,.dataPoint7{
|
564
575
|
fill: #00ffff;
|
565
576
|
stroke: none;
|
566
|
-
stroke-width: 1px;
|
577
|
+
stroke-width: 1px;
|
567
578
|
}
|
568
579
|
.key8,.dataPoint8{
|
569
580
|
fill: #ffff00;
|
570
581
|
stroke: none;
|
571
|
-
stroke-width: 1px;
|
582
|
+
stroke-width: 1px;
|
572
583
|
}
|
573
584
|
.key9,.dataPoint9{
|
574
585
|
fill: #cc6666;
|
575
586
|
stroke: none;
|
576
|
-
stroke-width: 1px;
|
587
|
+
stroke-width: 1px;
|
577
588
|
}
|
578
589
|
.key10,.dataPoint10{
|
579
590
|
fill: #663399;
|
580
591
|
stroke: none;
|
581
|
-
stroke-width: 1px;
|
592
|
+
stroke-width: 1px;
|
582
593
|
}
|
583
594
|
.key11,.dataPoint11{
|
584
595
|
fill: #339900;
|
585
596
|
stroke: none;
|
586
|
-
stroke-width: 1px;
|
597
|
+
stroke-width: 1px;
|
587
598
|
}
|
588
599
|
.key12,.dataPoint12{
|
589
600
|
fill: #9966FF;
|
590
601
|
stroke: none;
|
591
|
-
stroke-width: 1px;
|
602
|
+
stroke-width: 1px;
|
592
603
|
}
|
593
604
|
EOL
|
594
605
|
end
|