svg-graph 2.0.1 → 2.0.2.beta
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/History.txt +13 -2
- data/README.markdown +4 -0
- data/lib/SVG/Graph/Bar.rb +13 -10
- data/lib/SVG/Graph/BarHorizontal.rb +14 -11
- data/lib/SVG/Graph/ErrBar.rb +1 -1
- data/lib/SVG/Graph/Graph.rb +148 -77
- data/lib/SVG/Graph/Line.rb +90 -65
- data/lib/SVG/Graph/Pie.rb +12 -2
- data/lib/SVG/Graph/Plot.rb +39 -26
- data/lib/svggraph.rb +1 -1
- metadata +5 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d856850b800bff5e1688e0ad450ccbcc21bdd21d
|
4
|
+
data.tar.gz: 095e8398439866cc6cd52439d60d036ef960ac8a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 69d1f607ba2edec2452d14e39fdb18079bc59a76c52898e101d7adff4c383d8ae40c589e45a001eb3b50912bd2d46af6ac89e4d1b9a9a68f87968a8cf2382877
|
7
|
+
data.tar.gz: 2cb188581fe18553a4c1a2527168f4d4988cf2b47dd6577dadf248b4d93fe09d26608f164bdca8fddb9d97ee9feece347b1287ee8fef12e325245a11d63b05d6
|
data/History.txt
CHANGED
@@ -1,7 +1,18 @@
|
|
1
1
|
TODO
|
2
|
-
* fix axis titles positioning for
|
2
|
+
* fix axis titles positioning for staggered axis (prevent overlap)
|
3
|
+
* fix scale_integer
|
4
|
+
* fix sorting and connected lines for Plot
|
3
5
|
|
4
|
-
|
6
|
+
|
7
|
+
=== 2.0.2 work in progress
|
8
|
+
* fix axis-title positioning
|
9
|
+
* fix/add support for popups of values line, plot, bar graph
|
10
|
+
* Line,Plot,Pie graph support nil values (or pairs of nil for plot) for non-present datapoints
|
11
|
+
* changed text anchor of first datapoint label to avoid overlap with axis
|
12
|
+
* fix pie graph key overlapping the graph
|
13
|
+
*
|
14
|
+
|
15
|
+
=== 2.0.1 / 2016-08-09
|
5
16
|
* dropped support for ruby 1.8.7 [lumean]
|
6
17
|
* Merge ErrBar graph from https://github.com/mondhs/svg-graph
|
7
18
|
* Merge number formatting to have more readable floats from https://github.com/mondhs/svg-graph
|
data/README.markdown
CHANGED
@@ -74,10 +74,14 @@ File.open('bar.svg', 'w') {|f| f.write(g.burn_svg_only)}
|
|
74
74
|
|
75
75
|
### BarHorizontal
|
76
76
|
|
77
|
+
![example bar_horizontal graph](https://cdn.rawgit.com/lumean/svg-graph2/master/examples/bar_horizontal.svg)
|
78
|
+
|
77
79
|
### ErrBar
|
78
80
|
|
79
81
|
### Line
|
80
82
|
|
83
|
+
![example line graph](https://cdn.rawgit.com/lumean/svg-graph2/master/examples/line.svg)
|
84
|
+
|
81
85
|
### Pie
|
82
86
|
|
83
87
|
### Plot
|
data/lib/SVG/Graph/Bar.rb
CHANGED
@@ -61,7 +61,7 @@ module SVG
|
|
61
61
|
# See Graph::initialize and BarBase::set_defaults
|
62
62
|
def set_defaults
|
63
63
|
super
|
64
|
-
self.top_align = self.top_font = 1
|
64
|
+
# self.top_align = self.top_font = 1
|
65
65
|
end
|
66
66
|
|
67
67
|
protected
|
@@ -78,16 +78,17 @@ module SVG
|
|
78
78
|
top_pad = range == 0 ? 10 : range / 20.0
|
79
79
|
scale_range = (maxvalue + top_pad) - minvalue
|
80
80
|
|
81
|
-
|
81
|
+
@y_scale_division = scale_divisions || (scale_range / 10.0)
|
82
82
|
|
83
83
|
if scale_integers
|
84
|
-
|
84
|
+
@y_scale_division = @y_scale_division < 1 ? 1 : @y_scale_division.round
|
85
85
|
end
|
86
86
|
|
87
87
|
rv = []
|
88
|
-
|
89
|
-
maxvalue
|
90
|
-
|
88
|
+
if maxvalue%@y_scale_division != 0
|
89
|
+
maxvalue = maxvalue + @y_scale_division
|
90
|
+
end
|
91
|
+
minvalue.step( maxvalue, @y_scale_division ) {|v| rv << v}
|
91
92
|
return rv
|
92
93
|
end
|
93
94
|
|
@@ -99,8 +100,9 @@ module SVG
|
|
99
100
|
minvalue = min_value
|
100
101
|
fieldwidth = field_width
|
101
102
|
|
102
|
-
unit_size = (@graph_height.to_f - font_size*2*top_font) /
|
103
|
-
|
103
|
+
# unit_size = (@graph_height.to_f - font_size*2*top_font) /
|
104
|
+
# (get_y_labels.max - get_y_labels.min)
|
105
|
+
unit_size = field_height
|
104
106
|
bargap = bar_gap ? (fieldwidth < 10 ? fieldwidth / 2 : 10) : 0
|
105
107
|
|
106
108
|
bar_width = fieldwidth - bargap
|
@@ -120,7 +122,7 @@ module SVG
|
|
120
122
|
# +ve -ve value - 0
|
121
123
|
# -ve -ve value.abs - 0
|
122
124
|
|
123
|
-
value = dataset[:data][i]
|
125
|
+
value = dataset[:data][i]/@y_scale_division
|
124
126
|
|
125
127
|
left = (fieldwidth * field_count)
|
126
128
|
|
@@ -137,7 +139,8 @@ module SVG
|
|
137
139
|
"class" => "fill#{dataset_count+1}"
|
138
140
|
})
|
139
141
|
|
140
|
-
make_datapoint_text(left + bar_width/2.0, top -
|
142
|
+
make_datapoint_text(left + bar_width/2.0, top - font_size/2, dataset[:data][i].to_s)
|
143
|
+
add_popup(left + bar_width/2.0, top , dataset[:data][i].to_s)
|
141
144
|
dataset_count += 1
|
142
145
|
end
|
143
146
|
field_count += 1
|
@@ -67,7 +67,7 @@ module SVG
|
|
67
67
|
:show_x_guidelines => true,
|
68
68
|
:show_y_guidelines => false
|
69
69
|
)
|
70
|
-
self.right_align = self.right_font = 1
|
70
|
+
# self.right_align = self.right_font = 1
|
71
71
|
end
|
72
72
|
|
73
73
|
protected
|
@@ -79,16 +79,17 @@ module SVG
|
|
79
79
|
top_pad = range == 0 ? 10 : range / 20.0
|
80
80
|
scale_range = (maxvalue + top_pad) - minvalue
|
81
81
|
|
82
|
-
|
82
|
+
@x_scale_division = scale_divisions || (scale_range / 10.0)
|
83
83
|
|
84
84
|
if scale_integers
|
85
|
-
|
85
|
+
@x_scale_division = @x_scale_division < 1 ? 1 : @x_scale_division.round
|
86
86
|
end
|
87
87
|
|
88
88
|
rv = []
|
89
|
-
|
90
|
-
maxvalue
|
91
|
-
|
89
|
+
if maxvalue%@x_scale_division != 0
|
90
|
+
maxvalue = maxvalue + @x_scale_division
|
91
|
+
end
|
92
|
+
minvalue.step( maxvalue, @x_scale_division ) {|v| rv << v}
|
92
93
|
return rv
|
93
94
|
end
|
94
95
|
|
@@ -103,9 +104,10 @@ module SVG
|
|
103
104
|
def draw_data
|
104
105
|
minvalue = min_value
|
105
106
|
fieldheight = field_height
|
106
|
-
|
107
|
-
unit_size = (@graph_width.to_f - font_size*2*right_font ) /
|
108
|
-
|
107
|
+
# number of steps in px between x-labels
|
108
|
+
# unit_size = (@graph_width.to_f - font_size*2*right_font ) /
|
109
|
+
# (get_x_labels.max - get_x_labels.min )
|
110
|
+
unit_size = field_width
|
109
111
|
bargap = bar_gap ? (fieldheight < 10 ? fieldheight / 2 : 10) : 0
|
110
112
|
|
111
113
|
bar_height = fieldheight - bargap
|
@@ -116,7 +118,7 @@ module SVG
|
|
116
118
|
@config[:fields].each_index { |i|
|
117
119
|
dataset_count = 0
|
118
120
|
for dataset in @data
|
119
|
-
value = dataset[:data][i]
|
121
|
+
value = dataset[:data][i]/@x_scale_division
|
120
122
|
|
121
123
|
top = @graph_height - (fieldheight * field_count)
|
122
124
|
top += (bar_height * dataset_count) if stack == :side
|
@@ -137,8 +139,9 @@ module SVG
|
|
137
139
|
})
|
138
140
|
|
139
141
|
make_datapoint_text(
|
140
|
-
left+length+5, top+y_mod,
|
142
|
+
left+length+5, top+y_mod, dataset[:data][i].to_s, "text-anchor: start; "
|
141
143
|
)
|
144
|
+
add_popup(left+length, top+y_mod , dataset[:data][i].to_s)
|
142
145
|
dataset_count += 1
|
143
146
|
end
|
144
147
|
field_count += 1
|
data/lib/SVG/Graph/ErrBar.rb
CHANGED
data/lib/SVG/Graph/Graph.rb
CHANGED
@@ -102,7 +102,8 @@ module SVG
|
|
102
102
|
def initialize( config )
|
103
103
|
@config = config
|
104
104
|
@data = []
|
105
|
-
self.top_align = self.top_font =
|
105
|
+
#self.top_align = self.top_font = 0
|
106
|
+
#self.right_align = self.right_font = 0
|
106
107
|
|
107
108
|
init_with({
|
108
109
|
:width => 500,
|
@@ -192,12 +193,15 @@ module SVG
|
|
192
193
|
#
|
193
194
|
# This method will croak unless at least one data set has
|
194
195
|
# been added to the graph object.
|
195
|
-
#
|
196
|
+
#
|
196
197
|
# print graph.burn
|
198
|
+
#
|
197
199
|
def burn
|
198
200
|
raise "No data available" unless @data.size > 0
|
199
201
|
|
200
|
-
|
202
|
+
# undocumented and not used in any sublass
|
203
|
+
# to be removed
|
204
|
+
#calculations if methods.include? 'calculations'
|
201
205
|
|
202
206
|
start_svg
|
203
207
|
calculate_graph_dimensions
|
@@ -205,7 +209,7 @@ module SVG
|
|
205
209
|
draw_graph
|
206
210
|
draw_titles
|
207
211
|
draw_legend
|
208
|
-
draw_data
|
212
|
+
draw_data # this method needs to be implemented by child classes
|
209
213
|
@graph.add_element( @foreground )
|
210
214
|
style
|
211
215
|
|
@@ -278,7 +282,7 @@ module SVG
|
|
278
282
|
# Default it false, to turn on set to true.
|
279
283
|
attr_accessor :rotate_x_labels
|
280
284
|
# This turns the Y axis labels by 90 degrees.
|
281
|
-
# Default it
|
285
|
+
# Default it true, to turn on set to false.
|
282
286
|
attr_accessor :rotate_y_labels
|
283
287
|
# How many "steps" to use between displayed X axis labels,
|
284
288
|
# a step of one means display every label, a step of two results
|
@@ -382,7 +386,9 @@ module SVG
|
|
382
386
|
|
383
387
|
|
384
388
|
protected
|
385
|
-
|
389
|
+
|
390
|
+
# implementation of quicksort
|
391
|
+
# used for Schedule and Plot
|
386
392
|
def sort( *arrys )
|
387
393
|
sort_multiple( arrys )
|
388
394
|
end
|
@@ -396,35 +402,45 @@ module SVG
|
|
396
402
|
@popup_radius ||= 10
|
397
403
|
end
|
398
404
|
|
399
|
-
|
405
|
+
# unknown why needed
|
406
|
+
# attr_accessor :top_align, :top_font, :right_align, :right_font
|
400
407
|
|
408
|
+
# size of the square box in the legend which indicates the colors
|
401
409
|
KEY_BOX_SIZE = 12
|
402
410
|
|
403
411
|
# Override this (and call super) to change the margin to the left
|
404
412
|
# of the plot area. Results in @border_left being set.
|
413
|
+
#
|
414
|
+
# By default it is 7 + max label height(font size or string length, depending on rotate) + title height
|
405
415
|
def calculate_left_margin
|
406
416
|
@border_left = 7
|
407
417
|
# Check size of Y labels
|
408
|
-
|
409
|
-
if
|
410
|
-
|
418
|
+
@border_left += max_y_label_width_px
|
419
|
+
if (show_y_title && (y_title_location ==:middle))
|
420
|
+
@border_left += y_title_font_size + 5
|
411
421
|
end
|
412
|
-
|
413
|
-
@border_left += max_y_label_height_px if show_y_labels
|
414
|
-
@border_left += max_y_label_height_px + 10 if stagger_y_labels
|
415
|
-
@border_left += y_title_font_size + 5 if (show_y_title && (y_title_location ==:middle))
|
416
422
|
end
|
417
423
|
|
418
|
-
|
419
424
|
# Calculates the width of the widest Y label. This will be the
|
420
|
-
# character height if the Y labels are rotated
|
425
|
+
# character height if the Y labels are rotated. Returns 0 if labels
|
426
|
+
# are not shown
|
421
427
|
def max_y_label_width_px
|
422
|
-
return
|
428
|
+
return 0 if !show_y_labels
|
429
|
+
if !rotate_y_labels
|
430
|
+
max_width = get_longest_label(get_y_labels).to_s.length * y_label_font_size * 0.6
|
431
|
+
else
|
432
|
+
max_width = y_label_font_size + 3
|
433
|
+
end
|
434
|
+
max_width += 10 if stagger_y_labels
|
435
|
+
return max_width
|
423
436
|
end
|
424
437
|
|
425
438
|
|
426
439
|
# Override this (and call super) to change the margin to the right
|
427
440
|
# of the plot area. Results in @border_right being set.
|
441
|
+
#
|
442
|
+
# By default it is 7 + width of the key if it is placed on the right
|
443
|
+
# or the maximum of this value or the tilte length (if title is placed at :end)
|
428
444
|
def calculate_right_margin
|
429
445
|
@border_right = 7
|
430
446
|
if key and key_position == :right
|
@@ -441,6 +457,8 @@ module SVG
|
|
441
457
|
|
442
458
|
# Override this (and call super) to change the margin to the top
|
443
459
|
# of the plot area. Results in @border_top being set.
|
460
|
+
#
|
461
|
+
# This is 5 + the Title size + 5 + subTitle size
|
444
462
|
def calculate_top_margin
|
445
463
|
@border_top = 5
|
446
464
|
@border_top += [title_font_size, y_title_font_size].max if (show_graph_title || (y_title_location ==:end))
|
@@ -448,32 +466,42 @@ module SVG
|
|
448
466
|
@border_top += subtitle_font_size if show_graph_subtitle
|
449
467
|
end
|
450
468
|
|
469
|
+
def add_datapoint_text_and_popup( x, y, label )
|
470
|
+
add_popup( x, y, label )
|
471
|
+
make_datapoint_text( x, y, label )
|
472
|
+
end
|
451
473
|
|
452
|
-
# Adds pop-up point information to a graph.
|
453
|
-
def add_popup( x, y, label )
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
"
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
"
|
475
|
-
|
476
|
-
|
474
|
+
# Adds pop-up point information to a graph only if the config option is set.
|
475
|
+
def add_popup( x, y, label, style="" )
|
476
|
+
if add_popups
|
477
|
+
if( numeric?(label) )
|
478
|
+
label = @number_format % label
|
479
|
+
end
|
480
|
+
txt_width = label.length * font_size * 0.6 + 10
|
481
|
+
tx = (x+txt_width > width ? x-5 : x+5)
|
482
|
+
t = @foreground.add_element( "text", {
|
483
|
+
"x" => tx.to_s,
|
484
|
+
"y" => (y - font_size).to_s,
|
485
|
+
"class" => "dataPointLabel",
|
486
|
+
"visibility" => "hidden",
|
487
|
+
})
|
488
|
+
t.attributes["style"] = "stroke-width: 2; fill: #000; #{style}"+
|
489
|
+
(x+txt_width > width ? "text-anchor: end;" : "text-anchor: start;")
|
490
|
+
t.text = label.to_s
|
491
|
+
t.attributes["id"] = t.object_id.to_s
|
492
|
+
|
493
|
+
# add a circle to catch the mouseover
|
494
|
+
@foreground.add_element( "circle", {
|
495
|
+
"cx" => x.to_s,
|
496
|
+
"cy" => y.to_s,
|
497
|
+
"r" => "#{popup_radius}",
|
498
|
+
"style" => "opacity: 0",
|
499
|
+
"onmouseover" =>
|
500
|
+
"document.getElementById(#{t.object_id}).setAttribute('visibility', 'visible' )",
|
501
|
+
"onmouseout" =>
|
502
|
+
"document.getElementById(#{t.object_id}).setAttribute('visibility', 'hidden' )",
|
503
|
+
})
|
504
|
+
end # add_popups
|
477
505
|
end
|
478
506
|
|
479
507
|
# returns the longest label from an array of labels as string
|
@@ -491,22 +519,32 @@ module SVG
|
|
491
519
|
|
492
520
|
# Override this (and call super) to change the margin to the bottom
|
493
521
|
# of the plot area. Results in @border_bottom being set.
|
522
|
+
#
|
523
|
+
# 7 + max label height(font size or string length, depending on rotate) + title height
|
494
524
|
def calculate_bottom_margin
|
495
525
|
@border_bottom = 7
|
496
526
|
if key and key_position == :bottom
|
497
527
|
@border_bottom += @data.size * (font_size + 5)
|
498
528
|
@border_bottom += 10
|
499
529
|
end
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
530
|
+
@border_bottom += max_x_label_height_px
|
531
|
+
if (show_x_title && (x_title_location ==:middle))
|
532
|
+
@border_bottom += x_title_font_size + 5
|
533
|
+
end
|
534
|
+
end
|
535
|
+
|
536
|
+
# returns the maximum height of the labels respect the rotation or 0 if
|
537
|
+
# the labels are not shown
|
538
|
+
def max_x_label_height_px
|
539
|
+
return 0 if !show_x_labels
|
540
|
+
|
541
|
+
if rotate_x_labels
|
542
|
+
max_height = get_longest_label(get_x_labels).to_s.length * x_label_font_size * 0.6
|
543
|
+
else
|
544
|
+
max_height = x_label_font_size + 3
|
508
545
|
end
|
509
|
-
|
546
|
+
max_height += 10 if stagger_x_labels
|
547
|
+
return max_height
|
510
548
|
end
|
511
549
|
|
512
550
|
|
@@ -553,18 +591,25 @@ module SVG
|
|
553
591
|
true if Float(object) rescue false
|
554
592
|
end
|
555
593
|
|
594
|
+
# adds the datapoint text to the graph only if the config option is set
|
556
595
|
def make_datapoint_text( x, y, value, style="" )
|
557
596
|
if show_data_values
|
558
597
|
textStr = value
|
559
598
|
if( numeric?(value) )
|
560
599
|
textStr = @number_format % value
|
561
600
|
end
|
601
|
+
# change anchor is label overlaps axis
|
602
|
+
if x < textStr.length/2 * font_size * 0.6
|
603
|
+
style << "text-anchor: start;"
|
604
|
+
end
|
605
|
+
# white background for better readability
|
562
606
|
@foreground.add_element( "text", {
|
563
607
|
"x" => x.to_s,
|
564
608
|
"y" => y.to_s,
|
565
609
|
"class" => "dataPointLabel",
|
566
610
|
"style" => "#{style} stroke: #fff; stroke-width: 2;"
|
567
611
|
}).text = textStr
|
612
|
+
# actual label
|
568
613
|
text = @foreground.add_element( "text", {
|
569
614
|
"x" => x.to_s,
|
570
615
|
"y" => y.to_s,
|
@@ -574,7 +619,7 @@ module SVG
|
|
574
619
|
text.attributes["style"] = style if style.length > 0
|
575
620
|
end
|
576
621
|
end
|
577
|
-
|
622
|
+
|
578
623
|
|
579
624
|
# Draws the X axis labels
|
580
625
|
def draw_x_labels
|
@@ -591,6 +636,10 @@ module SVG
|
|
591
636
|
end
|
592
637
|
|
593
638
|
if step == 0 then
|
639
|
+
label = label.to_s
|
640
|
+
if( numeric?(label) )
|
641
|
+
label = @number_format % label
|
642
|
+
end
|
594
643
|
text = @graph.add_element( "text" )
|
595
644
|
text.attributes["class"] = "xAxisLabels"
|
596
645
|
text.text = label.to_s
|
@@ -632,20 +681,39 @@ module SVG
|
|
632
681
|
0
|
633
682
|
end
|
634
683
|
|
635
|
-
|
684
|
+
# override this method in child class
|
685
|
+
# must return the array of labels for the x-axis
|
686
|
+
def get_x_labels
|
687
|
+
end
|
688
|
+
|
689
|
+
# override this method in child class
|
690
|
+
# must return the array of labels for the y-axis
|
691
|
+
# this method defines @y_scale_division
|
692
|
+
def get_y_labels
|
693
|
+
end
|
694
|
+
|
695
|
+
# space in px between x-labels
|
636
696
|
def field_width
|
637
|
-
(@graph_width.to_f - font_size*2*right_font) /
|
638
|
-
|
697
|
+
#(@graph_width.to_f - font_size*2*right_font) /
|
698
|
+
# (get_x_labels.length - right_align)
|
699
|
+
@graph_width.to_f / get_x_labels.length
|
639
700
|
end
|
640
701
|
|
641
|
-
|
702
|
+
# space in px between the y-labels
|
642
703
|
def field_height
|
643
|
-
(@graph_height.to_f - font_size*2*top_font) /
|
644
|
-
|
704
|
+
#(@graph_height.to_f - font_size*2*top_font) /
|
705
|
+
# (get_y_labels.length - top_align)
|
706
|
+
@graph_height.to_f / get_y_labels.length
|
645
707
|
end
|
646
708
|
|
647
709
|
|
648
|
-
# Draws the Y axis labels
|
710
|
+
# Draws the Y axis labels, the Y-Axis (@graph_height) is divided equally into #get_y_labels.lenght sections
|
711
|
+
# So the y coordinate for an arbitrary value is calculated as follows:
|
712
|
+
# y = @graph_height equals the min_value
|
713
|
+
# #normalize value of a single scale_division:
|
714
|
+
# count = value /(@y_scale_division)
|
715
|
+
# y = @graph_height - count * field_height
|
716
|
+
#
|
649
717
|
def draw_y_labels
|
650
718
|
stagger = y_label_font_size + 5
|
651
719
|
if show_y_labels
|
@@ -735,16 +803,12 @@ module SVG
|
|
735
803
|
end
|
736
804
|
|
737
805
|
if show_x_title
|
738
|
-
y = @graph_height + @border_top + x_title_font_size
|
739
806
|
if (x_title_location == :end)
|
740
|
-
y =
|
741
|
-
x =
|
807
|
+
y = @graph_height + @border_top + x_title_font_size/2.0
|
808
|
+
x = @border_left + @graph_width + x_title.length * x_title_font_size * 0.6/2.0
|
742
809
|
else
|
743
|
-
|
744
|
-
|
745
|
-
y += x_label_font_size + 5 if stagger_x_labels
|
746
|
-
y += x_label_font_size + 5
|
747
|
-
end
|
810
|
+
y = @graph_height + @border_top + x_title_font_size + max_x_label_height_px
|
811
|
+
x = @border_left + @graph_width / 2
|
748
812
|
end
|
749
813
|
|
750
814
|
@root.add_element("text", {
|
@@ -755,12 +819,12 @@ module SVG
|
|
755
819
|
end
|
756
820
|
|
757
821
|
if show_y_title
|
758
|
-
x = y_title_font_size + (y_title_text_direction==:bt ? 3 : -3)
|
759
822
|
if (y_title_location == :end)
|
760
823
|
x = y_title.length * y_title_font_size * 0.6/2.0 # positioning is not optimal but ok for now
|
761
824
|
y = @border_top - y_title_font_size/2.0
|
762
825
|
else
|
763
|
-
|
826
|
+
x = y_title_font_size + (y_title_text_direction==:bt ? 3 : -3)
|
827
|
+
y = @border_top + @graph_height / 2
|
764
828
|
end
|
765
829
|
text = @root.add_element("text", {
|
766
830
|
"x" => x.to_s,
|
@@ -816,12 +880,12 @@ module SVG
|
|
816
880
|
x_offset = @border_left + 20
|
817
881
|
y_offset = @border_top + @graph_height + 5
|
818
882
|
if show_x_labels
|
819
|
-
max_x_label_height_px = (not rotate_x_labels) ?
|
820
|
-
x_label_font_size :
|
821
|
-
get_x_labels.max{|a,b|
|
822
|
-
|
823
|
-
}.to_s.length * x_label_font_size * 0.6
|
824
|
-
|
883
|
+
# max_x_label_height_px = (not rotate_x_labels) ?
|
884
|
+
# x_label_font_size :
|
885
|
+
# get_x_labels.max{|a,b|
|
886
|
+
# a.to_s.length<=>b.to_s.length
|
887
|
+
# }.to_s.length * x_label_font_size * 0.6
|
888
|
+
# x_label_font_size
|
825
889
|
y_offset += max_x_label_height_px
|
826
890
|
y_offset += max_x_label_height_px + 5 if stagger_x_labels
|
827
891
|
end
|
@@ -898,10 +962,17 @@ module SVG
|
|
898
962
|
|
899
963
|
|
900
964
|
# Override and place code to add defs here
|
965
|
+
# @param defs [REXML::Element]
|
901
966
|
def add_defs defs
|
902
967
|
end
|
903
968
|
|
904
|
-
|
969
|
+
# Creates the XML document and adds the root svg element with
|
970
|
+
# the width, height and viewBox attributes already set.
|
971
|
+
# The element is stored as @root.
|
972
|
+
#
|
973
|
+
# In addition a rectangle background of the same size as the
|
974
|
+
# svg is added.
|
975
|
+
#
|
905
976
|
def start_svg
|
906
977
|
# Base document
|
907
978
|
@doc = Document.new
|
@@ -946,7 +1017,7 @@ module SVG
|
|
946
1017
|
})
|
947
1018
|
end
|
948
1019
|
|
949
|
-
|
1020
|
+
#
|
950
1021
|
def calculate_graph_dimensions
|
951
1022
|
calculate_left_margin
|
952
1023
|
calculate_right_margin
|
data/lib/SVG/Graph/Line.rb
CHANGED
@@ -45,7 +45,10 @@ module SVG
|
|
45
45
|
# http://www.germane-software/repositories/public/SVG/test/single.rb
|
46
46
|
#
|
47
47
|
# = Notes
|
48
|
-
#
|
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
|
+
#
|
49
52
|
# The default stylesheet handles upto 10 data sets, if you
|
50
53
|
# use more you must create your own stylesheet and add the
|
51
54
|
# additional settings for the extra data sets. You will know
|
@@ -78,65 +81,64 @@ module SVG
|
|
78
81
|
# Fill in the area under the plot if true
|
79
82
|
attr_accessor :area_fill
|
80
83
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
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
|
91
94
|
|
92
95
|
# In addition to the defaults set in Graph::initialize, sets
|
93
96
|
# [show_data_points] true
|
94
97
|
# [show_data_values] true
|
95
98
|
# [stacked] false
|
96
99
|
# [area_fill] false
|
97
|
-
|
100
|
+
def set_defaults
|
98
101
|
init_with(
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
102
|
+
:show_data_points => true,
|
103
|
+
:show_data_values => true,
|
104
|
+
:stacked => false,
|
105
|
+
:area_fill => false
|
103
106
|
)
|
104
|
-
|
105
|
-
|
106
|
-
self.top_align = self.top_font = self.right_align = self.right_font = 1
|
107
|
-
end
|
107
|
+
# self.top_align = self.top_font = self.right_align = self.right_font = 1
|
108
|
+
end
|
108
109
|
|
109
110
|
protected
|
110
111
|
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
sums[i] += data[:data][i].to_f
|
120
|
-
end
|
121
|
-
end
|
122
|
-
|
123
|
-
max = sums.max
|
124
|
-
else
|
125
|
-
max = @data.collect{|x| x[:data].max}.max
|
112
|
+
def max_value
|
113
|
+
max = 0
|
114
|
+
if stacked
|
115
|
+
sums = Array.new(@config[:fields].length).fill(0)
|
116
|
+
|
117
|
+
@data.each do |data|
|
118
|
+
sums.each_index do |i|
|
119
|
+
sums[i] += data[:data][i].to_f
|
126
120
|
end
|
127
|
-
|
128
|
-
|
121
|
+
end
|
122
|
+
max = sums.max
|
123
|
+
else
|
124
|
+
# compact removes nil values when computing the max
|
125
|
+
max = @data.collect{ |x|
|
126
|
+
x[:data].compact.max
|
127
|
+
}.max
|
129
128
|
end
|
129
|
+
|
130
|
+
return max
|
131
|
+
end
|
130
132
|
|
131
133
|
def min_value
|
132
134
|
min = 0
|
133
|
-
|
134
|
-
if (min_scale_value.nil?
|
135
|
+
# compact removes nil values
|
136
|
+
if (!min_scale_value.nil?) then
|
135
137
|
min = min_scale_value
|
136
138
|
elsif (stacked == true) then
|
137
|
-
min = @data[-1][:data].min
|
139
|
+
min = @data[-1][:data].compact.min
|
138
140
|
else
|
139
|
-
min = @data.collect{|x| x[:data].min}.min
|
141
|
+
min = @data.collect{|x| x[:data].compact.min}.min
|
140
142
|
end
|
141
143
|
|
142
144
|
return min
|
@@ -155,58 +157,75 @@ module SVG
|
|
155
157
|
def get_y_labels
|
156
158
|
maxvalue = max_value
|
157
159
|
minvalue = min_value
|
160
|
+
#
|
158
161
|
range = maxvalue - minvalue
|
159
|
-
|
162
|
+
if range == 0
|
163
|
+
top_pad = 10
|
164
|
+
else
|
165
|
+
top_pad = range / 20.0
|
166
|
+
end
|
160
167
|
scale_range = (maxvalue + top_pad) - minvalue
|
161
168
|
|
162
|
-
|
169
|
+
@y_scale_division = scale_divisions || (scale_range / 10.0)
|
163
170
|
|
164
171
|
if scale_integers
|
165
|
-
|
172
|
+
# only use integers if there will be at least 3 labels and division is > 0.5
|
173
|
+
if maxvalue/@y_scale_division >= 3 && @y_scale_division > 0.5
|
174
|
+
@y_scale_division = @y_scale_division.round
|
175
|
+
end
|
166
176
|
end
|
167
177
|
|
168
178
|
rv = []
|
169
|
-
|
170
|
-
|
171
|
-
|
179
|
+
# make sure we have at least one label higher than the max_value
|
180
|
+
if maxvalue%@y_scale_division != 0
|
181
|
+
maxvalue = maxvalue + @y_scale_division
|
182
|
+
end
|
183
|
+
minvalue.step( maxvalue, @y_scale_division ) {|v| rv << v}
|
172
184
|
return rv
|
173
185
|
end
|
174
186
|
|
175
187
|
def calc_coords(field, value, width = field_width, height = field_height)
|
176
188
|
coords = {:x => 0, :y => 0}
|
177
189
|
coords[:x] = width * field
|
178
|
-
coords[:y] = @graph_height - value * height
|
190
|
+
coords[:y] = @graph_height - value/@y_scale_division * height
|
179
191
|
|
180
192
|
return coords
|
181
193
|
end
|
182
194
|
|
183
195
|
def draw_data
|
184
196
|
minvalue = min_value
|
185
|
-
fieldheight = (@graph_height.to_f - font_size*2*top_font) /
|
186
|
-
|
197
|
+
#fieldheight = (@graph_height.to_f - font_size*2*top_font) /
|
198
|
+
# (get_y_labels.max - get_y_labels.min)
|
199
|
+
fieldheight = field_height
|
187
200
|
fieldwidth = field_width
|
188
201
|
line = @data.length
|
189
202
|
|
190
203
|
prev_sum = Array.new(@config[:fields].length).fill(0)
|
191
|
-
cum_sum = Array.new(@config[:fields].length).fill(
|
204
|
+
cum_sum = Array.new(@config[:fields].length).fill(nil)
|
192
205
|
|
193
206
|
for data in @data.reverse
|
194
207
|
lpath = ""
|
195
208
|
apath = ""
|
196
|
-
|
197
|
-
if not stacked then cum_sum.fill(-minvalue) end
|
198
209
|
|
199
|
-
|
200
|
-
|
201
|
-
|
210
|
+
# reset cum_sum if we are not in a stacked graph
|
211
|
+
if not stacked then cum_sum.fill(nil) end
|
212
|
+
|
213
|
+
# only consider as many datapoints as we have fields
|
214
|
+
@config[:fields].each_index do |i|
|
215
|
+
next if data[:data][i].nil?
|
216
|
+
if cum_sum[i].nil? #first time init
|
217
|
+
cum_sum[i] = data[:data][i] - minvalue
|
218
|
+
else # in case of stacked
|
219
|
+
cum_sum[i] += data[:data][i]
|
220
|
+
end
|
202
221
|
c = calc_coords(i, cum_sum[i], fieldwidth, fieldheight)
|
203
|
-
|
204
222
|
lpath << "#{c[:x]} #{c[:y]} "
|
205
223
|
end
|
206
224
|
|
207
225
|
if area_fill
|
208
226
|
if stacked then
|
209
227
|
(prev_sum.length - 1).downto 0 do |i|
|
228
|
+
next if prev_sum[i].nil?
|
210
229
|
c = calc_coords(i, prev_sum[i], fieldwidth, fieldheight)
|
211
230
|
|
212
231
|
apath << "#{c[:x]} #{c[:y]} "
|
@@ -229,21 +248,27 @@ module SVG
|
|
229
248
|
"class" => "line#{line}"
|
230
249
|
})
|
231
250
|
|
232
|
-
if show_data_points || show_data_values
|
251
|
+
if show_data_points || show_data_values || add_popups
|
233
252
|
cum_sum.each_index do |i|
|
253
|
+
# skip datapoint if nil
|
254
|
+
next if cum_sum[i].nil?
|
255
|
+
c = calc_coords(i, cum_sum[i], fieldwidth, fieldheight)
|
234
256
|
if show_data_points
|
235
257
|
@graph.add_element( "circle", {
|
236
|
-
"cx" => (fieldwidth * i).to_s,
|
237
|
-
"cy" => (@graph_height - cum_sum[i] * fieldheight).to_s,
|
258
|
+
# "cx" => (fieldwidth * i).to_s,
|
259
|
+
# "cy" => (@graph_height - cum_sum[i] * fieldheight).to_s,
|
260
|
+
"cx" => c[:x].to_s,
|
261
|
+
"cy" => c[:y].to_s,
|
238
262
|
"r" => "2.5",
|
239
263
|
"class" => "dataPoint#{line}"
|
240
264
|
})
|
241
265
|
end
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
)
|
266
|
+
#x = fieldwidth * i
|
267
|
+
#y = @graph_height - cum_sum[i] * fieldheight
|
268
|
+
#make_datapoint_text( x, y - font_size/2, cum_sum[i] + minvalue)
|
269
|
+
#add_popup(x, y, cum_sum[i] + minvalue)
|
270
|
+
make_datapoint_text( c[:x], c[:y] - font_size/2, cum_sum[i] + minvalue)
|
271
|
+
add_popup(c[:x], c[:y], cum_sum[i] + minvalue)
|
247
272
|
end
|
248
273
|
end
|
249
274
|
|
data/lib/SVG/Graph/Pie.rb
CHANGED
@@ -108,10 +108,17 @@ module SVG
|
|
108
108
|
# is the same as:
|
109
109
|
#
|
110
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
|
+
#
|
111
116
|
def add_data arg
|
112
117
|
arg[:data].each_index {|idx|
|
113
118
|
@data[idx] = 0 unless @data[idx]
|
114
|
-
|
119
|
+
if !arg[:data][idx].nil?
|
120
|
+
@data[idx] += arg[:data][idx]
|
121
|
+
end
|
115
122
|
}
|
116
123
|
end
|
117
124
|
|
@@ -160,10 +167,12 @@ module SVG
|
|
160
167
|
def draw_graph
|
161
168
|
end
|
162
169
|
|
170
|
+
# We don't have axis labels
|
163
171
|
def get_y_labels
|
164
172
|
[""]
|
165
173
|
end
|
166
174
|
|
175
|
+
# We don't have axis labels
|
167
176
|
def get_x_labels
|
168
177
|
[""]
|
169
178
|
end
|
@@ -195,7 +204,8 @@ module SVG
|
|
195
204
|
diameter -= 10 if show_shadow
|
196
205
|
radius = diameter / 2.0
|
197
206
|
|
198
|
-
xoff = (width - diameter) / 2
|
207
|
+
#xoff = (width - diameter) / 2
|
208
|
+
xoff = (@graph_width - diameter) / 2
|
199
209
|
yoff = (height - @border_bottom - diameter)
|
200
210
|
yoff -= 10 if show_shadow
|
201
211
|
@graph.attributes['transform'] = "translate( #{xoff} #{yoff} )"
|
data/lib/SVG/Graph/Plot.rb
CHANGED
@@ -102,7 +102,7 @@ module SVG
|
|
102
102
|
:show_lines => true,
|
103
103
|
:round_popups => true
|
104
104
|
)
|
105
|
-
self.top_align = self.right_align = self.top_font = self.right_font = 1
|
105
|
+
# self.top_align = self.right_align = self.top_font = self.right_font = 1
|
106
106
|
end
|
107
107
|
|
108
108
|
# Determines the scaling for the X axis divisions.
|
@@ -143,32 +143,41 @@ module SVG
|
|
143
143
|
|
144
144
|
|
145
145
|
# Adds data to the plot. The data must be in X,Y pairs; EG
|
146
|
-
# [ 1, 2 ] # A data set with 1 point: (1,2)
|
147
|
-
# [ 1,2, 5,6] # A data set with 2 points: (1,2) and (5,6)
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
146
|
+
# data_set1 = [ 1, 2 ] # A data set with 1 point: (1,2)
|
147
|
+
# data_set2 = [ 1,2, 5,6] # A data set with 2 points: (1,2) and (5,6)
|
148
|
+
#
|
149
|
+
# graph.add_data({
|
150
|
+
# :data => data_set1,
|
151
|
+
# :title => 'single point'
|
152
|
+
# })
|
153
|
+
# graph.add_data({
|
154
|
+
# :data => data_set2,
|
155
|
+
# :title => 'two points'
|
156
|
+
# })
|
157
|
+
def add_data(conf)
|
158
|
+
@data ||= []
|
159
|
+
# remove nil values
|
160
|
+
conf[:data] = conf[:data].compact
|
161
|
+
raise "No data provided by #{conf.inspect}" unless conf[:data] and
|
162
|
+
conf[:data].kind_of? Array
|
154
163
|
raise "Data supplied must be x,y pairs! "+
|
155
164
|
"The data provided contained an odd set of "+
|
156
|
-
"data points" unless
|
157
|
-
return if
|
165
|
+
"data points" unless conf[:data].length % 2 == 0
|
166
|
+
return if conf[:data].length == 0
|
158
167
|
|
159
|
-
|
160
|
-
if
|
161
|
-
raise "Description for popups does not have same size as provided data: #{
|
168
|
+
conf[:description] ||= Array.new(conf[:data].size/2)
|
169
|
+
if conf[:description].size != conf[:data].size/2
|
170
|
+
raise "Description for popups does not have same size as provided data: #{conf[:description].size} vs #{conf[:data].size/2}"
|
162
171
|
end
|
163
172
|
|
164
173
|
x = []
|
165
174
|
y = []
|
166
|
-
|
167
|
-
(i%2 == 0 ? x : y) <<
|
175
|
+
conf[:data].each_index {|i|
|
176
|
+
(i%2 == 0 ? x : y) << conf[:data][i]
|
168
177
|
}
|
169
|
-
sort( x, y,
|
170
|
-
|
171
|
-
@data <<
|
178
|
+
sort( x, y, conf[:description] )
|
179
|
+
conf[:data] = [x,y]
|
180
|
+
@data << conf
|
172
181
|
end
|
173
182
|
|
174
183
|
protected
|
@@ -231,11 +240,13 @@ module SVG
|
|
231
240
|
alias :get_x_labels :get_x_values
|
232
241
|
|
233
242
|
def field_width
|
243
|
+
# exclude values which are outside max_x_range
|
234
244
|
values = get_x_values
|
235
245
|
max = max_x_range
|
236
246
|
dx = (max - values[-1]).to_f / (values[-1] - values[-2])
|
237
|
-
(@graph_width.to_f - font_size*2*right_font) /
|
238
|
-
|
247
|
+
#(@graph_width.to_f - font_size*2*right_font) /
|
248
|
+
# (values.length + dx - right_align)
|
249
|
+
@graph_width.to_f / values.length
|
239
250
|
end
|
240
251
|
|
241
252
|
|
@@ -283,6 +294,7 @@ module SVG
|
|
283
294
|
alias :get_y_labels :get_y_values
|
284
295
|
|
285
296
|
def field_height
|
297
|
+
# exclude values which are outside max_x_range
|
286
298
|
values = get_y_values
|
287
299
|
max = max_y_range
|
288
300
|
if values.length == 1
|
@@ -290,8 +302,9 @@ module SVG
|
|
290
302
|
else
|
291
303
|
dx = (max - values[-1]).to_f / (values[-1] - values[-2])
|
292
304
|
end
|
293
|
-
(@graph_height.to_f - font_size*2*top_font) /
|
294
|
-
|
305
|
+
#(@graph_height.to_f - font_size*2*top_font) /
|
306
|
+
# (values.length + dx - top_align)
|
307
|
+
@graph_height.to_f / values.length
|
295
308
|
end
|
296
309
|
|
297
310
|
def draw_data
|
@@ -330,7 +343,7 @@ module SVG
|
|
330
343
|
})
|
331
344
|
end
|
332
345
|
|
333
|
-
if show_data_points || show_data_values
|
346
|
+
if show_data_points || show_data_values || add_popups
|
334
347
|
x_points.each_index { |idx|
|
335
348
|
x = (x_points[idx] - x_min) * x_step
|
336
349
|
y = @graph_height - (y_points[idx] - y_min) * y_step
|
@@ -338,9 +351,9 @@ module SVG
|
|
338
351
|
DataPoint.new(x, y, line).shape(data[:description][idx]).each{|s|
|
339
352
|
@graph.add_element( *s )
|
340
353
|
}
|
341
|
-
add_popup(x, y, format( x_points[idx], y_points[idx], data[:description][idx])) if add_popups
|
342
354
|
end
|
343
|
-
make_datapoint_text( x, y-6, y_points[idx] )
|
355
|
+
make_datapoint_text( x, y-6, y_points[idx] )
|
356
|
+
add_popup(x, y, format( x_points[idx], y_points[idx], data[:description][idx]))
|
344
357
|
}
|
345
358
|
end
|
346
359
|
line += 1
|
data/lib/svggraph.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: svg-graph
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.0.
|
4
|
+
version: 2.0.2.beta
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sean Russell
|
@@ -54,7 +54,7 @@ files:
|
|
54
54
|
- test/test_svg_graph.rb
|
55
55
|
homepage: http://www.germane-software.com/software/SVG/SVG::Graph/
|
56
56
|
licenses:
|
57
|
-
- GPL
|
57
|
+
- GPL-2.0
|
58
58
|
metadata: {}
|
59
59
|
post_install_message:
|
60
60
|
rdoc_options: []
|
@@ -67,12 +67,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
67
67
|
version: '0'
|
68
68
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
69
69
|
requirements:
|
70
|
-
- - "
|
70
|
+
- - ">"
|
71
71
|
- !ruby/object:Gem::Version
|
72
|
-
version:
|
72
|
+
version: 1.3.1
|
73
73
|
requirements: []
|
74
74
|
rubyforge_project:
|
75
|
-
rubygems_version: 2.
|
75
|
+
rubygems_version: 2.6.7
|
76
76
|
signing_key:
|
77
77
|
specification_version: 4
|
78
78
|
summary: SVG:::Graph is a pure Ruby library for generating charts, which are a type
|