svg-graph 2.0.1 → 2.0.2.beta
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 +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
|
+

|
78
|
+
|
77
79
|
### ErrBar
|
78
80
|
|
79
81
|
### Line
|
80
82
|
|
83
|
+

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