gruff 0.11.0-java → 0.14.0-java
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/.github/workflows/ci.yml +66 -0
- data/.gitignore +1 -0
- data/.rubocop.yml +24 -8
- data/.rubocop_todo.yml +57 -53
- data/CHANGELOG.md +34 -0
- data/README.md +15 -7
- data/assets/fonts/LICENSE.txt +202 -0
- data/assets/fonts/Roboto-Bold.ttf +0 -0
- data/assets/fonts/Roboto-Regular.ttf +0 -0
- data/gruff.gemspec +8 -5
- data/lib/gruff.rb +9 -3
- data/lib/gruff/accumulator_bar.rb +3 -3
- data/lib/gruff/area.rb +5 -12
- data/lib/gruff/bar.rb +43 -47
- data/lib/gruff/base.rb +267 -146
- data/lib/gruff/bezier.rb +4 -10
- data/lib/gruff/bullet.rb +13 -19
- data/lib/gruff/dot.rb +14 -19
- data/lib/gruff/font.rb +39 -0
- data/lib/gruff/helper/bar_conversion.rb +28 -13
- data/lib/gruff/helper/bar_value_label.rb +68 -0
- data/lib/gruff/helper/stacked_mixin.rb +1 -2
- data/lib/gruff/histogram.rb +9 -8
- data/lib/gruff/line.rb +59 -56
- data/lib/gruff/mini/bar.rb +10 -7
- data/lib/gruff/mini/legend.rb +19 -10
- data/lib/gruff/mini/pie.rb +10 -8
- data/lib/gruff/mini/side_bar.rb +10 -8
- data/lib/gruff/net.rb +13 -20
- data/lib/gruff/patch/rmagick.rb +22 -24
- data/lib/gruff/patch/string.rb +7 -4
- data/lib/gruff/pie.rb +24 -70
- data/lib/gruff/renderer/bezier.rb +11 -11
- data/lib/gruff/renderer/circle.rb +11 -11
- data/lib/gruff/renderer/dash_line.rb +12 -12
- data/lib/gruff/renderer/dot.rb +16 -16
- data/lib/gruff/renderer/ellipse.rb +11 -11
- data/lib/gruff/renderer/line.rb +12 -12
- data/lib/gruff/renderer/polygon.rb +13 -13
- data/lib/gruff/renderer/polyline.rb +11 -11
- data/lib/gruff/renderer/rectangle.rb +9 -9
- data/lib/gruff/renderer/renderer.rb +23 -50
- data/lib/gruff/renderer/text.rb +32 -29
- data/lib/gruff/scatter.rb +52 -76
- data/lib/gruff/scene.rb +15 -14
- data/lib/gruff/side_bar.rb +49 -52
- data/lib/gruff/side_stacked_bar.rb +29 -20
- data/lib/gruff/spider.rb +13 -22
- data/lib/gruff/stacked_area.rb +10 -16
- data/lib/gruff/stacked_bar.rb +29 -18
- data/lib/gruff/store/{base_data.rb → basic_data.rb} +5 -7
- data/lib/gruff/store/custom_data.rb +4 -6
- data/lib/gruff/store/store.rb +9 -12
- data/lib/gruff/store/xy_data.rb +6 -7
- data/lib/gruff/themes.rb +6 -6
- data/lib/gruff/version.rb +1 -1
- data/rails_generators/gruff/templates/controller.rb +1 -1
- metadata +24 -14
- data/.travis.yml +0 -26
- data/Rakefile +0 -47
- data/assets/plastik/blue.png +0 -0
- data/assets/plastik/green.png +0 -0
- data/assets/plastik/red.png +0 -0
- data/docker/Dockerfile +0 -14
- data/docker/build.sh +0 -4
- data/docker/launch.sh +0 -4
- data/lib/gruff/helper/bar_value_label_mixin.rb +0 -30
- data/lib/gruff/photo_bar.rb +0 -97
|
Binary file
|
|
Binary file
|
data/gruff.gemspec
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
# -*- encoding: utf-8 -*-
|
|
2
1
|
# frozen_string_literal: true
|
|
3
2
|
|
|
4
3
|
lib = File.expand_path('lib')
|
|
@@ -10,10 +9,11 @@ Gem::Specification.new do |s|
|
|
|
10
9
|
s.name = 'gruff'
|
|
11
10
|
s.version = Gruff::VERSION
|
|
12
11
|
s.authors = ['Geoffrey Grosenbach', 'Uwe Kubosch']
|
|
13
|
-
s.date = Date.today.to_s
|
|
14
12
|
s.description = 'Beautiful graphs for one or multiple datasets. Can be used on websites or in documents.'
|
|
15
13
|
s.email = 'boss@topfunky.com'
|
|
16
|
-
s.files = `git ls-files`.split
|
|
14
|
+
s.files = `git ls-files`.split.reject do |f|
|
|
15
|
+
f =~ /^test|^docker|^Rakefile/i
|
|
16
|
+
end
|
|
17
17
|
s.homepage = 'https://github.com/topfunky/gruff'
|
|
18
18
|
s.require_paths = %w[lib]
|
|
19
19
|
s.summary = 'Beautiful graphs for one or multiple datasets.'
|
|
@@ -27,12 +27,15 @@ Gem::Specification.new do |s|
|
|
|
27
27
|
s.add_dependency 'rmagick4j'
|
|
28
28
|
else
|
|
29
29
|
s.add_dependency 'rmagick'
|
|
30
|
-
s.add_development_dependency 'rubocop', '~>
|
|
30
|
+
s.add_development_dependency 'rubocop', '~> 1.12.1'
|
|
31
|
+
s.add_development_dependency 'rubocop-performance', '~> 1.10.2'
|
|
32
|
+
s.add_development_dependency 'rubocop-rake', '~> 0.5.1'
|
|
31
33
|
end
|
|
32
34
|
s.add_dependency 'histogram'
|
|
33
|
-
s.required_ruby_version = '>=
|
|
35
|
+
s.required_ruby_version = '>= 2.4.0'
|
|
34
36
|
|
|
35
37
|
s.add_development_dependency 'rake'
|
|
36
38
|
s.add_development_dependency 'minitest-reporters'
|
|
39
|
+
s.add_development_dependency 'simplecov'
|
|
37
40
|
s.add_development_dependency 'yard', '~> 0.9.25'
|
|
38
41
|
end
|
data/lib/gruff.rb
CHANGED
|
@@ -9,8 +9,14 @@ require 'gruff/version'
|
|
|
9
9
|
patch/rmagick
|
|
10
10
|
patch/string
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
font
|
|
13
13
|
base
|
|
14
|
+
|
|
15
|
+
helper/bar_conversion.rb
|
|
16
|
+
helper/stacked_mixin
|
|
17
|
+
helper/bar_value_label
|
|
18
|
+
|
|
19
|
+
themes
|
|
14
20
|
area
|
|
15
21
|
bar
|
|
16
22
|
bezier
|
|
@@ -22,10 +28,10 @@ require 'gruff/version'
|
|
|
22
28
|
pie
|
|
23
29
|
scatter
|
|
24
30
|
spider
|
|
31
|
+
side_bar
|
|
25
32
|
stacked_area
|
|
26
33
|
stacked_bar
|
|
27
34
|
side_stacked_bar
|
|
28
|
-
side_bar
|
|
29
35
|
accumulator_bar
|
|
30
36
|
|
|
31
37
|
scene
|
|
@@ -43,7 +49,7 @@ require 'gruff/version'
|
|
|
43
49
|
renderer/text
|
|
44
50
|
|
|
45
51
|
store/store
|
|
46
|
-
store/
|
|
52
|
+
store/basic_data
|
|
47
53
|
store/custom_data
|
|
48
54
|
store/xy_data
|
|
49
55
|
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'gruff/base'
|
|
4
|
-
|
|
5
3
|
#
|
|
6
4
|
# Gruff::AccumulatorBar is a special bar graph that shows a
|
|
7
5
|
# single dataset as a set of stacked bars.
|
|
@@ -16,7 +14,9 @@ require 'gruff/base'
|
|
|
16
14
|
# g.write('accumulator_bar.png')
|
|
17
15
|
#
|
|
18
16
|
class Gruff::AccumulatorBar < Gruff::StackedBar
|
|
19
|
-
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def setup_data
|
|
20
20
|
raise(Gruff::IncorrectNumberOfDatasetsException) unless store.length == 1
|
|
21
21
|
|
|
22
22
|
accum_array = store.data.first.points[0..-2].reduce([0]) { |a, v| a << a.last + v }
|
data/lib/gruff/area.rb
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'gruff/base'
|
|
4
|
-
|
|
5
3
|
#
|
|
6
4
|
# Gruff::Area provides an area graph which displays graphically
|
|
7
5
|
# quantitative data.
|
|
@@ -22,19 +20,16 @@ class Gruff::Area < Gruff::Base
|
|
|
22
20
|
# Specifies the stroke width in line around area graph. Default is +2.0+.
|
|
23
21
|
attr_writer :stroke_width
|
|
24
22
|
|
|
25
|
-
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def initialize_attributes
|
|
26
26
|
super
|
|
27
27
|
@sorted_drawing = true
|
|
28
28
|
@fill_opacity = 0.85
|
|
29
29
|
@stroke_width = 2.0
|
|
30
30
|
end
|
|
31
|
-
private :initialize_ivars
|
|
32
|
-
|
|
33
|
-
def draw
|
|
34
|
-
super
|
|
35
|
-
|
|
36
|
-
return unless data_given?
|
|
37
31
|
|
|
32
|
+
def draw_graph
|
|
38
33
|
x_increment = @graph_width / (column_count - 1).to_f
|
|
39
34
|
|
|
40
35
|
store.norm_data.each do |data_row|
|
|
@@ -57,9 +52,7 @@ class Gruff::Area < Gruff::Base
|
|
|
57
52
|
poly_points << @graph_left
|
|
58
53
|
poly_points << @graph_bottom - 1
|
|
59
54
|
|
|
60
|
-
Gruff::Renderer::Polygon.new(color: data_row.color, width: @stroke_width, opacity: @fill_opacity).render(poly_points)
|
|
55
|
+
Gruff::Renderer::Polygon.new(renderer, color: data_row.color, width: @stroke_width, opacity: @fill_opacity).render(poly_points)
|
|
61
56
|
end
|
|
62
|
-
|
|
63
|
-
Gruff::Renderer.finish
|
|
64
57
|
end
|
|
65
58
|
end
|
data/lib/gruff/bar.rb
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'gruff/base'
|
|
4
|
-
require 'gruff/helper/bar_conversion'
|
|
5
|
-
|
|
6
3
|
#
|
|
7
4
|
# Gruff::Bar provide a bar graph that presents categorical data
|
|
8
5
|
# with rectangular bars.
|
|
@@ -25,7 +22,7 @@ class Gruff::Bar < Gruff::Base
|
|
|
25
22
|
# Spacing factor applied between a group of bars belonging to the same label.
|
|
26
23
|
attr_writer :group_spacing
|
|
27
24
|
|
|
28
|
-
# Set the number output format
|
|
25
|
+
# Set the number output format string or lambda.
|
|
29
26
|
# Default is +"%.2f"+.
|
|
30
27
|
attr_writer :label_formatting
|
|
31
28
|
|
|
@@ -33,42 +30,57 @@ class Gruff::Bar < Gruff::Base
|
|
|
33
30
|
# Default is +false+.
|
|
34
31
|
attr_writer :show_labels_for_bar_values
|
|
35
32
|
|
|
36
|
-
|
|
33
|
+
# Prevent drawing of column labels below a bar graph. Default is +false+.
|
|
34
|
+
attr_writer :hide_labels
|
|
35
|
+
|
|
36
|
+
# Can be used to adjust the spaces between the bars.
|
|
37
|
+
# Accepts values between 0.00 and 1.00 where 0.00 means no spacing at all
|
|
38
|
+
# and 1 means that each bars' width is nearly 0 (so each bar is a simple
|
|
39
|
+
# line with no x dimension).
|
|
40
|
+
#
|
|
41
|
+
# Default value is +0.9+.
|
|
42
|
+
def spacing_factor=(space_percent)
|
|
43
|
+
raise ArgumentError, 'spacing_factor must be between 0.00 and 1.00' unless (space_percent >= 0) && (space_percent <= 1)
|
|
44
|
+
|
|
45
|
+
@spacing_factor = (1 - space_percent)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def initialize_attributes
|
|
37
51
|
super
|
|
38
52
|
@spacing_factor = 0.9
|
|
39
53
|
@group_spacing = 10
|
|
40
54
|
@label_formatting = nil
|
|
41
55
|
@show_labels_for_bar_values = false
|
|
56
|
+
@hide_labels = false
|
|
42
57
|
end
|
|
43
|
-
private :initialize_ivars
|
|
44
58
|
|
|
45
|
-
def
|
|
59
|
+
def setup_drawing
|
|
46
60
|
# Labels will be centered over the left of the bar if
|
|
47
61
|
# there are more labels than columns. This is basically the same
|
|
48
62
|
# as where it would be for a line graph.
|
|
49
63
|
@center_labels_over_point = (@labels.keys.length > column_count)
|
|
50
64
|
|
|
51
65
|
super
|
|
52
|
-
|
|
66
|
+
end
|
|
53
67
|
|
|
54
|
-
|
|
68
|
+
def hide_labels?
|
|
69
|
+
@hide_labels
|
|
55
70
|
end
|
|
56
71
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
# line with no x dimension).
|
|
61
|
-
#
|
|
62
|
-
# Default value is +0.9+.
|
|
63
|
-
def spacing_factor=(space_percent)
|
|
64
|
-
raise ArgumentError, 'spacing_factor must be between 0.00 and 1.00' unless (space_percent >= 0) && (space_percent <= 1)
|
|
72
|
+
def hide_left_label_area?
|
|
73
|
+
@hide_line_markers
|
|
74
|
+
end
|
|
65
75
|
|
|
66
|
-
|
|
76
|
+
def hide_bottom_label_area?
|
|
77
|
+
hide_labels?
|
|
67
78
|
end
|
|
68
79
|
|
|
69
|
-
|
|
80
|
+
# Value to avoid completely overwriting the coordinate axis
|
|
81
|
+
AXIS_MARGIN = 0.5
|
|
70
82
|
|
|
71
|
-
def
|
|
83
|
+
def draw_graph
|
|
72
84
|
# Setup spacing.
|
|
73
85
|
#
|
|
74
86
|
# Columns sit side-by-side.
|
|
@@ -78,24 +90,10 @@ protected
|
|
|
78
90
|
padding = (bar_width * (1 - @bar_spacing)) / 2
|
|
79
91
|
|
|
80
92
|
# Setup the BarConversion Object
|
|
81
|
-
conversion = Gruff::BarConversion.new
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
# Set up the right mode [1,2,3] see BarConversion for further explanation
|
|
86
|
-
if minimum_value >= 0
|
|
87
|
-
# all bars go from zero to positive
|
|
88
|
-
conversion.mode = 1
|
|
89
|
-
elsif maximum_value <= 0
|
|
90
|
-
# all bars go from 0 to negative
|
|
91
|
-
conversion.mode = 2
|
|
92
|
-
else
|
|
93
|
-
# bars either go from zero to negative or to positive
|
|
94
|
-
conversion.mode = 3
|
|
95
|
-
conversion.spread = @spread
|
|
96
|
-
conversion.minimum_value = minimum_value
|
|
97
|
-
conversion.zero = -minimum_value / @spread
|
|
98
|
-
end
|
|
93
|
+
conversion = Gruff::BarConversion.new(
|
|
94
|
+
top: @graph_top, bottom: @graph_bottom,
|
|
95
|
+
minimum_value: minimum_value, maximum_value: maximum_value, spread: @spread
|
|
96
|
+
)
|
|
99
97
|
|
|
100
98
|
# iterate over all normalised data
|
|
101
99
|
store.norm_data.each_with_index do |data_row, row_index|
|
|
@@ -107,11 +105,11 @@ protected
|
|
|
107
105
|
left_x = @graph_left + (bar_width * (row_index + point_index + ((store.length - 1) * point_index))) + padding + group_spacing
|
|
108
106
|
right_x = left_x + bar_width * @bar_spacing
|
|
109
107
|
# y
|
|
110
|
-
left_y, right_y = conversion.
|
|
108
|
+
left_y, right_y = conversion.get_top_bottom_scaled(data_point)
|
|
111
109
|
|
|
112
110
|
# create new bar
|
|
113
|
-
rect_renderer = Gruff::Renderer::Rectangle.new(color: data_row.color)
|
|
114
|
-
rect_renderer.render(left_x, left_y, right_x, right_y)
|
|
111
|
+
rect_renderer = Gruff::Renderer::Rectangle.new(renderer, color: data_row.color)
|
|
112
|
+
rect_renderer.render(left_x, left_y - AXIS_MARGIN, right_x, right_y - AXIS_MARGIN)
|
|
115
113
|
|
|
116
114
|
# Calculate center based on bar_width and current row
|
|
117
115
|
label_center = @graph_left + group_spacing + (store.length * bar_width * point_index) + (store.length * bar_width / 2.0)
|
|
@@ -119,18 +117,16 @@ protected
|
|
|
119
117
|
# Subtract half a bar width to center left if requested
|
|
120
118
|
draw_label(label_center, point_index)
|
|
121
119
|
if @show_labels_for_bar_values
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
120
|
+
bar_value_label = Gruff::BarValueLabel::Bar.new([left_x, left_y, right_x, right_y], store.data[row_index].points[point_index])
|
|
121
|
+
bar_value_label.prepare_rendering(@label_formatting, bar_width) do |x, y, text|
|
|
122
|
+
draw_value_label(x, y, text)
|
|
123
|
+
end
|
|
126
124
|
end
|
|
127
125
|
end
|
|
128
126
|
end
|
|
129
127
|
|
|
130
128
|
# Draw the last label if requested
|
|
131
129
|
draw_label(@graph_right, column_count, Magick::NorthWestGravity) if @center_labels_over_point
|
|
132
|
-
|
|
133
|
-
Gruff::Renderer.finish
|
|
134
130
|
end
|
|
135
131
|
|
|
136
132
|
def calculate_spacing
|
data/lib/gruff/base.rb
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'rmagick'
|
|
4
3
|
require 'bigdecimal'
|
|
5
4
|
|
|
6
5
|
##
|
|
@@ -17,6 +16,9 @@ require 'bigdecimal'
|
|
|
17
16
|
#
|
|
18
17
|
# See {Gruff::Base#theme=} for setting themes.
|
|
19
18
|
module Gruff
|
|
19
|
+
using String::GruffCommify
|
|
20
|
+
|
|
21
|
+
# A common base class inherited from class of drawing a graph.
|
|
20
22
|
class Base
|
|
21
23
|
# Space around text elements. Mostly used for vertical spacing.
|
|
22
24
|
LEGEND_MARGIN = TITLE_MARGIN = 20.0
|
|
@@ -91,15 +93,6 @@ module Gruff
|
|
|
91
93
|
# Set the large title of the graph displayed at the top.
|
|
92
94
|
attr_writer :title
|
|
93
95
|
|
|
94
|
-
# Same as {#font=} but for the title.
|
|
95
|
-
attr_writer :title_font
|
|
96
|
-
|
|
97
|
-
# Specifies whether to draw the title bolded or not. Default is +true+.
|
|
98
|
-
attr_writer :bold_title
|
|
99
|
-
|
|
100
|
-
# Specifies the text color.
|
|
101
|
-
attr_writer :font_color
|
|
102
|
-
|
|
103
96
|
# Prevent drawing of line markers. Default is +false+.
|
|
104
97
|
attr_writer :hide_line_markers
|
|
105
98
|
|
|
@@ -116,21 +109,9 @@ module Gruff
|
|
|
116
109
|
# to +"No Data."+.
|
|
117
110
|
attr_writer :no_data_message
|
|
118
111
|
|
|
119
|
-
# Set the font size of the large title at the top of the graph. Default is +36+.
|
|
120
|
-
attr_writer :title_font_size
|
|
121
|
-
|
|
122
|
-
# Optionally set the size of the font. Based on an 800x600px graph.
|
|
123
|
-
# Default is +20+.
|
|
124
|
-
#
|
|
125
|
-
# Will be scaled down if the graph is smaller than 800px wide.
|
|
126
|
-
attr_writer :legend_font_size
|
|
127
|
-
|
|
128
112
|
# Display the legend under the graph. Default is +false+.
|
|
129
113
|
attr_writer :legend_at_bottom
|
|
130
114
|
|
|
131
|
-
# The font size of the labels around the graph. Default is +21+.
|
|
132
|
-
attr_writer :marker_font_size
|
|
133
|
-
|
|
134
115
|
# Set the color of the auxiliary lines.
|
|
135
116
|
attr_writer :marker_color
|
|
136
117
|
|
|
@@ -154,9 +135,11 @@ module Gruff
|
|
|
154
135
|
# Will be scaled down if graph is smaller than 800px wide.
|
|
155
136
|
attr_writer :legend_box_size
|
|
156
137
|
|
|
157
|
-
#
|
|
158
|
-
|
|
159
|
-
|
|
138
|
+
# Allow passing lambdas to format labels for x axis.
|
|
139
|
+
attr_writer :x_axis_label_format
|
|
140
|
+
|
|
141
|
+
# Allow passing lambdas to format labels for y axis.
|
|
142
|
+
attr_writer :y_axis_label_format
|
|
160
143
|
|
|
161
144
|
# If one numerical argument is given, the graph is drawn at 4/3 ratio
|
|
162
145
|
# according to the given width (+800+ results in 800x600, +400+ gives 400x300,
|
|
@@ -177,7 +160,7 @@ module Gruff
|
|
|
177
160
|
@rows.freeze
|
|
178
161
|
|
|
179
162
|
initialize_graph_scale
|
|
180
|
-
|
|
163
|
+
initialize_attributes
|
|
181
164
|
initialize_store
|
|
182
165
|
|
|
183
166
|
self.theme = Themes::KEYNOTE
|
|
@@ -195,31 +178,27 @@ module Gruff
|
|
|
195
178
|
protected :initialize_graph_scale
|
|
196
179
|
|
|
197
180
|
def initialize_store
|
|
198
|
-
@store = Gruff::Store.new(Gruff::Store::
|
|
181
|
+
@store = Gruff::Store.new(Gruff::Store::BasicData)
|
|
199
182
|
end
|
|
200
183
|
protected :initialize_store
|
|
201
184
|
|
|
202
|
-
# Initialize instance variable of
|
|
185
|
+
# Initialize instance variable of attributes
|
|
203
186
|
#
|
|
204
187
|
# Subclasses can override this, call super, then set values separately.
|
|
205
188
|
#
|
|
206
189
|
# This makes it possible to set defaults in a subclass but still allow
|
|
207
190
|
# developers to change this values in their program.
|
|
208
|
-
def
|
|
191
|
+
def initialize_attributes
|
|
209
192
|
@marker_count = nil
|
|
210
193
|
@maximum_value = @minimum_value = nil
|
|
211
194
|
@labels = {}
|
|
212
195
|
@sort = false
|
|
213
196
|
@sorted_drawing = false
|
|
214
197
|
@title = nil
|
|
215
|
-
@title_font = nil
|
|
216
198
|
|
|
217
|
-
@
|
|
218
|
-
@
|
|
219
|
-
|
|
220
|
-
@marker_font_size = 21.0
|
|
221
|
-
@legend_font_size = 20.0
|
|
222
|
-
@title_font_size = 36.0
|
|
199
|
+
@title_font = Gruff::Font.new(size: 36.0, bold: true)
|
|
200
|
+
@marker_font = Gruff::Font.new(size: 21.0)
|
|
201
|
+
@legend_font = Gruff::Font.new(size: 20.0)
|
|
223
202
|
|
|
224
203
|
@top_margin = @bottom_margin = @left_margin = @right_margin = DEFAULT_MARGIN
|
|
225
204
|
@legend_margin = LEGEND_MARGIN
|
|
@@ -236,12 +215,14 @@ module Gruff
|
|
|
236
215
|
@label_max_size = 0
|
|
237
216
|
@label_truncation_style = :absolute
|
|
238
217
|
|
|
239
|
-
@use_data_label = false
|
|
240
218
|
@x_axis_increment = nil
|
|
241
219
|
@x_axis_label = @y_axis_label = nil
|
|
242
220
|
@y_axis_increment = nil
|
|
221
|
+
|
|
222
|
+
@x_axis_label_format = nil
|
|
223
|
+
@y_axis_label_format = nil
|
|
243
224
|
end
|
|
244
|
-
protected :
|
|
225
|
+
protected :initialize_attributes
|
|
245
226
|
|
|
246
227
|
# Sets the top, bottom, left and right margins to +margin+.
|
|
247
228
|
#
|
|
@@ -256,8 +237,62 @@ module Gruff
|
|
|
256
237
|
# @param font_path [String] The path to font.
|
|
257
238
|
#
|
|
258
239
|
def font=(font_path)
|
|
259
|
-
@
|
|
260
|
-
|
|
240
|
+
@title_font.path = font_path unless @title_font.path
|
|
241
|
+
@marker_font.path = font_path
|
|
242
|
+
@legend_font.path = font_path
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
# Same as {#font=} but for the title.
|
|
246
|
+
#
|
|
247
|
+
# @param font_path [String] The path to font.
|
|
248
|
+
#
|
|
249
|
+
def title_font=(font_path)
|
|
250
|
+
@title_font.path = font_path
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
# Set the font size of the large title at the top of the graph. Default is +36+.
|
|
254
|
+
#
|
|
255
|
+
# @param value [Numeric] title font size
|
|
256
|
+
#
|
|
257
|
+
def title_font_size=(value)
|
|
258
|
+
@title_font.size = value
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
# The font size of the labels around the graph. Default is +21+.
|
|
262
|
+
#
|
|
263
|
+
# @param value [Numeric] marker font size
|
|
264
|
+
#
|
|
265
|
+
def marker_font_size=(value)
|
|
266
|
+
@marker_font.size = value
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
# Optionally set the size of the font. Based on an 800x600px graph.
|
|
270
|
+
# Default is +20+.
|
|
271
|
+
#
|
|
272
|
+
# Will be scaled down if the graph is smaller than 800px wide.
|
|
273
|
+
#
|
|
274
|
+
# @param value [Numeric] legend font size
|
|
275
|
+
#
|
|
276
|
+
def legend_font_size=(value)
|
|
277
|
+
@legend_font.size = value
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
# Specifies whether to draw the title bolded or not. Default is +true+.
|
|
281
|
+
#
|
|
282
|
+
# @param value [Boolean] specifies whether to draw the title bolded or not.
|
|
283
|
+
#
|
|
284
|
+
def bold_title=(value)
|
|
285
|
+
@title_font.bold = value
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
# Specifies the text color.
|
|
289
|
+
#
|
|
290
|
+
# @param value [String] color
|
|
291
|
+
#
|
|
292
|
+
def font_color=(value)
|
|
293
|
+
@title_font.color = value
|
|
294
|
+
@marker_font.color = value
|
|
295
|
+
@legend_font.color = value
|
|
261
296
|
end
|
|
262
297
|
|
|
263
298
|
# Add a color to the list of available colors for lines.
|
|
@@ -326,12 +361,13 @@ module Gruff
|
|
|
326
361
|
}
|
|
327
362
|
@theme_options = defaults.merge options
|
|
328
363
|
|
|
364
|
+
self.marker_color = @theme_options[:marker_color]
|
|
365
|
+
self.font_color = @theme_options[:font_color] || @marker_color
|
|
366
|
+
|
|
329
367
|
@colors = @theme_options[:colors]
|
|
330
|
-
@marker_color = @theme_options[:marker_color]
|
|
331
368
|
@marker_shadow_color = @theme_options[:marker_shadow_color]
|
|
332
|
-
@font_color = @theme_options[:font_color] || @marker_color
|
|
333
369
|
|
|
334
|
-
Gruff::Renderer.
|
|
370
|
+
@renderer = Gruff::Renderer.new(@columns, @rows, @scale, @theme_options)
|
|
335
371
|
end
|
|
336
372
|
|
|
337
373
|
# Apply Apple's keynote theme.
|
|
@@ -414,39 +450,65 @@ module Gruff
|
|
|
414
450
|
# @example
|
|
415
451
|
# write('graphs/my_pretty_graph.png')
|
|
416
452
|
def write(file_name = 'graph.png')
|
|
417
|
-
|
|
418
|
-
|
|
453
|
+
to_image.write(file_name)
|
|
454
|
+
end
|
|
455
|
+
|
|
456
|
+
# Return a rendered graph image.
|
|
457
|
+
# This can use RMagick's methods to adjust the image before saving.
|
|
458
|
+
#
|
|
459
|
+
# @return [Magick::Image] The rendered image.
|
|
460
|
+
#
|
|
461
|
+
# @example
|
|
462
|
+
# g = Gruff::Line.new
|
|
463
|
+
# g.data :Jimmy, [25, 36, 86, 39, 25, 31, 79, 88]
|
|
464
|
+
# g.data :Charles, [80, 54, 67, 54, 68, 70, 90, 95]
|
|
465
|
+
# image = g.to_image
|
|
466
|
+
# image = image.resize(400, 300).quantize(128, Magick::RGBColorspace)
|
|
467
|
+
# image.write('test.png')
|
|
468
|
+
#
|
|
469
|
+
def to_image
|
|
470
|
+
@to_image ||= begin
|
|
471
|
+
draw
|
|
472
|
+
renderer.finish
|
|
473
|
+
renderer.image
|
|
474
|
+
end
|
|
419
475
|
end
|
|
420
476
|
|
|
421
477
|
# Return the graph as a rendered binary blob.
|
|
422
478
|
#
|
|
423
479
|
# @param image_format [String] The image format of binary blob.
|
|
480
|
+
#
|
|
481
|
+
# @deprecated Please use +to_image.to_blob+ instead.
|
|
424
482
|
def to_blob(image_format = 'PNG')
|
|
425
|
-
|
|
426
|
-
|
|
483
|
+
warn '#to_blob is deprecated. Please use `to_image.to_blob` instead'
|
|
484
|
+
to_image.to_blob do
|
|
485
|
+
self.format = image_format
|
|
486
|
+
end
|
|
427
487
|
end
|
|
428
488
|
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
# Overridden by subclasses to do the actual plotting of the graph.
|
|
432
|
-
#
|
|
433
|
-
# Subclasses should start by calling super() for this method.
|
|
489
|
+
# Draw a graph.
|
|
434
490
|
def draw
|
|
491
|
+
setup_data
|
|
492
|
+
|
|
435
493
|
# Maybe should be done in one of the following functions for more granularity.
|
|
436
494
|
unless data_given?
|
|
437
495
|
draw_no_data
|
|
438
496
|
return
|
|
439
497
|
end
|
|
440
498
|
|
|
441
|
-
setup_data
|
|
442
499
|
setup_drawing
|
|
443
500
|
|
|
444
501
|
draw_legend
|
|
445
502
|
draw_line_markers
|
|
446
503
|
draw_axis_labels
|
|
447
504
|
draw_title
|
|
505
|
+
draw_graph
|
|
448
506
|
end
|
|
449
507
|
|
|
508
|
+
protected
|
|
509
|
+
|
|
510
|
+
attr_reader :renderer
|
|
511
|
+
|
|
450
512
|
# Perform data manipulation before calculating chart measurements
|
|
451
513
|
def setup_data # :nodoc:
|
|
452
514
|
if @y_axis_increment && !@hide_line_markers
|
|
@@ -486,6 +548,18 @@ module Gruff
|
|
|
486
548
|
store.columns
|
|
487
549
|
end
|
|
488
550
|
|
|
551
|
+
def marker_count
|
|
552
|
+
@marker_count ||= begin
|
|
553
|
+
count = nil
|
|
554
|
+
(3..7).each do |lines|
|
|
555
|
+
if @spread.to_f % lines == 0.0
|
|
556
|
+
count = lines and break
|
|
557
|
+
end
|
|
558
|
+
end
|
|
559
|
+
count || 4
|
|
560
|
+
end
|
|
561
|
+
end
|
|
562
|
+
|
|
489
563
|
# Make copy of data with values scaled between 0-100
|
|
490
564
|
def normalize
|
|
491
565
|
store.normalize(minimum: minimum_value, spread: @spread)
|
|
@@ -500,6 +574,18 @@ module Gruff
|
|
|
500
574
|
@hide_title || @title.nil? || @title.empty?
|
|
501
575
|
end
|
|
502
576
|
|
|
577
|
+
def hide_labels?
|
|
578
|
+
@hide_line_markers
|
|
579
|
+
end
|
|
580
|
+
|
|
581
|
+
def hide_left_label_area?
|
|
582
|
+
@hide_line_markers
|
|
583
|
+
end
|
|
584
|
+
|
|
585
|
+
def hide_bottom_label_area?
|
|
586
|
+
@hide_line_markers
|
|
587
|
+
end
|
|
588
|
+
|
|
503
589
|
##
|
|
504
590
|
# Calculates size of drawable area, general font dimensions, etc.
|
|
505
591
|
|
|
@@ -527,13 +613,13 @@ module Gruff
|
|
|
527
613
|
x_axis_label_y_coordinate = @graph_bottom + LABEL_MARGIN + @marker_caps_height
|
|
528
614
|
|
|
529
615
|
# TODO: Center between graph area
|
|
530
|
-
text_renderer = Gruff::Renderer::Text.new(@x_axis_label, font: @
|
|
616
|
+
text_renderer = Gruff::Renderer::Text.new(renderer, @x_axis_label, font: @marker_font)
|
|
531
617
|
text_renderer.add_to_render_queue(@raw_columns, 1.0, 0.0, x_axis_label_y_coordinate)
|
|
532
618
|
end
|
|
533
619
|
|
|
534
620
|
if @y_axis_label
|
|
535
621
|
# Y Axis, rotated vertically
|
|
536
|
-
text_renderer = Gruff::Renderer::Text.new(@y_axis_label, font: @
|
|
622
|
+
text_renderer = Gruff::Renderer::Text.new(renderer, @y_axis_label, font: @marker_font, rotation: -90)
|
|
537
623
|
text_renderer.add_to_render_queue(1.0, @raw_rows, @left_margin + @marker_caps_height / 2.0, 0.0, Magick::CenterGravity)
|
|
538
624
|
end
|
|
539
625
|
end
|
|
@@ -545,28 +631,21 @@ module Gruff
|
|
|
545
631
|
increment_scaled = @graph_height.to_f / (@spread / @increment)
|
|
546
632
|
|
|
547
633
|
# Draw horizontal line markers and annotate with numbers
|
|
548
|
-
(0
|
|
634
|
+
(0..marker_count).each do |index|
|
|
549
635
|
y = @graph_top + @graph_height - index.to_f * increment_scaled
|
|
550
636
|
|
|
551
|
-
line_renderer = Gruff::Renderer::Line.new(color: @marker_color, shadow_color: @marker_shadow_color)
|
|
637
|
+
line_renderer = Gruff::Renderer::Line.new(renderer, color: @marker_color, shadow_color: @marker_shadow_color)
|
|
552
638
|
line_renderer.render(@graph_left, y, @graph_right, y)
|
|
553
639
|
|
|
554
640
|
unless @hide_line_numbers
|
|
555
641
|
marker_label = BigDecimal(index.to_s) * BigDecimal(@increment.to_s) + BigDecimal(minimum_value.to_s)
|
|
556
|
-
label =
|
|
557
|
-
text_renderer = Gruff::Renderer::Text.new(label, font: @
|
|
642
|
+
label = y_axis_label(marker_label, @increment)
|
|
643
|
+
text_renderer = Gruff::Renderer::Text.new(renderer, label, font: @marker_font)
|
|
558
644
|
text_renderer.add_to_render_queue(@graph_left - LABEL_MARGIN, 1.0, 0.0, y, Magick::EastGravity)
|
|
559
645
|
end
|
|
560
646
|
end
|
|
561
647
|
end
|
|
562
648
|
|
|
563
|
-
# Return the sum of values in an array.
|
|
564
|
-
#
|
|
565
|
-
# Duplicated to not conflict with active_support in Rails.
|
|
566
|
-
def sum(arr)
|
|
567
|
-
arr.reduce(0) { |i, m| m + i }
|
|
568
|
-
end
|
|
569
|
-
|
|
570
649
|
# Return a calculation of center
|
|
571
650
|
def center(size)
|
|
572
651
|
(@raw_columns - size) / 2
|
|
@@ -581,10 +660,10 @@ module Gruff
|
|
|
581
660
|
legend_square_width = @legend_box_size # small square with color of this item
|
|
582
661
|
label_widths = calculate_legend_label_widths_for_each_line(legend_labels, legend_square_width)
|
|
583
662
|
|
|
584
|
-
current_x_offset = center(
|
|
663
|
+
current_x_offset = center(label_widths.first.sum)
|
|
585
664
|
current_y_offset = begin
|
|
586
665
|
if @legend_at_bottom
|
|
587
|
-
@
|
|
666
|
+
@graph_bottom + @legend_margin + @legend_caps_height + LABEL_MARGIN
|
|
588
667
|
else
|
|
589
668
|
hide_title? ? @top_margin + @title_margin : @top_margin + @title_margin + @title_caps_height
|
|
590
669
|
end
|
|
@@ -594,30 +673,28 @@ module Gruff
|
|
|
594
673
|
next if legend_label.empty?
|
|
595
674
|
|
|
596
675
|
# Draw label
|
|
597
|
-
text_renderer = Gruff::Renderer::Text.new(legend_label, font: @
|
|
676
|
+
text_renderer = Gruff::Renderer::Text.new(renderer, legend_label, font: @legend_font)
|
|
598
677
|
text_renderer.add_to_render_queue(@raw_columns, 1.0, current_x_offset + (legend_square_width * 1.7), current_y_offset, Magick::WestGravity)
|
|
599
678
|
|
|
600
679
|
# Now draw box with color of this dataset
|
|
601
|
-
rect_renderer = Gruff::Renderer::Rectangle.new(color: store.data[index].color)
|
|
680
|
+
rect_renderer = Gruff::Renderer::Rectangle.new(renderer, color: store.data[index].color)
|
|
602
681
|
rect_renderer.render(current_x_offset,
|
|
603
682
|
current_y_offset - legend_square_width / 2.0,
|
|
604
683
|
current_x_offset + legend_square_width,
|
|
605
684
|
current_y_offset + legend_square_width / 2.0)
|
|
606
685
|
|
|
607
|
-
width = calculate_width(@
|
|
686
|
+
width = calculate_width(@legend_font, legend_label)
|
|
608
687
|
current_x_offset += width + (legend_square_width * 2.7)
|
|
609
688
|
label_widths.first.shift
|
|
610
689
|
|
|
611
690
|
# Handle wrapping
|
|
612
691
|
if label_widths.first.empty?
|
|
613
692
|
label_widths.shift
|
|
614
|
-
current_x_offset = center(
|
|
693
|
+
current_x_offset = center(label_widths.first.sum) unless label_widths.empty?
|
|
615
694
|
line_height = [@legend_caps_height, legend_square_width].max + @legend_margin
|
|
616
695
|
unless label_widths.empty?
|
|
617
696
|
# Wrap to next line and shrink available graph dimensions
|
|
618
697
|
current_y_offset += line_height
|
|
619
|
-
@graph_top += line_height
|
|
620
|
-
@graph_height = @graph_bottom - @graph_top
|
|
621
698
|
end
|
|
622
699
|
end
|
|
623
700
|
end
|
|
@@ -627,15 +704,12 @@ module Gruff
|
|
|
627
704
|
def draw_title
|
|
628
705
|
return if hide_title?
|
|
629
706
|
|
|
630
|
-
|
|
631
|
-
font_weight = @bold_title ? Magick::BoldWeight : Magick::NormalWeight
|
|
632
|
-
font_size = @title_font_size
|
|
633
|
-
|
|
634
|
-
metrics = Renderer::Text.metrics(@title, font_size, font_weight)
|
|
707
|
+
metrics = Gruff::Renderer::Text.new(renderer, @title, font: @title_font).metrics
|
|
635
708
|
if metrics.width > @raw_columns
|
|
636
|
-
|
|
709
|
+
@title_font.size = @title_font.size * (@raw_columns / metrics.width) * 0.95
|
|
637
710
|
end
|
|
638
|
-
|
|
711
|
+
|
|
712
|
+
text_renderer = Gruff::Renderer::Text.new(renderer, @title, font: @title_font)
|
|
639
713
|
text_renderer.add_to_render_queue(@raw_columns, 1.0, 0, @top_margin)
|
|
640
714
|
end
|
|
641
715
|
|
|
@@ -651,17 +725,14 @@ module Gruff
|
|
|
651
725
|
# TODO: See if index.odd? is the best stragegy
|
|
652
726
|
y_offset += @label_stagger_height if index.odd?
|
|
653
727
|
|
|
654
|
-
label_text = truncate_label_text(@labels[index].to_s)
|
|
655
|
-
|
|
656
728
|
if x_offset >= @graph_left && x_offset <= @graph_right
|
|
657
|
-
|
|
658
|
-
text_renderer.add_to_render_queue(1.0, 1.0, x_offset, y_offset, gravity)
|
|
729
|
+
draw_label_at(1.0, 1.0, x_offset, y_offset, @labels[index], gravity)
|
|
659
730
|
end
|
|
660
731
|
end
|
|
661
732
|
end
|
|
662
733
|
|
|
663
734
|
def draw_unique_label(index)
|
|
664
|
-
return if
|
|
735
|
+
return if hide_labels?
|
|
665
736
|
|
|
666
737
|
@labels_seen ||= {}
|
|
667
738
|
if !@labels[index].nil? && @labels_seen[index].nil?
|
|
@@ -670,20 +741,33 @@ module Gruff
|
|
|
670
741
|
end
|
|
671
742
|
end
|
|
672
743
|
|
|
744
|
+
def draw_label_at(width, height, x, y, text, gravity = Magick::NorthGravity)
|
|
745
|
+
label_text = truncate_label_text(text)
|
|
746
|
+
text_renderer = Gruff::Renderer::Text.new(renderer, label_text, font: @marker_font)
|
|
747
|
+
text_renderer.add_to_render_queue(width, height, x, y, gravity)
|
|
748
|
+
end
|
|
749
|
+
|
|
673
750
|
# Draws the data value over the data point in bar graphs
|
|
674
|
-
def draw_value_label(x_offset, y_offset, data_point
|
|
675
|
-
return if @hide_line_markers
|
|
751
|
+
def draw_value_label(x_offset, y_offset, data_point)
|
|
752
|
+
return if @hide_line_markers
|
|
676
753
|
|
|
677
|
-
text_renderer = Gruff::Renderer::Text.new(data_point, font: @
|
|
754
|
+
text_renderer = Gruff::Renderer::Text.new(renderer, data_point, font: @marker_font)
|
|
678
755
|
text_renderer.add_to_render_queue(1.0, 1.0, x_offset, y_offset)
|
|
679
756
|
end
|
|
680
757
|
|
|
681
758
|
# Shows an error message because you have no data.
|
|
682
759
|
def draw_no_data
|
|
683
|
-
|
|
760
|
+
font = @title_font.dup
|
|
761
|
+
font.size = 80
|
|
762
|
+
font.bold = false
|
|
763
|
+
text_renderer = Gruff::Renderer::Text.new(renderer, @no_data_message, font: font)
|
|
684
764
|
text_renderer.render(@raw_columns, @raw_rows, 0, 0, Magick::CenterGravity)
|
|
685
765
|
end
|
|
686
766
|
|
|
767
|
+
def draw_graph
|
|
768
|
+
raise 'Should implement this method at inherited class.'
|
|
769
|
+
end
|
|
770
|
+
|
|
687
771
|
# Resets everything to defaults (except data).
|
|
688
772
|
def reset_themes
|
|
689
773
|
@theme_options = {}
|
|
@@ -746,15 +830,15 @@ module Gruff
|
|
|
746
830
|
private
|
|
747
831
|
|
|
748
832
|
def setup_marker_caps_height
|
|
749
|
-
|
|
833
|
+
hide_bottom_label_area? ? 0 : calculate_caps_height(@marker_font)
|
|
750
834
|
end
|
|
751
835
|
|
|
752
836
|
def setup_title_caps_height
|
|
753
|
-
hide_title? ? 0 : calculate_caps_height(@
|
|
837
|
+
hide_title? ? 0 : calculate_caps_height(@title_font) * @title.lines.to_a.size
|
|
754
838
|
end
|
|
755
839
|
|
|
756
840
|
def setup_legend_caps_height
|
|
757
|
-
@hide_legend ? 0 : calculate_caps_height(@
|
|
841
|
+
@hide_legend ? 0 : calculate_caps_height(@legend_font)
|
|
758
842
|
end
|
|
759
843
|
|
|
760
844
|
def graph_right_margin
|
|
@@ -765,19 +849,22 @@ module Gruff
|
|
|
765
849
|
# Make space for half the width of the rightmost column label.
|
|
766
850
|
# Might be greater than the number of columns if between-style bar markers are used.
|
|
767
851
|
last_label = @labels.keys.max.to_i
|
|
768
|
-
(last_label >= (column_count - 1) && @center_labels_over_point) ? calculate_width(@
|
|
852
|
+
(last_label >= (column_count - 1) && @center_labels_over_point) ? calculate_width(@marker_font, @labels[last_label]) / 2.0 : 0
|
|
769
853
|
end
|
|
770
854
|
|
|
771
855
|
def setup_left_margin
|
|
772
|
-
return @left_margin if
|
|
856
|
+
return @left_margin if hide_left_label_area?
|
|
773
857
|
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
858
|
+
text = begin
|
|
859
|
+
if @has_left_labels
|
|
860
|
+
@labels.values.reduce('') { |value, memo| (value.to_s.length > memo.to_s.length) ? value : memo }
|
|
861
|
+
else
|
|
862
|
+
y_axis_label(maximum_value.to_f, @increment)
|
|
863
|
+
end
|
|
780
864
|
end
|
|
865
|
+
longest_left_label_width = calculate_width(@marker_font, truncate_label_text(text))
|
|
866
|
+
longest_left_label_width *= 1.25 if @has_left_labels
|
|
867
|
+
|
|
781
868
|
# Shift graph if left line numbers are hidden
|
|
782
869
|
line_number_width = @hide_line_numbers && !@has_left_labels ? 0.0 : (longest_left_label_width + LABEL_MARGIN * 2)
|
|
783
870
|
|
|
@@ -785,17 +872,16 @@ module Gruff
|
|
|
785
872
|
end
|
|
786
873
|
|
|
787
874
|
def setup_top_margin
|
|
788
|
-
return @top_margin if @legend_at_bottom
|
|
789
|
-
|
|
790
875
|
# When @hide title, leave a title_margin space for aesthetics.
|
|
791
876
|
# Same with @hide_legend
|
|
792
877
|
@top_margin +
|
|
793
878
|
(hide_title? ? @title_margin : @title_caps_height + @title_margin) +
|
|
794
|
-
(@hide_legend ? @legend_margin :
|
|
879
|
+
((@hide_legend || @legend_at_bottom) ? @legend_margin : calculate_legend_height + @legend_margin)
|
|
795
880
|
end
|
|
796
881
|
|
|
797
882
|
def setup_bottom_margin
|
|
798
|
-
graph_bottom_margin =
|
|
883
|
+
graph_bottom_margin = hide_bottom_label_area? ? @bottom_margin : @bottom_margin + @marker_caps_height + LABEL_MARGIN
|
|
884
|
+
graph_bottom_margin += (calculate_legend_height + @legend_margin) if @legend_at_bottom
|
|
799
885
|
|
|
800
886
|
x_axis_label_height = @x_axis_label.nil? ? 0.0 : @marker_caps_height + LABEL_MARGIN
|
|
801
887
|
# FIXME: Consider chart types other than bar
|
|
@@ -803,6 +889,7 @@ module Gruff
|
|
|
803
889
|
end
|
|
804
890
|
|
|
805
891
|
def truncate_label_text(text)
|
|
892
|
+
text = text.to_s
|
|
806
893
|
return text if text.size <= @label_max_size
|
|
807
894
|
|
|
808
895
|
if @label_truncation_style == :trailing_dots
|
|
@@ -817,44 +904,62 @@ module Gruff
|
|
|
817
904
|
# Return a formatted string representing a number value that should be
|
|
818
905
|
# printed as a label.
|
|
819
906
|
def label(value, increment)
|
|
820
|
-
label =
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
907
|
+
label = begin
|
|
908
|
+
if increment
|
|
909
|
+
if increment >= 10 || (increment * 1) == (increment * 1).to_i.to_f
|
|
910
|
+
sprintf('%0i', value)
|
|
911
|
+
elsif increment >= 1.0 || (increment * 10) == (increment * 10).to_i.to_f
|
|
912
|
+
sprintf('%0.1f', value)
|
|
913
|
+
elsif increment >= 0.1 || (increment * 100) == (increment * 100).to_i.to_f
|
|
914
|
+
sprintf('%0.2f', value)
|
|
915
|
+
elsif increment >= 0.01 || (increment * 1000) == (increment * 1000).to_i.to_f
|
|
916
|
+
sprintf('%0.3f', value)
|
|
917
|
+
elsif increment >= 0.001 || (increment * 10000) == (increment * 10000).to_i.to_f
|
|
918
|
+
sprintf('%0.4f', value)
|
|
919
|
+
else
|
|
920
|
+
value.to_s
|
|
921
|
+
end
|
|
922
|
+
elsif (@spread.to_f % (marker_count.to_f == 0 ? 1 : marker_count.to_f) == 0) || !@y_axis_increment.nil?
|
|
923
|
+
value.to_i.to_s
|
|
924
|
+
elsif @spread > 10.0
|
|
925
|
+
sprintf('%0i', value)
|
|
926
|
+
elsif @spread >= 3.0
|
|
927
|
+
sprintf('%0.2f', value)
|
|
928
|
+
else
|
|
929
|
+
value.to_s
|
|
930
|
+
end
|
|
931
|
+
end
|
|
843
932
|
|
|
844
933
|
parts = label.split('.')
|
|
845
934
|
parts[0] = parts[0].commify
|
|
846
935
|
parts.join('.')
|
|
847
936
|
end
|
|
848
937
|
|
|
938
|
+
def x_axis_label(value, increment)
|
|
939
|
+
if @x_axis_label_format
|
|
940
|
+
@x_axis_label_format.call(value)
|
|
941
|
+
else
|
|
942
|
+
label(value, increment)
|
|
943
|
+
end
|
|
944
|
+
end
|
|
945
|
+
|
|
946
|
+
def y_axis_label(value, increment)
|
|
947
|
+
if @y_axis_label_format
|
|
948
|
+
@y_axis_label_format.call(value)
|
|
949
|
+
else
|
|
950
|
+
label(value, increment)
|
|
951
|
+
end
|
|
952
|
+
end
|
|
953
|
+
|
|
849
954
|
def calculate_legend_label_widths_for_each_line(legend_labels, legend_square_width)
|
|
850
955
|
# May fix legend drawing problem at small sizes
|
|
851
956
|
label_widths = [[]] # Used to calculate line wrap
|
|
852
957
|
legend_labels.each do |label|
|
|
853
|
-
width = calculate_width(@
|
|
958
|
+
width = calculate_width(@legend_font, label)
|
|
854
959
|
label_width = width + legend_square_width * 2.7
|
|
855
960
|
label_widths.last.push label_width
|
|
856
961
|
|
|
857
|
-
if
|
|
962
|
+
if label_widths.last.sum > (@raw_columns * 0.9)
|
|
858
963
|
label_widths.push [label_widths.last.pop]
|
|
859
964
|
end
|
|
860
965
|
end
|
|
@@ -862,25 +967,50 @@ module Gruff
|
|
|
862
967
|
label_widths
|
|
863
968
|
end
|
|
864
969
|
|
|
970
|
+
def calculate_legend_height
|
|
971
|
+
return 0.0 if @hide_legend
|
|
972
|
+
|
|
973
|
+
legend_labels = store.data.map(&:label)
|
|
974
|
+
legend_square_width = @legend_box_size
|
|
975
|
+
label_widths = calculate_legend_label_widths_for_each_line(legend_labels, legend_square_width)
|
|
976
|
+
legend_height = 0.0
|
|
977
|
+
|
|
978
|
+
legend_labels.each_with_index do |legend_label, _index|
|
|
979
|
+
next if legend_label.empty?
|
|
980
|
+
|
|
981
|
+
label_widths.first.shift
|
|
982
|
+
if label_widths.first.empty?
|
|
983
|
+
label_widths.shift
|
|
984
|
+
line_height = [@legend_caps_height, legend_square_width].max + @legend_margin
|
|
985
|
+
unless label_widths.empty?
|
|
986
|
+
# Wrap to next line and shrink available graph dimensions
|
|
987
|
+
legend_height += line_height
|
|
988
|
+
end
|
|
989
|
+
end
|
|
990
|
+
end
|
|
991
|
+
|
|
992
|
+
legend_height + @legend_caps_height
|
|
993
|
+
end
|
|
994
|
+
|
|
865
995
|
# Returns the height of the capital letter 'X' for the current font and
|
|
866
996
|
# size.
|
|
867
997
|
#
|
|
868
998
|
# Not scaled since it deals with dimensions that the regular scaling will
|
|
869
999
|
# handle.
|
|
870
|
-
def calculate_caps_height(
|
|
871
|
-
metrics = Renderer::Text.
|
|
1000
|
+
def calculate_caps_height(font)
|
|
1001
|
+
metrics = Gruff::Renderer::Text.new(renderer, 'X', font: font).metrics
|
|
872
1002
|
metrics.height
|
|
873
1003
|
end
|
|
874
1004
|
|
|
875
|
-
# Returns the width of a string at this
|
|
1005
|
+
# Returns the width of a string at this point size.
|
|
876
1006
|
#
|
|
877
1007
|
# Not scaled since it deals with dimensions that the regular
|
|
878
1008
|
# scaling will handle.
|
|
879
|
-
def calculate_width(
|
|
1009
|
+
def calculate_width(font, text)
|
|
880
1010
|
text = text.to_s
|
|
881
1011
|
return 0 if text.empty?
|
|
882
1012
|
|
|
883
|
-
metrics = Renderer::Text.
|
|
1013
|
+
metrics = Gruff::Renderer::Text.new(renderer, text, font: font).metrics
|
|
884
1014
|
metrics.width
|
|
885
1015
|
end
|
|
886
1016
|
|
|
@@ -889,19 +1019,10 @@ module Gruff
|
|
|
889
1019
|
# Try to use a number of horizontal lines that will come out even.
|
|
890
1020
|
#
|
|
891
1021
|
# TODO Do the same for larger numbers...100, 75, 50, 25
|
|
892
|
-
|
|
893
|
-
(3..7).each do |lines|
|
|
894
|
-
if @spread % lines == 0.0
|
|
895
|
-
@marker_count = lines
|
|
896
|
-
break
|
|
897
|
-
end
|
|
898
|
-
end
|
|
899
|
-
@marker_count ||= 4
|
|
900
|
-
end
|
|
901
|
-
@increment = (@spread > 0 && @marker_count > 0) ? significant(@spread / @marker_count) : 1
|
|
1022
|
+
@increment = (@spread > 0 && marker_count > 0) ? significant(@spread / marker_count) : 1
|
|
902
1023
|
else
|
|
903
1024
|
# TODO: Make this work for negative values
|
|
904
|
-
|
|
1025
|
+
self.marker_count = (@spread / @y_axis_increment).to_i
|
|
905
1026
|
@increment = @y_axis_increment
|
|
906
1027
|
end
|
|
907
1028
|
end
|