gruff 0.2.8 → 0.2.9
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +5 -1
- data/Manifest.txt +15 -10
- data/Rakefile +20 -3
- data/assets/bubble.png +0 -0
- data/assets/plastik/blue.png +0 -0
- data/assets/plastik/green.png +0 -0
- data/assets/plastik/red.png +0 -0
- data/lib/gruff.rb +1 -0
- data/lib/gruff/base.rb +213 -147
- data/lib/gruff/line.rb +11 -0
- data/lib/gruff/mini/legend.rb +3 -3
- data/lib/gruff/mini/side_bar.rb +13 -0
- data/lib/gruff/pie.rb +25 -11
- data/lib/gruff/scene.rb +15 -3
- data/lib/gruff/side_bar.rb +58 -61
- data/lib/gruff/side_stacked_bar.rb +51 -99
- data/lib/gruff/stacked_area.rb +66 -0
- data/lib/gruff/stacked_bar.rb +8 -4
- data/lib/gruff/stacked_mixin.rb +23 -0
- data/test/gruff_test_case.rb +1 -0
- data/test/test_pie.rb +25 -0
- metadata +41 -19
data/lib/gruff/line.rb
CHANGED
@@ -1,6 +1,17 @@
|
|
1
1
|
|
2
2
|
require File.dirname(__FILE__) + '/base'
|
3
3
|
|
4
|
+
##
|
5
|
+
# Here's how to make a Line graph:
|
6
|
+
#
|
7
|
+
# g = Gruff::Line.new
|
8
|
+
# g.title = "A Line Graph"
|
9
|
+
# g.data 'Fries', [20, 23, 19, 8]
|
10
|
+
# g.data 'Hamburgers', [50, 19, 99, 29]
|
11
|
+
# g.write("test/output/line.png")
|
12
|
+
#
|
13
|
+
# There are also other options described below, such as #baseline_value, #baseline_color, #hide_dots, and #hide_lines.
|
14
|
+
|
4
15
|
class Gruff::Line < Gruff::Base
|
5
16
|
|
6
17
|
# Draw a dashed line at the given value
|
data/lib/gruff/mini/legend.rb
CHANGED
@@ -20,14 +20,14 @@ module Gruff
|
|
20
20
|
|
21
21
|
legend_square_width = 40.0 # small square with color of this item
|
22
22
|
legend_square_margin = 10.0
|
23
|
-
@legend_left_margin =
|
23
|
+
@legend_left_margin = 100.0
|
24
24
|
legend_top_margin = 40.0
|
25
25
|
|
26
26
|
# May fix legend drawing problem at small sizes
|
27
27
|
@d.font = @font if @font
|
28
28
|
@d.pointsize = @legend_font_size
|
29
29
|
|
30
|
-
current_x_offset = @
|
30
|
+
current_x_offset = @legend_left_margin
|
31
31
|
current_y_offset = @original_rows + legend_top_margin
|
32
32
|
|
33
33
|
debug { @d.line 0.0, current_y_offset, @raw_columns, current_y_offset }
|
@@ -66,7 +66,7 @@ module Gruff
|
|
66
66
|
|
67
67
|
def truncate_legend_label(label)
|
68
68
|
truncated_label = label.to_s
|
69
|
-
while calculate_width(scale_fontsize(@legend_font_size), truncated_label) > (@columns - @legend_left_margin -
|
69
|
+
while calculate_width(scale_fontsize(@legend_font_size), truncated_label) > (@columns - @legend_left_margin - @right_margin) && (truncated_label.length > 1)
|
70
70
|
truncated_label = truncated_label[0..truncated_label.length-2]
|
71
71
|
end
|
72
72
|
truncated_label + (truncated_label.length < label.to_s.length ? "…" : '')
|
data/lib/gruff/mini/side_bar.rb
CHANGED
@@ -7,6 +7,8 @@ module Gruff
|
|
7
7
|
|
8
8
|
class SideBar < Gruff::SideBar
|
9
9
|
|
10
|
+
include Gruff::Mini::Legend
|
11
|
+
|
10
12
|
def initialize_ivars
|
11
13
|
super
|
12
14
|
@hide_legend = true
|
@@ -14,6 +16,17 @@ module Gruff
|
|
14
16
|
@hide_line_numbers = true
|
15
17
|
|
16
18
|
@marker_font_size = 50.0
|
19
|
+
@legend_font_size = 50.0
|
20
|
+
end
|
21
|
+
|
22
|
+
def draw
|
23
|
+
expand_canvas_for_vertical_legend
|
24
|
+
|
25
|
+
super
|
26
|
+
|
27
|
+
draw_vertical_legend
|
28
|
+
|
29
|
+
@d.draw(@base_image)
|
17
30
|
end
|
18
31
|
|
19
32
|
end
|
data/lib/gruff/pie.rb
CHANGED
@@ -1,6 +1,16 @@
|
|
1
|
-
|
2
1
|
require File.dirname(__FILE__) + '/base'
|
3
2
|
|
3
|
+
##
|
4
|
+
# Here's how to make a Pie graph:
|
5
|
+
#
|
6
|
+
# g = Gruff::Pie.new
|
7
|
+
# g.title = "Visual Pie Graph Test"
|
8
|
+
# g.data 'Fries', 20
|
9
|
+
# g.data 'Hamburgers', 50
|
10
|
+
# g.write("test/output/pie_keynote.png")
|
11
|
+
#
|
12
|
+
# To control where the pie chart starts creating slices, use #zero_degree.
|
13
|
+
|
4
14
|
class Gruff::Pie < Gruff::Base
|
5
15
|
|
6
16
|
TEXT_OFFSET_PERCENTAGE = 0.15
|
@@ -8,7 +18,7 @@ class Gruff::Pie < Gruff::Base
|
|
8
18
|
# Can be used to make the pie start cutting slices at the top (-90.0)
|
9
19
|
# or at another angle. Default is 0.0, which starts at 3 o'clock.
|
10
20
|
attr_accessor :zero_degree
|
11
|
-
|
21
|
+
|
12
22
|
def initialize_ivars
|
13
23
|
super
|
14
24
|
@zero_degree = 0.0
|
@@ -30,7 +40,8 @@ class Gruff::Pie < Gruff::Base
|
|
30
40
|
prev_degrees = @zero_degree
|
31
41
|
|
32
42
|
# Use full data since we can easily calculate percentages
|
33
|
-
@data.sort{ |a, b| a[DATA_VALUES_INDEX][0] <=> b[DATA_VALUES_INDEX][0] }
|
43
|
+
data = (@sort ? @data.sort{ |a, b| a[DATA_VALUES_INDEX][0] <=> b[DATA_VALUES_INDEX][0] } : @data)
|
44
|
+
data.each do |data_row|
|
34
45
|
if data_row[DATA_VALUES_INDEX][0] > 0
|
35
46
|
@d = @d.stroke data_row[DATA_COLOR_INDEX]
|
36
47
|
@d = @d.fill 'transparent'
|
@@ -47,13 +58,16 @@ class Gruff::Pie < Gruff::Base
|
|
47
58
|
|
48
59
|
half_angle = prev_degrees + ((prev_degrees + current_degrees) - prev_degrees) / 2
|
49
60
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
61
|
+
unless @hide_line_markers then
|
62
|
+
# End the string with %% to escape the single %.
|
63
|
+
# RMagick must use sprintf with the string and % has special significance.
|
64
|
+
label_string = ((data_row[DATA_VALUES_INDEX][0] / total_sum) *
|
65
|
+
100.0).round.to_s + '%%'
|
66
|
+
@d = draw_label(center_x,center_y, half_angle,
|
67
|
+
radius + (radius * TEXT_OFFSET_PERCENTAGE),
|
68
|
+
label_string)
|
69
|
+
end
|
70
|
+
|
57
71
|
prev_degrees += current_degrees
|
58
72
|
end
|
59
73
|
end
|
@@ -99,10 +113,10 @@ private
|
|
99
113
|
|
100
114
|
end
|
101
115
|
|
102
|
-
|
103
116
|
class Float
|
104
117
|
# Used for degree => radian conversions
|
105
118
|
def deg2rad
|
106
119
|
self * (Math::PI/180.0)
|
107
120
|
end
|
108
121
|
end
|
122
|
+
|
data/lib/gruff/scene.rb
CHANGED
@@ -14,19 +14,31 @@ require File.dirname(__FILE__) + '/base'
|
|
14
14
|
#
|
15
15
|
# Usage:
|
16
16
|
#
|
17
|
-
# g = Gruff::Scene.new("500x100", "
|
17
|
+
# g = Gruff::Scene.new("500x100", "path/to/city_scene_directory")
|
18
|
+
#
|
19
|
+
# # Define order of layers, back to front
|
18
20
|
# g.layers = %w(background haze sky clouds)
|
21
|
+
#
|
22
|
+
# # Define groups that will be controlled by the same input
|
19
23
|
# g.weather_group = %w(clouds)
|
20
24
|
# g.time_group = %w(background sky)
|
25
|
+
#
|
26
|
+
# # Set values for the layers or groups
|
21
27
|
# g.weather = "cloudy"
|
22
28
|
# g.time = Time.now
|
23
29
|
# g.haze = true
|
24
|
-
# g.write "hazy_daytime_city_scene.png"
|
25
30
|
#
|
31
|
+
# # Write the final graph to disk
|
32
|
+
# g.write "hazy_daytime_city_scene.png"
|
26
33
|
#
|
27
34
|
#
|
28
|
-
#
|
35
|
+
# There are several rules that will magically select a layer when possible.
|
29
36
|
#
|
37
|
+
# * Numbered files will be selected according to the closest value that is less than the input value.
|
38
|
+
# * 'true.png' and 'false.png' will be used as booleans.
|
39
|
+
# * Other named files will be used if the input matches the filename (without the filetype extension).
|
40
|
+
# * If there is a file named 'default.png', it will be used unless other input values are set for the corresponding layer.
|
41
|
+
|
30
42
|
class Gruff::Scene < Gruff::Base
|
31
43
|
|
32
44
|
# An array listing the foldernames that will be rendered, from back to front.
|
data/lib/gruff/side_bar.rb
CHANGED
@@ -1,21 +1,66 @@
|
|
1
1
|
require File.dirname(__FILE__) + '/base'
|
2
2
|
|
3
3
|
##
|
4
|
-
#
|
5
|
-
|
6
|
-
# Graph with horizontal bars instead of vertical.
|
7
|
-
#
|
8
|
-
# TODO SideStackedBar should probably inherit from this
|
9
|
-
# to consolidate the line marker drawing.
|
4
|
+
# Graph with individual horizontal bars instead of vertical bars.
|
5
|
+
|
10
6
|
class Gruff::SideBar < Gruff::Base
|
11
7
|
|
8
|
+
def draw
|
9
|
+
@has_left_labels = true
|
10
|
+
super
|
11
|
+
|
12
|
+
return unless @has_data
|
13
|
+
|
14
|
+
# Setup spacing.
|
15
|
+
#
|
16
|
+
spacing_factor = 0.9
|
17
|
+
|
18
|
+
@bars_width = @graph_height / @column_count.to_f
|
19
|
+
@bar_width = @bars_width * spacing_factor / @norm_data.size
|
20
|
+
@d = @d.stroke_opacity 0.0
|
21
|
+
height = Array.new(@column_count, 0)
|
22
|
+
length = Array.new(@column_count, @graph_left)
|
23
|
+
|
24
|
+
@norm_data.each_with_index do |data_row, row_index|
|
25
|
+
@d = @d.fill data_row[DATA_COLOR_INDEX]
|
26
|
+
|
27
|
+
data_row[1].each_with_index do |data_point, point_index|
|
28
|
+
|
29
|
+
# Using the original calcs from the stacked bar chart
|
30
|
+
# to get the difference between
|
31
|
+
# part of the bart chart we wish to stack.
|
32
|
+
temp1 = @graph_left + (@graph_width - data_point * @graph_width - height[point_index])
|
33
|
+
temp2 = @graph_left + @graph_width - height[point_index]
|
34
|
+
difference = temp2 - temp1
|
35
|
+
|
36
|
+
left_x = length[point_index] - 1
|
37
|
+
left_y = @graph_top + (@bars_width * point_index) + (@bar_width * row_index)
|
38
|
+
right_x = left_x + difference
|
39
|
+
right_y = left_y + @bar_width
|
40
|
+
|
41
|
+
height[point_index] += (data_point * @graph_width)
|
42
|
+
|
43
|
+
@d = @d.rectangle(left_x, left_y, right_x, right_y)
|
44
|
+
|
45
|
+
# Calculate center based on bar_width and current row
|
46
|
+
label_center = @graph_top + (@bars_width * point_index + @bars_width / 2)
|
47
|
+
draw_label(label_center, point_index)
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
@d.draw(@base_image)
|
53
|
+
end
|
54
|
+
|
55
|
+
protected
|
56
|
+
|
12
57
|
# Instead of base class version, draws vertical background lines and label
|
13
58
|
def draw_line_markers
|
14
59
|
|
15
60
|
return if @hide_line_markers
|
16
61
|
|
17
62
|
@d = @d.stroke_antialias false
|
18
|
-
|
63
|
+
|
19
64
|
# Draw horizontal line markers and annotate with numbers
|
20
65
|
@d = @d.stroke(@marker_color)
|
21
66
|
@d = @d.stroke_width 1
|
@@ -36,10 +81,11 @@ class Gruff::SideBar < Gruff::Base
|
|
36
81
|
@d.font = @font if @font
|
37
82
|
@d.stroke = 'transparent'
|
38
83
|
@d.pointsize = scale_fontsize(@marker_font_size)
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
84
|
+
@d.gravity = CenterGravity
|
85
|
+
# TODO Center text over line
|
86
|
+
@d = @d.annotate_scaled( @base_image,
|
87
|
+
0, 0, # Width of box to draw text in
|
88
|
+
x, @graph_bottom + (LABEL_MARGIN * 2.0), # Coordinates of text
|
43
89
|
marker_label.to_s, @scale)
|
44
90
|
end # unless
|
45
91
|
@d = @d.stroke_antialias true
|
@@ -48,7 +94,7 @@ class Gruff::SideBar < Gruff::Base
|
|
48
94
|
|
49
95
|
##
|
50
96
|
# Draw on the Y axis instead of the X
|
51
|
-
|
97
|
+
|
52
98
|
def draw_label(y_offset, index)
|
53
99
|
if !@labels[index].nil? && @labels_seen[index].nil?
|
54
100
|
@d.fill = @font_color
|
@@ -65,53 +111,4 @@ class Gruff::SideBar < Gruff::Base
|
|
65
111
|
end
|
66
112
|
end
|
67
113
|
|
68
|
-
def draw
|
69
|
-
@has_left_labels = true
|
70
|
-
super
|
71
|
-
|
72
|
-
return unless @has_data
|
73
|
-
|
74
|
-
# Setup spacing.
|
75
|
-
#
|
76
|
-
# Columns sit stacked.
|
77
|
-
spacing_factor = 0.9
|
78
|
-
|
79
|
-
@bar_width = @graph_height / @column_count.to_f
|
80
|
-
@d = @d.stroke_opacity 0.0
|
81
|
-
height = Array.new(@column_count, 0)
|
82
|
-
length = Array.new(@column_count, @graph_left)
|
83
|
-
|
84
|
-
@norm_data.each_with_index do |data_row, row_index|
|
85
|
-
@d = @d.fill data_row[DATA_COLOR_INDEX]
|
86
|
-
|
87
|
-
data_row[1].each_with_index do |data_point, point_index|
|
88
|
-
|
89
|
-
# Using the original calcs from the stacked bar chart
|
90
|
-
# to get the difference between
|
91
|
-
# part of the bart chart we wish to stack.
|
92
|
-
temp1 = @graph_left + (@graph_width -
|
93
|
-
data_point * @graph_width -
|
94
|
-
height[point_index]) + 1
|
95
|
-
temp2 = @graph_left + @graph_width - height[point_index] - 1
|
96
|
-
difference = temp2 - temp1
|
97
|
-
|
98
|
-
left_x = length[point_index] #+ 1
|
99
|
-
left_y = @graph_top + (@bar_width * point_index)
|
100
|
-
right_x = left_x + difference
|
101
|
-
right_y = left_y + @bar_width * spacing_factor
|
102
|
-
length[point_index] += difference
|
103
|
-
height[point_index] += (data_point * @graph_width - 2)
|
104
|
-
|
105
|
-
@d = @d.rectangle(left_x, left_y, right_x, right_y)
|
106
|
-
|
107
|
-
# Calculate center based on bar_width and current row
|
108
|
-
label_center = @graph_top + (@bar_width * point_index) + (@bar_width * spacing_factor / 2.0)
|
109
|
-
draw_label(label_center, point_index)
|
110
|
-
end
|
111
|
-
|
112
|
-
end
|
113
|
-
|
114
|
-
@d.draw(@base_image)
|
115
|
-
end
|
116
|
-
|
117
114
|
end
|
@@ -1,121 +1,73 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/base'
|
2
|
+
require File.dirname(__FILE__) + '/side_bar'
|
3
|
+
require File.dirname(__FILE__) + '/stacked_mixin'
|
4
|
+
|
1
5
|
##
|
2
|
-
# New gruff graph type added to enable sideways stacking bar charts
|
3
|
-
# flip of a standard stacking bar chart)
|
6
|
+
# New gruff graph type added to enable sideways stacking bar charts
|
7
|
+
# (basically looks like a x/y flip of a standard stacking bar chart)
|
4
8
|
#
|
5
9
|
# alun.eyre@googlemail.com
|
6
|
-
#
|
7
|
-
require File.dirname(__FILE__) + '/base'
|
8
|
-
|
9
|
-
class Gruff::SideStackedBar < Gruff::Base
|
10
|
-
|
11
|
-
# instead of base class version, draws vertical background lines and label
|
12
|
-
def draw_line_markers
|
13
10
|
|
14
|
-
|
11
|
+
class Gruff::SideStackedBar < Gruff::SideBar
|
12
|
+
include StackedMixin
|
15
13
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
14
|
+
def draw
|
15
|
+
@has_left_labels = true
|
16
|
+
get_maximum_by_stack
|
17
|
+
super
|
20
18
|
|
21
|
-
|
22
|
-
increment = significant(@maximum_value.to_f / number_of_lines)
|
23
|
-
(0..number_of_lines).each do |index|
|
19
|
+
return unless @has_data
|
24
20
|
|
25
|
-
|
26
|
-
|
27
|
-
|
21
|
+
# Setup spacing.
|
22
|
+
#
|
23
|
+
# Columns sit stacked.
|
24
|
+
spacing_factor = 0.9
|
28
25
|
|
29
|
-
|
30
|
-
|
26
|
+
@bar_width = @graph_height / @column_count.to_f
|
27
|
+
@d = @d.stroke_opacity 0.0
|
28
|
+
height = Array.new(@column_count, 0)
|
29
|
+
length = Array.new(@column_count, @graph_left)
|
31
30
|
|
32
|
-
|
33
|
-
|
34
|
-
@d.stroke = 'transparent'
|
35
|
-
@d.pointsize = scale_fontsize(@marker_font_size)
|
36
|
-
# @d.gravity = NorthGravity
|
37
|
-
@d = @d.annotate_scaled( @base_image,
|
38
|
-
100, 20,
|
39
|
-
x - (@marker_font_size/1.5), @graph_bottom + 40,
|
40
|
-
marker_label.to_s, @scale)
|
31
|
+
@norm_data.each_with_index do |data_row, row_index|
|
32
|
+
@d = @d.fill data_row[DATA_COLOR_INDEX]
|
41
33
|
|
42
|
-
|
43
|
-
end
|
44
|
-
|
45
|
-
# instead of base class version, modified to enable us to draw on the Y axis instead of X
|
46
|
-
def draw_label(y_offset, index)
|
47
|
-
if !@labels[index].nil? && @labels_seen[index].nil?
|
48
|
-
@d.fill = @marker_color
|
49
|
-
@d.font = @font if @font
|
50
|
-
@d.stroke = 'transparent'
|
51
|
-
@d.font_weight = NormalWeight
|
52
|
-
@d.pointsize = scale_fontsize(@marker_font_size)
|
53
|
-
@d.gravity = CenterGravity
|
54
|
-
@d = @d.annotate_scaled(@base_image,
|
55
|
-
1, 1,
|
56
|
-
@graph_left / 2, y_offset,
|
57
|
-
@labels[index], @scale)
|
58
|
-
@labels_seen[index] = 1
|
59
|
-
end
|
60
|
-
end
|
34
|
+
data_row[1].each_with_index do |data_point, point_index|
|
61
35
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
36
|
+
## using the original calcs from the stacked bar chart to get the difference between
|
37
|
+
## part of the bart chart we wish to stack.
|
38
|
+
temp1 = @graph_left + (@graph_width -
|
39
|
+
data_point * @graph_width -
|
40
|
+
height[point_index]) + 1
|
41
|
+
temp2 = @graph_left + @graph_width - height[point_index] - 1
|
42
|
+
difference = temp2 - temp1
|
66
43
|
|
67
|
-
|
44
|
+
left_x = length[point_index] #+ 1
|
45
|
+
left_y = @graph_top + (@bar_width * point_index)
|
46
|
+
right_x = left_x + difference
|
47
|
+
right_y = left_y + @bar_width * spacing_factor
|
48
|
+
length[point_index] += difference
|
49
|
+
height[point_index] += (data_point * @graph_width - 2)
|
68
50
|
|
69
|
-
|
70
|
-
#
|
71
|
-
# Columns sit stacked.
|
72
|
-
spacing_factor = 0.9
|
73
|
-
|
74
|
-
@bar_width = @graph_height / @column_count.to_f
|
75
|
-
@d = @d.stroke_opacity 0.0
|
76
|
-
height = Array.new(@column_count, 0)
|
77
|
-
length = Array.new(@column_count, @graph_left)
|
78
|
-
|
79
|
-
@norm_data.each_with_index do |data_row, row_index|
|
80
|
-
@d = @d.fill data_row[DATA_COLOR_INDEX]
|
81
|
-
|
82
|
-
data_row[1].each_with_index do |data_point, point_index|
|
83
|
-
|
84
|
-
## using the original calcs from the stacked bar chart to get the difference between
|
85
|
-
## part of the bart chart we wish to stack.
|
86
|
-
temp1 = @graph_left + (@graph_width -
|
87
|
-
data_point * @graph_width -
|
88
|
-
height[point_index]) + 1
|
89
|
-
temp2 = @graph_left + @graph_width - height[point_index] - 1
|
90
|
-
difference = temp2 - temp1
|
91
|
-
|
92
|
-
left_x = length[point_index] #+ 1
|
93
|
-
left_y = @graph_top + (@bar_width * point_index)
|
94
|
-
right_x = left_x + difference
|
95
|
-
right_y = left_y + @bar_width * spacing_factor
|
96
|
-
length[point_index] += difference
|
97
|
-
height[point_index] += (data_point * @graph_width - 2)
|
98
|
-
|
99
|
-
@d = @d.rectangle(left_x, left_y, right_x, right_y)
|
100
|
-
|
101
|
-
# Calculate center based on bar_width and current row
|
102
|
-
label_center = @graph_top + (@bar_width * point_index) + (@bar_width * spacing_factor / 2.0)
|
103
|
-
draw_label(label_center, point_index)
|
104
|
-
end
|
51
|
+
@d = @d.rectangle(left_x, left_y, right_x, right_y)
|
105
52
|
|
53
|
+
# Calculate center based on bar_width and current row
|
54
|
+
label_center = @graph_top + (@bar_width * point_index) + (@bar_width * spacing_factor / 2.0)
|
55
|
+
draw_label(label_center, point_index)
|
106
56
|
end
|
107
57
|
|
108
|
-
@d.draw(@base_image)
|
109
58
|
end
|
110
59
|
|
111
|
-
|
60
|
+
@d.draw(@base_image)
|
61
|
+
end
|
112
62
|
|
113
|
-
|
114
|
-
max(data_point, index) > @maximum_value
|
115
|
-
end
|
63
|
+
protected
|
116
64
|
|
117
|
-
|
118
|
-
|
119
|
-
|
65
|
+
def larger_than_max?(data_point, index=0)
|
66
|
+
max(data_point, index) > @maximum_value
|
67
|
+
end
|
68
|
+
|
69
|
+
def max(data_point, index)
|
70
|
+
@data.inject(0) {|sum, item| sum + item[1][index]}
|
71
|
+
end
|
120
72
|
|
121
73
|
end
|