gruff 0.10.0-java → 0.13.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/.gitignore +1 -0
- data/.rubocop.yml +24 -4
- data/.rubocop_todo.yml +94 -42
- data/.travis.yml +3 -6
- data/CHANGELOG.md +35 -0
- data/README.md +10 -1
- 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 -3
- data/lib/gruff.rb +8 -3
- data/lib/gruff/accumulator_bar.rb +0 -2
- data/lib/gruff/area.rb +2 -6
- data/lib/gruff/bar.rb +35 -35
- data/lib/gruff/base.rb +295 -188
- data/lib/gruff/bezier.rb +0 -4
- data/lib/gruff/bullet.rb +12 -14
- data/lib/gruff/dot.rb +8 -33
- data/lib/gruff/helper/bar_conversion.rb +34 -19
- data/lib/gruff/helper/bar_value_label.rb +68 -0
- data/lib/gruff/histogram.rb +25 -25
- data/lib/gruff/line.rb +29 -26
- data/lib/gruff/mini/bar.rb +1 -1
- data/lib/gruff/mini/legend.rb +9 -4
- data/lib/gruff/mini/pie.rb +1 -2
- data/lib/gruff/mini/side_bar.rb +1 -2
- data/lib/gruff/net.rb +19 -20
- data/lib/gruff/patch/rmagick.rb +22 -24
- data/lib/gruff/patch/string.rb +7 -4
- data/lib/gruff/photo_bar.rb +12 -16
- data/lib/gruff/pie.rb +19 -30
- data/lib/gruff/renderer/bezier.rb +4 -3
- data/lib/gruff/renderer/circle.rb +4 -3
- data/lib/gruff/renderer/dash_line.rb +4 -3
- data/lib/gruff/renderer/dot.rb +4 -3
- data/lib/gruff/renderer/ellipse.rb +4 -3
- data/lib/gruff/renderer/line.rb +14 -5
- data/lib/gruff/renderer/polygon.rb +5 -4
- data/lib/gruff/renderer/polyline.rb +4 -3
- data/lib/gruff/renderer/rectangle.rb +3 -2
- data/lib/gruff/renderer/renderer.rb +31 -38
- data/lib/gruff/renderer/text.rb +39 -9
- data/lib/gruff/scatter.rb +30 -44
- data/lib/gruff/scene.rb +0 -1
- data/lib/gruff/side_bar.rb +60 -45
- data/lib/gruff/side_stacked_bar.rb +30 -19
- data/lib/gruff/spider.rb +18 -17
- data/lib/gruff/stacked_area.rb +8 -7
- data/lib/gruff/stacked_bar.rb +28 -18
- data/lib/gruff/store/{base_data.rb → basic_data.rb} +9 -7
- data/lib/gruff/store/custom_data.rb +8 -6
- data/lib/gruff/store/store.rb +6 -5
- data/lib/gruff/store/xy_data.rb +10 -7
- data/lib/gruff/version.rb +1 -1
- metadata +36 -9
- data/Rakefile +0 -23
- 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
|
Binary file
|
|
Binary file
|
data/gruff.gemspec
CHANGED
|
@@ -13,7 +13,9 @@ Gem::Specification.new do |s|
|
|
|
13
13
|
s.date = Date.today.to_s
|
|
14
14
|
s.description = 'Beautiful graphs for one or multiple datasets. Can be used on websites or in documents.'
|
|
15
15
|
s.email = 'boss@topfunky.com'
|
|
16
|
-
s.files = `git ls-files`.split
|
|
16
|
+
s.files = `git ls-files`.split.reject do |f|
|
|
17
|
+
f =~ /^test|^docker|^Rakefile/
|
|
18
|
+
end
|
|
17
19
|
s.homepage = 'https://github.com/topfunky/gruff'
|
|
18
20
|
s.require_paths = %w[lib]
|
|
19
21
|
s.summary = 'Beautiful graphs for one or multiple datasets.'
|
|
@@ -27,12 +29,15 @@ Gem::Specification.new do |s|
|
|
|
27
29
|
s.add_dependency 'rmagick4j'
|
|
28
30
|
else
|
|
29
31
|
s.add_dependency 'rmagick'
|
|
30
|
-
s.add_development_dependency 'rubocop', '~>
|
|
32
|
+
s.add_development_dependency 'rubocop', '~> 1.12.1'
|
|
33
|
+
s.add_development_dependency 'rubocop-performance', '~> 1.10.2'
|
|
31
34
|
end
|
|
32
35
|
s.add_dependency 'histogram'
|
|
33
|
-
s.required_ruby_version = '>=
|
|
36
|
+
s.required_ruby_version = '>= 2.4.0'
|
|
34
37
|
|
|
35
38
|
s.add_development_dependency 'rake'
|
|
39
|
+
s.add_development_dependency 'parallel'
|
|
36
40
|
s.add_development_dependency 'minitest-reporters'
|
|
41
|
+
s.add_development_dependency 'simplecov'
|
|
37
42
|
s.add_development_dependency 'yard', '~> 0.9.25'
|
|
38
43
|
end
|
data/lib/gruff.rb
CHANGED
|
@@ -9,8 +9,13 @@ require 'gruff/version'
|
|
|
9
9
|
patch/rmagick
|
|
10
10
|
patch/string
|
|
11
11
|
|
|
12
|
-
themes
|
|
13
12
|
base
|
|
13
|
+
|
|
14
|
+
helper/bar_conversion.rb
|
|
15
|
+
helper/stacked_mixin
|
|
16
|
+
helper/bar_value_label
|
|
17
|
+
|
|
18
|
+
themes
|
|
14
19
|
area
|
|
15
20
|
bar
|
|
16
21
|
bezier
|
|
@@ -22,10 +27,10 @@ require 'gruff/version'
|
|
|
22
27
|
pie
|
|
23
28
|
scatter
|
|
24
29
|
spider
|
|
30
|
+
side_bar
|
|
25
31
|
stacked_area
|
|
26
32
|
stacked_bar
|
|
27
33
|
side_stacked_bar
|
|
28
|
-
side_bar
|
|
29
34
|
accumulator_bar
|
|
30
35
|
|
|
31
36
|
scene
|
|
@@ -43,7 +48,7 @@ require 'gruff/version'
|
|
|
43
48
|
renderer/text
|
|
44
49
|
|
|
45
50
|
store/store
|
|
46
|
-
store/
|
|
51
|
+
store/basic_data
|
|
47
52
|
store/custom_data
|
|
48
53
|
store/xy_data
|
|
49
54
|
|
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.
|
|
@@ -17,10 +15,10 @@ require 'gruff/base'
|
|
|
17
15
|
#
|
|
18
16
|
class Gruff::Area < Gruff::Base
|
|
19
17
|
# Specifies the filling opacity in area graph. Default is +0.85+.
|
|
20
|
-
|
|
18
|
+
attr_writer :fill_opacity
|
|
21
19
|
|
|
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
|
def initialize_ivars
|
|
26
24
|
super
|
|
@@ -59,7 +57,5 @@ class Gruff::Area < Gruff::Base
|
|
|
59
57
|
|
|
60
58
|
Gruff::Renderer::Polygon.new(color: data_row.color, width: @stroke_width, opacity: @fill_opacity).render(poly_points)
|
|
61
59
|
end
|
|
62
|
-
|
|
63
|
-
Gruff::Renderer.finish
|
|
64
60
|
end
|
|
65
61
|
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.
|
|
@@ -20,24 +17,29 @@ require 'gruff/helper/bar_conversion'
|
|
|
20
17
|
#
|
|
21
18
|
class Gruff::Bar < Gruff::Base
|
|
22
19
|
# Spacing factor applied between bars.
|
|
23
|
-
|
|
20
|
+
attr_writer :bar_spacing
|
|
24
21
|
|
|
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
|
|
|
32
29
|
# Output the values for the bars on a bar graph.
|
|
33
30
|
# Default is +false+.
|
|
34
|
-
|
|
31
|
+
attr_writer :show_labels_for_bar_values
|
|
32
|
+
|
|
33
|
+
# Prevent drawing of column labels below a bar graph. Default is +false+.
|
|
34
|
+
attr_writer :hide_labels
|
|
35
35
|
|
|
36
36
|
def initialize_ivars
|
|
37
37
|
super
|
|
38
38
|
@spacing_factor = 0.9
|
|
39
|
+
@group_spacing = 10
|
|
39
40
|
@label_formatting = nil
|
|
40
41
|
@show_labels_for_bar_values = false
|
|
42
|
+
@hide_labels = false
|
|
41
43
|
end
|
|
42
44
|
private :initialize_ivars
|
|
43
45
|
|
|
@@ -67,35 +69,35 @@ class Gruff::Bar < Gruff::Base
|
|
|
67
69
|
|
|
68
70
|
protected
|
|
69
71
|
|
|
72
|
+
def hide_labels?
|
|
73
|
+
@hide_labels
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def hide_left_label_area?
|
|
77
|
+
@hide_line_markers
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def hide_bottom_label_area?
|
|
81
|
+
hide_labels?
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Value to avoid completely overwriting the coordinate axis
|
|
85
|
+
AXIS_MARGIN = 0.5
|
|
86
|
+
|
|
70
87
|
def draw_bars
|
|
71
88
|
# Setup spacing.
|
|
72
89
|
#
|
|
73
90
|
# Columns sit side-by-side.
|
|
74
91
|
@bar_spacing ||= @spacing_factor # space between the bars
|
|
75
|
-
@group_spacing ||= 10
|
|
76
92
|
|
|
77
93
|
bar_width = (@graph_width - calculate_spacing) / (column_count * store.length).to_f
|
|
78
94
|
padding = (bar_width * (1 - @bar_spacing)) / 2
|
|
79
95
|
|
|
80
96
|
# 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
|
|
97
|
+
conversion = Gruff::BarConversion.new(
|
|
98
|
+
top: @graph_top, bottom: @graph_bottom,
|
|
99
|
+
minimum_value: minimum_value, maximum_value: maximum_value, spread: @spread
|
|
100
|
+
)
|
|
99
101
|
|
|
100
102
|
# iterate over all normalised data
|
|
101
103
|
store.norm_data.each_with_index do |data_row, row_index|
|
|
@@ -107,11 +109,11 @@ protected
|
|
|
107
109
|
left_x = @graph_left + (bar_width * (row_index + point_index + ((store.length - 1) * point_index))) + padding + group_spacing
|
|
108
110
|
right_x = left_x + bar_width * @bar_spacing
|
|
109
111
|
# y
|
|
110
|
-
left_y, right_y = conversion.
|
|
112
|
+
left_y, right_y = conversion.get_top_bottom_scaled(data_point)
|
|
111
113
|
|
|
112
114
|
# create new bar
|
|
113
115
|
rect_renderer = Gruff::Renderer::Rectangle.new(color: data_row.color)
|
|
114
|
-
rect_renderer.render(left_x, left_y, right_x, right_y)
|
|
116
|
+
rect_renderer.render(left_x, left_y - AXIS_MARGIN, right_x, right_y - AXIS_MARGIN)
|
|
115
117
|
|
|
116
118
|
# Calculate center based on bar_width and current row
|
|
117
119
|
label_center = @graph_left + group_spacing + (store.length * bar_width * point_index) + (store.length * bar_width / 2.0)
|
|
@@ -119,18 +121,16 @@ protected
|
|
|
119
121
|
# Subtract half a bar width to center left if requested
|
|
120
122
|
draw_label(label_center, point_index)
|
|
121
123
|
if @show_labels_for_bar_values
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
124
|
+
bar_value_label = Gruff::BarValueLabel::Bar.new([left_x, left_y, right_x, right_y], store.data[row_index].points[point_index])
|
|
125
|
+
bar_value_label.prepare_rendering(@label_formatting, bar_width) do |x, y, text|
|
|
126
|
+
draw_value_label(x, y, text, true)
|
|
127
|
+
end
|
|
126
128
|
end
|
|
127
129
|
end
|
|
128
130
|
end
|
|
129
131
|
|
|
130
132
|
# Draw the last label if requested
|
|
131
133
|
draw_label(@graph_right, column_count, Magick::NorthWestGravity) if @center_labels_over_point
|
|
132
|
-
|
|
133
|
-
Gruff::Renderer.finish
|
|
134
134
|
end
|
|
135
135
|
|
|
136
136
|
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,31 +16,34 @@ 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
|
|
23
|
-
LABEL_MARGIN =
|
|
25
|
+
LABEL_MARGIN = 15.0
|
|
24
26
|
DEFAULT_MARGIN = 20.0
|
|
25
27
|
|
|
26
28
|
DEFAULT_TARGET_WIDTH = 800.0
|
|
27
29
|
|
|
28
30
|
# Blank space above the graph. Default is +20+.
|
|
29
|
-
|
|
31
|
+
attr_writer :top_margin
|
|
30
32
|
|
|
31
33
|
# Blank space below the graph. Default is +20+.
|
|
32
|
-
|
|
34
|
+
attr_writer :bottom_margin
|
|
33
35
|
|
|
34
36
|
# Blank space to the right of the graph. Default is +20+.
|
|
35
|
-
|
|
37
|
+
attr_writer :right_margin
|
|
36
38
|
|
|
37
39
|
# Blank space to the left of the graph. Default is +20+.
|
|
38
|
-
|
|
40
|
+
attr_writer :left_margin
|
|
39
41
|
|
|
40
42
|
# Blank space below the title. Default is +20+.
|
|
41
|
-
|
|
43
|
+
attr_writer :title_margin
|
|
42
44
|
|
|
43
45
|
# Blank space below the legend. Default is +20+.
|
|
44
|
-
|
|
46
|
+
attr_writer :legend_margin
|
|
45
47
|
|
|
46
48
|
# A hash of names for the individual columns, where the key is the array
|
|
47
49
|
# index for the column this label represents.
|
|
@@ -50,118 +52,115 @@ module Gruff
|
|
|
50
52
|
#
|
|
51
53
|
# @example
|
|
52
54
|
# { 0 => 2005, 3 => 2006, 5 => 2007, 7 => 2008 }
|
|
53
|
-
|
|
55
|
+
attr_writer :labels
|
|
54
56
|
|
|
55
57
|
# Used internally for spacing.
|
|
56
58
|
#
|
|
57
59
|
# By default, labels are centered over the point they represent.
|
|
58
|
-
|
|
60
|
+
attr_writer :center_labels_over_point
|
|
59
61
|
|
|
60
62
|
# Used internally for horizontal graph types. Default is +false+.
|
|
61
|
-
|
|
63
|
+
attr_writer :has_left_labels
|
|
62
64
|
|
|
63
65
|
# Set a label for the bottom of the graph.
|
|
64
|
-
|
|
66
|
+
attr_writer :x_axis_label
|
|
65
67
|
|
|
66
68
|
# Set a label for the left side of the graph.
|
|
67
|
-
|
|
69
|
+
attr_writer :y_axis_label
|
|
68
70
|
|
|
69
71
|
# Set increment of the vertical marking lines.
|
|
70
|
-
|
|
72
|
+
attr_writer :x_axis_increment
|
|
71
73
|
|
|
72
74
|
# Set increment of the horizontal marking lines.
|
|
73
|
-
|
|
75
|
+
attr_writer :y_axis_increment
|
|
74
76
|
|
|
75
77
|
# Height of staggering between labels (Bar graph only).
|
|
76
|
-
|
|
78
|
+
attr_writer :label_stagger_height
|
|
77
79
|
|
|
78
80
|
# Truncates labels if longer than max specified.
|
|
79
|
-
|
|
81
|
+
attr_writer :label_max_size
|
|
80
82
|
|
|
81
|
-
# How truncated labels visually appear if they exceed {#label_max_size}.
|
|
83
|
+
# How truncated labels visually appear if they exceed {#label_max_size=}.
|
|
82
84
|
#
|
|
83
85
|
# - +:absolute+ - does not show trailing dots to indicate truncation. This is the default.
|
|
84
|
-
# - +:trailing_dots+ - shows trailing dots to indicate truncation (note that {#label_max_size}
|
|
86
|
+
# - +:trailing_dots+ - shows trailing dots to indicate truncation (note that {#label_max_size=}
|
|
85
87
|
# must be greater than 3).
|
|
86
|
-
|
|
88
|
+
attr_writer :label_truncation_style
|
|
87
89
|
|
|
88
90
|
# Get or set the list of colors that will be used to draw the bars or lines.
|
|
89
91
|
attr_accessor :colors
|
|
90
92
|
|
|
91
93
|
# Set the large title of the graph displayed at the top.
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
# Font used for titles, labels, etc. Works best if you provide the full
|
|
95
|
-
# path to the TTF font file. RMagick must be built with the Freetype
|
|
96
|
-
# libraries for this to work properly.
|
|
97
|
-
attr_reader :font
|
|
94
|
+
attr_writer :title
|
|
98
95
|
|
|
99
|
-
# Same as {#font} but for the title.
|
|
100
|
-
|
|
96
|
+
# Same as {#font=} but for the title.
|
|
97
|
+
attr_writer :title_font
|
|
101
98
|
|
|
102
99
|
# Specifies whether to draw the title bolded or not. Default is +true+.
|
|
103
|
-
|
|
100
|
+
attr_writer :bold_title
|
|
104
101
|
|
|
105
102
|
# Specifies the text color.
|
|
106
|
-
|
|
103
|
+
attr_writer :font_color
|
|
107
104
|
|
|
108
105
|
# Prevent drawing of line markers. Default is +false+.
|
|
109
|
-
|
|
106
|
+
attr_writer :hide_line_markers
|
|
110
107
|
|
|
111
108
|
# Prevent drawing of the legend. Default is +false+.
|
|
112
|
-
|
|
109
|
+
attr_writer :hide_legend
|
|
113
110
|
|
|
114
111
|
# Prevent drawing of the title. Default is +false+.
|
|
115
|
-
|
|
112
|
+
attr_writer :hide_title
|
|
116
113
|
|
|
117
114
|
# Prevent drawing of line numbers. Default is +false+.
|
|
118
|
-
|
|
115
|
+
attr_writer :hide_line_numbers
|
|
119
116
|
|
|
120
117
|
# Set a message shown when there is no data. Fits up to 20 characters. Defaults
|
|
121
118
|
# to +"No Data."+.
|
|
122
|
-
|
|
119
|
+
attr_writer :no_data_message
|
|
123
120
|
|
|
124
121
|
# Set the font size of the large title at the top of the graph. Default is +36+.
|
|
125
|
-
|
|
122
|
+
attr_writer :title_font_size
|
|
126
123
|
|
|
127
124
|
# Optionally set the size of the font. Based on an 800x600px graph.
|
|
128
125
|
# Default is +20+.
|
|
129
126
|
#
|
|
130
127
|
# Will be scaled down if the graph is smaller than 800px wide.
|
|
131
|
-
|
|
128
|
+
attr_writer :legend_font_size
|
|
132
129
|
|
|
133
130
|
# Display the legend under the graph. Default is +false+.
|
|
134
|
-
|
|
131
|
+
attr_writer :legend_at_bottom
|
|
135
132
|
|
|
136
133
|
# The font size of the labels around the graph. Default is +21+.
|
|
137
|
-
|
|
134
|
+
attr_writer :marker_font_size
|
|
138
135
|
|
|
139
136
|
# Set the color of the auxiliary lines.
|
|
140
|
-
|
|
137
|
+
attr_writer :marker_color
|
|
141
138
|
|
|
142
139
|
# Set the shadow color of the auxiliary lines.
|
|
143
|
-
|
|
140
|
+
attr_writer :marker_shadow_color
|
|
144
141
|
|
|
145
142
|
# Set the number of horizontal lines shown for reference.
|
|
146
|
-
|
|
143
|
+
attr_writer :marker_count
|
|
147
144
|
|
|
148
145
|
# Set to +true+ if you want the data sets sorted with largest avg values drawn
|
|
149
146
|
# first. Default is +false+.
|
|
150
|
-
|
|
147
|
+
attr_writer :sort
|
|
151
148
|
|
|
152
149
|
# Set to +true+ if you want the data sets drawn with largest avg values drawn
|
|
153
150
|
# first. This does not affect the legend. Default is +false+.
|
|
154
|
-
|
|
151
|
+
attr_writer :sorted_drawing
|
|
155
152
|
|
|
156
153
|
# Optionally set the size of the colored box by each item in the legend.
|
|
157
154
|
# Default is +20.0+.
|
|
158
155
|
#
|
|
159
156
|
# Will be scaled down if graph is smaller than 800px wide.
|
|
160
|
-
|
|
157
|
+
attr_writer :legend_box_size
|
|
158
|
+
|
|
159
|
+
# Allow passing lambdas to format labels for x axis.
|
|
160
|
+
attr_writer :x_axis_label_format
|
|
161
161
|
|
|
162
|
-
#
|
|
163
|
-
|
|
164
|
-
attr_accessor :use_data_label
|
|
162
|
+
# Allow passing lambdas to format labels for y axis.
|
|
163
|
+
attr_writer :y_axis_label_format
|
|
165
164
|
|
|
166
165
|
# If one numerical argument is given, the graph is drawn at 4/3 ratio
|
|
167
166
|
# according to the given width (+800+ results in 800x600, +400+ gives 400x300,
|
|
@@ -173,9 +172,7 @@ module Gruff
|
|
|
173
172
|
#
|
|
174
173
|
def initialize(target_width = DEFAULT_TARGET_WIDTH)
|
|
175
174
|
if target_width.is_a?(String)
|
|
176
|
-
|
|
177
|
-
@columns = geometric_width.to_f
|
|
178
|
-
@rows = geometric_height.to_f
|
|
175
|
+
@columns, @rows = target_width.split('x').map(&:to_f)
|
|
179
176
|
else
|
|
180
177
|
@columns = target_width.to_f
|
|
181
178
|
@rows = target_width.to_f * 0.75
|
|
@@ -183,19 +180,14 @@ module Gruff
|
|
|
183
180
|
@columns.freeze
|
|
184
181
|
@rows.freeze
|
|
185
182
|
|
|
183
|
+
initialize_graph_scale
|
|
186
184
|
initialize_ivars
|
|
185
|
+
initialize_store
|
|
187
186
|
|
|
188
187
|
self.theme = Themes::KEYNOTE
|
|
189
188
|
end
|
|
190
189
|
|
|
191
|
-
|
|
192
|
-
#
|
|
193
|
-
# Subclasses can override this, call super, then set values separately.
|
|
194
|
-
#
|
|
195
|
-
# This makes it possible to set defaults in a subclass but still allow
|
|
196
|
-
# developers to change this values in their program.
|
|
197
|
-
def initialize_ivars
|
|
198
|
-
# Internal for calculations
|
|
190
|
+
def initialize_graph_scale
|
|
199
191
|
@raw_columns = DEFAULT_TARGET_WIDTH
|
|
200
192
|
@raw_rows = DEFAULT_TARGET_WIDTH * (@rows / @columns)
|
|
201
193
|
@raw_columns.freeze
|
|
@@ -203,16 +195,29 @@ module Gruff
|
|
|
203
195
|
|
|
204
196
|
@scale = @columns / @raw_columns
|
|
205
197
|
@scale.freeze
|
|
198
|
+
end
|
|
199
|
+
protected :initialize_graph_scale
|
|
206
200
|
|
|
201
|
+
def initialize_store
|
|
202
|
+
@store = Gruff::Store.new(Gruff::Store::BasicData)
|
|
203
|
+
end
|
|
204
|
+
protected :initialize_store
|
|
205
|
+
|
|
206
|
+
# Initialize instance variable of attributes
|
|
207
|
+
#
|
|
208
|
+
# Subclasses can override this, call super, then set values separately.
|
|
209
|
+
#
|
|
210
|
+
# This makes it possible to set defaults in a subclass but still allow
|
|
211
|
+
# developers to change this values in their program.
|
|
212
|
+
def initialize_ivars
|
|
207
213
|
@marker_count = nil
|
|
208
214
|
@maximum_value = @minimum_value = nil
|
|
209
|
-
@increment = nil
|
|
210
215
|
@labels = {}
|
|
211
216
|
@sort = false
|
|
212
217
|
@sorted_drawing = false
|
|
213
218
|
@title = nil
|
|
214
|
-
@title_font = nil
|
|
215
219
|
|
|
220
|
+
@title_font = nil
|
|
216
221
|
@font = nil
|
|
217
222
|
@bold_title = true
|
|
218
223
|
|
|
@@ -235,14 +240,12 @@ module Gruff
|
|
|
235
240
|
@label_max_size = 0
|
|
236
241
|
@label_truncation_style = :absolute
|
|
237
242
|
|
|
238
|
-
@theme_options = {}
|
|
239
|
-
|
|
240
|
-
@use_data_label = false
|
|
241
243
|
@x_axis_increment = nil
|
|
242
244
|
@x_axis_label = @y_axis_label = nil
|
|
243
245
|
@y_axis_increment = nil
|
|
244
246
|
|
|
245
|
-
@
|
|
247
|
+
@x_axis_label_format = nil
|
|
248
|
+
@y_axis_label_format = nil
|
|
246
249
|
end
|
|
247
250
|
protected :initialize_ivars
|
|
248
251
|
|
|
@@ -298,11 +301,20 @@ module Gruff
|
|
|
298
301
|
# graph.theme = {
|
|
299
302
|
# colors: %w(orange purple green white red),
|
|
300
303
|
# marker_color: 'blue',
|
|
301
|
-
# background_colors: ['black', 'grey',
|
|
304
|
+
# background_colors: ['black', 'grey'],
|
|
305
|
+
# background_direction: :top_bottom
|
|
302
306
|
# }
|
|
303
307
|
#
|
|
304
308
|
# +background_image: 'squirrel.png'+ is also possible.
|
|
305
309
|
#
|
|
310
|
+
# +background_direction+ accepts one of following parameters.
|
|
311
|
+
# - +:top_bottom+
|
|
312
|
+
# - +:bottom_top+
|
|
313
|
+
# - +:left_right+
|
|
314
|
+
# - +:right_left+
|
|
315
|
+
# - +:topleft_bottomright+
|
|
316
|
+
# - +:topright_bottomleft+
|
|
317
|
+
#
|
|
306
318
|
# (Or hopefully something better looking than that.)
|
|
307
319
|
#
|
|
308
320
|
# @param options [Hash] The optional setting for theme
|
|
@@ -408,14 +420,40 @@ module Gruff
|
|
|
408
420
|
# @example
|
|
409
421
|
# write('graphs/my_pretty_graph.png')
|
|
410
422
|
def write(file_name = 'graph.png')
|
|
411
|
-
|
|
412
|
-
|
|
423
|
+
to_image.write(file_name)
|
|
424
|
+
end
|
|
425
|
+
|
|
426
|
+
# Return a rendered graph image.
|
|
427
|
+
# This can use RMagick's methods to adjust the image before saving.
|
|
428
|
+
#
|
|
429
|
+
# @return [Magick::Image] The rendered image.
|
|
430
|
+
#
|
|
431
|
+
# @example
|
|
432
|
+
# g = Gruff::Line.new
|
|
433
|
+
# g.data :Jimmy, [25, 36, 86, 39, 25, 31, 79, 88]
|
|
434
|
+
# g.data :Charles, [80, 54, 67, 54, 68, 70, 90, 95]
|
|
435
|
+
# image = g.to_image
|
|
436
|
+
# image = image.resize(400, 300).quantize(128, Magick::RGBColorspace)
|
|
437
|
+
# image.write('test.png')
|
|
438
|
+
#
|
|
439
|
+
def to_image
|
|
440
|
+
@to_image ||= begin
|
|
441
|
+
draw
|
|
442
|
+
Gruff::Renderer.finish
|
|
443
|
+
Gruff::Renderer.instance.image
|
|
444
|
+
end
|
|
413
445
|
end
|
|
414
446
|
|
|
415
447
|
# Return the graph as a rendered binary blob.
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
448
|
+
#
|
|
449
|
+
# @param image_format [String] The image format of binary blob.
|
|
450
|
+
#
|
|
451
|
+
# @deprecated Please use +to_image.to_blob+ instead.
|
|
452
|
+
def to_blob(image_format = 'PNG')
|
|
453
|
+
warn '#to_blob is deprecated. Please use `to_image.to_blob` instead'
|
|
454
|
+
to_image.to_blob do
|
|
455
|
+
self.format = image_format
|
|
456
|
+
end
|
|
419
457
|
end
|
|
420
458
|
|
|
421
459
|
protected
|
|
@@ -478,6 +516,18 @@ module Gruff
|
|
|
478
516
|
store.columns
|
|
479
517
|
end
|
|
480
518
|
|
|
519
|
+
def marker_count
|
|
520
|
+
@marker_count ||= begin
|
|
521
|
+
count = nil
|
|
522
|
+
(3..7).each do |lines|
|
|
523
|
+
if @spread.to_f % lines == 0.0
|
|
524
|
+
count = lines and break
|
|
525
|
+
end
|
|
526
|
+
end
|
|
527
|
+
count || 4
|
|
528
|
+
end
|
|
529
|
+
end
|
|
530
|
+
|
|
481
531
|
# Make copy of data with values scaled between 0-100
|
|
482
532
|
def normalize
|
|
483
533
|
store.normalize(minimum: minimum_value, spread: @spread)
|
|
@@ -492,61 +542,33 @@ module Gruff
|
|
|
492
542
|
@hide_title || @title.nil? || @title.empty?
|
|
493
543
|
end
|
|
494
544
|
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
def setup_graph_measurements
|
|
499
|
-
@marker_caps_height = @hide_line_markers ? 0 : calculate_caps_height(@marker_font_size)
|
|
500
|
-
@title_caps_height = hide_title? ? 0 : calculate_caps_height(@title_font_size) * @title.lines.to_a.size
|
|
501
|
-
@legend_caps_height = @hide_legend ? 0 : calculate_caps_height(@legend_font_size)
|
|
502
|
-
|
|
503
|
-
if @hide_line_markers
|
|
504
|
-
@graph_left = @left_margin
|
|
505
|
-
@graph_right_margin = @right_margin
|
|
506
|
-
@graph_bottom_margin = @bottom_margin
|
|
507
|
-
else
|
|
508
|
-
if @has_left_labels
|
|
509
|
-
longest_left_label_width = calculate_width(@marker_font_size,
|
|
510
|
-
labels.values.reduce('') { |value, memo| (value.to_s.length > memo.to_s.length) ? value : memo }) * 1.25
|
|
511
|
-
else
|
|
512
|
-
longest_left_label_width = calculate_width(@marker_font_size,
|
|
513
|
-
label(maximum_value.to_f, @increment))
|
|
514
|
-
end
|
|
515
|
-
|
|
516
|
-
# Shift graph if left line numbers are hidden
|
|
517
|
-
line_number_width = @hide_line_numbers && !@has_left_labels ? 0.0 : (longest_left_label_width + LABEL_MARGIN * 2)
|
|
545
|
+
def hide_labels?
|
|
546
|
+
@hide_line_markers
|
|
547
|
+
end
|
|
518
548
|
|
|
519
|
-
|
|
549
|
+
def hide_left_label_area?
|
|
550
|
+
@hide_line_markers
|
|
551
|
+
end
|
|
520
552
|
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
extra_room_for_long_label = begin
|
|
525
|
-
(last_label >= (column_count - 1) && @center_labels_over_point) ? calculate_width(@marker_font_size, @labels[last_label]) / 2.0 : 0
|
|
526
|
-
end
|
|
527
|
-
@graph_right_margin = @right_margin + extra_room_for_long_label
|
|
553
|
+
def hide_bottom_label_area?
|
|
554
|
+
@hide_line_markers
|
|
555
|
+
end
|
|
528
556
|
|
|
529
|
-
|
|
530
|
-
|
|
557
|
+
##
|
|
558
|
+
# Calculates size of drawable area, general font dimensions, etc.
|
|
531
559
|
|
|
532
|
-
|
|
533
|
-
@
|
|
560
|
+
def setup_graph_measurements
|
|
561
|
+
@marker_caps_height = setup_marker_caps_height
|
|
562
|
+
@title_caps_height = setup_title_caps_height
|
|
563
|
+
@legend_caps_height = setup_legend_caps_height
|
|
534
564
|
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
@
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
else
|
|
541
|
-
@top_margin +
|
|
542
|
-
(hide_title? ? title_margin : @title_caps_height + title_margin) +
|
|
543
|
-
(@hide_legend ? legend_margin : @legend_caps_height + legend_margin)
|
|
544
|
-
end
|
|
545
|
-
end
|
|
565
|
+
margin_on_right = graph_right_margin
|
|
566
|
+
@graph_right = @raw_columns - margin_on_right
|
|
567
|
+
@graph_left = setup_left_margin
|
|
568
|
+
@graph_top = setup_top_margin
|
|
569
|
+
@graph_bottom = setup_bottom_margin
|
|
546
570
|
|
|
547
|
-
|
|
548
|
-
# FIXME: Consider chart types other than bar
|
|
549
|
-
@graph_bottom = @raw_rows - @graph_bottom_margin - x_axis_label_height - @label_stagger_height
|
|
571
|
+
@graph_width = @raw_columns - @graph_left - margin_on_right
|
|
550
572
|
@graph_height = @graph_bottom - @graph_top
|
|
551
573
|
end
|
|
552
574
|
|
|
@@ -556,17 +578,17 @@ module Gruff
|
|
|
556
578
|
# X Axis
|
|
557
579
|
# Centered vertically and horizontally by setting the
|
|
558
580
|
# height to 1.0 and the width to the width of the graph.
|
|
559
|
-
x_axis_label_y_coordinate = @graph_bottom + LABEL_MARGIN
|
|
581
|
+
x_axis_label_y_coordinate = @graph_bottom + LABEL_MARGIN + @marker_caps_height
|
|
560
582
|
|
|
561
583
|
# TODO: Center between graph area
|
|
562
584
|
text_renderer = Gruff::Renderer::Text.new(@x_axis_label, font: @font, size: @marker_font_size, color: @font_color)
|
|
563
|
-
text_renderer.
|
|
585
|
+
text_renderer.add_to_render_queue(@raw_columns, 1.0, 0.0, x_axis_label_y_coordinate)
|
|
564
586
|
end
|
|
565
587
|
|
|
566
588
|
if @y_axis_label
|
|
567
589
|
# Y Axis, rotated vertically
|
|
568
590
|
text_renderer = Gruff::Renderer::Text.new(@y_axis_label, font: @font, size: @marker_font_size, color: @font_color, rotation: -90)
|
|
569
|
-
text_renderer.
|
|
591
|
+
text_renderer.add_to_render_queue(1.0, @raw_rows, @left_margin + @marker_caps_height / 2.0, 0.0, Magick::CenterGravity)
|
|
570
592
|
end
|
|
571
593
|
end
|
|
572
594
|
|
|
@@ -577,31 +599,21 @@ module Gruff
|
|
|
577
599
|
increment_scaled = @graph_height.to_f / (@spread / @increment)
|
|
578
600
|
|
|
579
601
|
# Draw horizontal line markers and annotate with numbers
|
|
580
|
-
(0
|
|
602
|
+
(0..marker_count).each do |index|
|
|
581
603
|
y = @graph_top + @graph_height - index.to_f * increment_scaled
|
|
582
604
|
|
|
583
|
-
Gruff::Renderer::Line.new(color: @marker_color
|
|
584
|
-
|
|
585
|
-
if @marker_shadow_color
|
|
586
|
-
Gruff::Renderer::Line.new(color: @marker_shadow_color).render(@graph_left, y + 1, @graph_right, y + 1)
|
|
587
|
-
end
|
|
605
|
+
line_renderer = Gruff::Renderer::Line.new(color: @marker_color, shadow_color: @marker_shadow_color)
|
|
606
|
+
line_renderer.render(@graph_left, y, @graph_right, y)
|
|
588
607
|
|
|
589
608
|
unless @hide_line_numbers
|
|
590
609
|
marker_label = BigDecimal(index.to_s) * BigDecimal(@increment.to_s) + BigDecimal(minimum_value.to_s)
|
|
591
|
-
label =
|
|
610
|
+
label = y_axis_label(marker_label, @increment)
|
|
592
611
|
text_renderer = Gruff::Renderer::Text.new(label, font: @font, size: @marker_font_size, color: @font_color)
|
|
593
|
-
text_renderer.
|
|
612
|
+
text_renderer.add_to_render_queue(@graph_left - LABEL_MARGIN, 1.0, 0.0, y, Magick::EastGravity)
|
|
594
613
|
end
|
|
595
614
|
end
|
|
596
615
|
end
|
|
597
616
|
|
|
598
|
-
# Return the sum of values in an array.
|
|
599
|
-
#
|
|
600
|
-
# Duplicated to not conflict with active_support in Rails.
|
|
601
|
-
def sum(arr)
|
|
602
|
-
arr.reduce(0) { |i, m| m + i }
|
|
603
|
-
end
|
|
604
|
-
|
|
605
617
|
# Return a calculation of center
|
|
606
618
|
def center(size)
|
|
607
619
|
(@raw_columns - size) / 2
|
|
@@ -613,27 +625,15 @@ module Gruff
|
|
|
613
625
|
return if @hide_legend
|
|
614
626
|
|
|
615
627
|
legend_labels = store.data.map(&:label)
|
|
616
|
-
|
|
617
628
|
legend_square_width = @legend_box_size # small square with color of this item
|
|
629
|
+
label_widths = calculate_legend_label_widths_for_each_line(legend_labels, legend_square_width)
|
|
618
630
|
|
|
619
|
-
|
|
620
|
-
label_widths = [[]] # Used to calculate line wrap
|
|
621
|
-
legend_labels.each do |label|
|
|
622
|
-
width = calculate_width(@legend_font_size, label)
|
|
623
|
-
label_width = width + legend_square_width * 2.7
|
|
624
|
-
label_widths.last.push label_width
|
|
625
|
-
|
|
626
|
-
if sum(label_widths.last) > (@raw_columns * 0.9)
|
|
627
|
-
label_widths.push [label_widths.last.pop]
|
|
628
|
-
end
|
|
629
|
-
end
|
|
630
|
-
|
|
631
|
-
current_x_offset = center(sum(label_widths.first))
|
|
631
|
+
current_x_offset = center(label_widths.first.sum)
|
|
632
632
|
current_y_offset = begin
|
|
633
633
|
if @legend_at_bottom
|
|
634
|
-
@
|
|
634
|
+
@graph_bottom + @legend_margin + @legend_caps_height + LABEL_MARGIN
|
|
635
635
|
else
|
|
636
|
-
hide_title? ? @top_margin + title_margin : @top_margin + title_margin + @title_caps_height
|
|
636
|
+
hide_title? ? @top_margin + @title_margin : @top_margin + @title_margin + @title_caps_height
|
|
637
637
|
end
|
|
638
638
|
end
|
|
639
639
|
|
|
@@ -642,7 +642,7 @@ module Gruff
|
|
|
642
642
|
|
|
643
643
|
# Draw label
|
|
644
644
|
text_renderer = Gruff::Renderer::Text.new(legend_label, font: @font, size: @legend_font_size, color: @font_color)
|
|
645
|
-
text_renderer.
|
|
645
|
+
text_renderer.add_to_render_queue(@raw_columns, 1.0, current_x_offset + (legend_square_width * 1.7), current_y_offset, Magick::WestGravity)
|
|
646
646
|
|
|
647
647
|
# Now draw box with color of this dataset
|
|
648
648
|
rect_renderer = Gruff::Renderer::Rectangle.new(color: store.data[index].color)
|
|
@@ -651,23 +651,19 @@ module Gruff
|
|
|
651
651
|
current_x_offset + legend_square_width,
|
|
652
652
|
current_y_offset + legend_square_width / 2.0)
|
|
653
653
|
|
|
654
|
-
width = calculate_width(legend_font_size, legend_label)
|
|
655
|
-
|
|
654
|
+
width = calculate_width(@legend_font_size, legend_label)
|
|
655
|
+
current_x_offset += width + (legend_square_width * 2.7)
|
|
656
|
+
label_widths.first.shift
|
|
656
657
|
|
|
657
658
|
# Handle wrapping
|
|
658
|
-
label_widths.first.shift
|
|
659
659
|
if label_widths.first.empty?
|
|
660
660
|
label_widths.shift
|
|
661
|
-
current_x_offset = center(
|
|
662
|
-
line_height = [@legend_caps_height, legend_square_width].max + legend_margin
|
|
661
|
+
current_x_offset = center(label_widths.first.sum) unless label_widths.empty?
|
|
662
|
+
line_height = [@legend_caps_height, legend_square_width].max + @legend_margin
|
|
663
663
|
unless label_widths.empty?
|
|
664
664
|
# Wrap to next line and shrink available graph dimensions
|
|
665
665
|
current_y_offset += line_height
|
|
666
|
-
@graph_top += line_height
|
|
667
|
-
@graph_height = @graph_bottom - @graph_top
|
|
668
666
|
end
|
|
669
|
-
else
|
|
670
|
-
current_x_offset += current_string_offset
|
|
671
667
|
end
|
|
672
668
|
end
|
|
673
669
|
end
|
|
@@ -680,12 +676,12 @@ module Gruff
|
|
|
680
676
|
font_weight = @bold_title ? Magick::BoldWeight : Magick::NormalWeight
|
|
681
677
|
font_size = @title_font_size
|
|
682
678
|
|
|
683
|
-
metrics = Renderer::Text.metrics(@title, font_size, font_weight)
|
|
679
|
+
metrics = Renderer::Text.metrics(@title, font, font_size, font_weight)
|
|
684
680
|
if metrics.width > @raw_columns
|
|
685
681
|
font_size = font_size * (@raw_columns / metrics.width) * 0.95
|
|
686
682
|
end
|
|
687
683
|
text_renderer = Gruff::Renderer::Text.new(@title, font: font, size: font_size, color: @font_color, weight: font_weight)
|
|
688
|
-
text_renderer.
|
|
684
|
+
text_renderer.add_to_render_queue(@raw_columns, 1.0, 0, @top_margin)
|
|
689
685
|
end
|
|
690
686
|
|
|
691
687
|
# Draws column labels below graph, centered over x_offset
|
|
@@ -700,17 +696,14 @@ module Gruff
|
|
|
700
696
|
# TODO: See if index.odd? is the best stragegy
|
|
701
697
|
y_offset += @label_stagger_height if index.odd?
|
|
702
698
|
|
|
703
|
-
label_text = truncate_label_text(labels[index].to_s)
|
|
704
|
-
|
|
705
699
|
if x_offset >= @graph_left && x_offset <= @graph_right
|
|
706
|
-
|
|
707
|
-
text_renderer.render(1.0, 1.0, x_offset, y_offset, gravity)
|
|
700
|
+
draw_label_at(1.0, 1.0, x_offset, y_offset, @labels[index], gravity)
|
|
708
701
|
end
|
|
709
702
|
end
|
|
710
703
|
end
|
|
711
704
|
|
|
712
705
|
def draw_unique_label(index)
|
|
713
|
-
return if
|
|
706
|
+
return if hide_labels?
|
|
714
707
|
|
|
715
708
|
@labels_seen ||= {}
|
|
716
709
|
if !@labels[index].nil? && @labels_seen[index].nil?
|
|
@@ -719,18 +712,24 @@ module Gruff
|
|
|
719
712
|
end
|
|
720
713
|
end
|
|
721
714
|
|
|
715
|
+
def draw_label_at(width, height, x, y, text, gravity = Magick::NorthGravity)
|
|
716
|
+
label_text = truncate_label_text(text)
|
|
717
|
+
text_renderer = Gruff::Renderer::Text.new(label_text, font: @font, size: @marker_font_size, color: @font_color)
|
|
718
|
+
text_renderer.add_to_render_queue(width, height, x, y, gravity)
|
|
719
|
+
end
|
|
720
|
+
|
|
722
721
|
# Draws the data value over the data point in bar graphs
|
|
723
722
|
def draw_value_label(x_offset, y_offset, data_point, bar_value = false)
|
|
724
723
|
return if @hide_line_markers && !bar_value
|
|
725
724
|
|
|
726
725
|
text_renderer = Gruff::Renderer::Text.new(data_point, font: @font, size: @marker_font_size, color: @font_color)
|
|
727
|
-
text_renderer.
|
|
726
|
+
text_renderer.add_to_render_queue(1.0, 1.0, x_offset, y_offset)
|
|
728
727
|
end
|
|
729
728
|
|
|
730
729
|
# Shows an error message because you have no data.
|
|
731
730
|
def draw_no_data
|
|
732
731
|
text_renderer = Gruff::Renderer::Text.new(@no_data_message, font: @font, size: 80, color: @font_color)
|
|
733
|
-
text_renderer.render(@raw_columns, @raw_rows
|
|
732
|
+
text_renderer.render(@raw_columns, @raw_rows, 0, 0, Magick::CenterGravity)
|
|
734
733
|
end
|
|
735
734
|
|
|
736
735
|
# Resets everything to defaults (except data).
|
|
@@ -794,7 +793,67 @@ module Gruff
|
|
|
794
793
|
|
|
795
794
|
private
|
|
796
795
|
|
|
796
|
+
def setup_marker_caps_height
|
|
797
|
+
hide_bottom_label_area? ? 0 : calculate_caps_height(@marker_font_size)
|
|
798
|
+
end
|
|
799
|
+
|
|
800
|
+
def setup_title_caps_height
|
|
801
|
+
hide_title? ? 0 : calculate_caps_height(@title_font_size) * @title.lines.to_a.size
|
|
802
|
+
end
|
|
803
|
+
|
|
804
|
+
def setup_legend_caps_height
|
|
805
|
+
@hide_legend ? 0 : calculate_caps_height(@legend_font_size)
|
|
806
|
+
end
|
|
807
|
+
|
|
808
|
+
def graph_right_margin
|
|
809
|
+
@hide_line_markers ? @right_margin : @right_margin + extra_room_for_long_label
|
|
810
|
+
end
|
|
811
|
+
|
|
812
|
+
def extra_room_for_long_label
|
|
813
|
+
# Make space for half the width of the rightmost column label.
|
|
814
|
+
# Might be greater than the number of columns if between-style bar markers are used.
|
|
815
|
+
last_label = @labels.keys.max.to_i
|
|
816
|
+
(last_label >= (column_count - 1) && @center_labels_over_point) ? calculate_width(@marker_font_size, @labels[last_label]) / 2.0 : 0
|
|
817
|
+
end
|
|
818
|
+
|
|
819
|
+
def setup_left_margin
|
|
820
|
+
return @left_margin if hide_left_label_area?
|
|
821
|
+
|
|
822
|
+
text = begin
|
|
823
|
+
if @has_left_labels
|
|
824
|
+
@labels.values.reduce('') { |value, memo| (value.to_s.length > memo.to_s.length) ? value : memo }
|
|
825
|
+
else
|
|
826
|
+
y_axis_label(maximum_value.to_f, @increment)
|
|
827
|
+
end
|
|
828
|
+
end
|
|
829
|
+
longest_left_label_width = calculate_width(@marker_font_size, truncate_label_text(text))
|
|
830
|
+
longest_left_label_width *= 1.25 if @has_left_labels
|
|
831
|
+
|
|
832
|
+
# Shift graph if left line numbers are hidden
|
|
833
|
+
line_number_width = @hide_line_numbers && !@has_left_labels ? 0.0 : (longest_left_label_width + LABEL_MARGIN * 2)
|
|
834
|
+
|
|
835
|
+
@left_margin + line_number_width + (@y_axis_label.nil? ? 0.0 : @marker_caps_height + LABEL_MARGIN * 2)
|
|
836
|
+
end
|
|
837
|
+
|
|
838
|
+
def setup_top_margin
|
|
839
|
+
# When @hide title, leave a title_margin space for aesthetics.
|
|
840
|
+
# Same with @hide_legend
|
|
841
|
+
@top_margin +
|
|
842
|
+
(hide_title? ? @title_margin : @title_caps_height + @title_margin) +
|
|
843
|
+
((@hide_legend || @legend_at_bottom) ? @legend_margin : calculate_legend_height + @legend_margin)
|
|
844
|
+
end
|
|
845
|
+
|
|
846
|
+
def setup_bottom_margin
|
|
847
|
+
graph_bottom_margin = hide_bottom_label_area? ? @bottom_margin : @bottom_margin + @marker_caps_height + LABEL_MARGIN
|
|
848
|
+
graph_bottom_margin += (calculate_legend_height + @legend_margin) if @legend_at_bottom
|
|
849
|
+
|
|
850
|
+
x_axis_label_height = @x_axis_label.nil? ? 0.0 : @marker_caps_height + LABEL_MARGIN
|
|
851
|
+
# FIXME: Consider chart types other than bar
|
|
852
|
+
@raw_rows - graph_bottom_margin - x_axis_label_height - @label_stagger_height
|
|
853
|
+
end
|
|
854
|
+
|
|
797
855
|
def truncate_label_text(text)
|
|
856
|
+
text = text.to_s
|
|
798
857
|
return text if text.size <= @label_max_size
|
|
799
858
|
|
|
800
859
|
if @label_truncation_style == :trailing_dots
|
|
@@ -823,7 +882,7 @@ module Gruff
|
|
|
823
882
|
else
|
|
824
883
|
value.to_s
|
|
825
884
|
end
|
|
826
|
-
elsif (@spread.to_f % (
|
|
885
|
+
elsif (@spread.to_f % (marker_count.to_f == 0 ? 1 : marker_count.to_f) == 0) || !@y_axis_increment.nil?
|
|
827
886
|
value.to_i.to_s
|
|
828
887
|
elsif @spread > 10.0
|
|
829
888
|
sprintf('%0i', value)
|
|
@@ -838,17 +897,74 @@ module Gruff
|
|
|
838
897
|
parts.join('.')
|
|
839
898
|
end
|
|
840
899
|
|
|
900
|
+
def x_axis_label(value, increment)
|
|
901
|
+
if @x_axis_label_format
|
|
902
|
+
@x_axis_label_format.call(value)
|
|
903
|
+
else
|
|
904
|
+
label(value, increment)
|
|
905
|
+
end
|
|
906
|
+
end
|
|
907
|
+
|
|
908
|
+
def y_axis_label(value, increment)
|
|
909
|
+
if @y_axis_label_format
|
|
910
|
+
@y_axis_label_format.call(value)
|
|
911
|
+
else
|
|
912
|
+
label(value, increment)
|
|
913
|
+
end
|
|
914
|
+
end
|
|
915
|
+
|
|
916
|
+
def calculate_legend_label_widths_for_each_line(legend_labels, legend_square_width)
|
|
917
|
+
# May fix legend drawing problem at small sizes
|
|
918
|
+
label_widths = [[]] # Used to calculate line wrap
|
|
919
|
+
legend_labels.each do |label|
|
|
920
|
+
width = calculate_width(@legend_font_size, label)
|
|
921
|
+
label_width = width + legend_square_width * 2.7
|
|
922
|
+
label_widths.last.push label_width
|
|
923
|
+
|
|
924
|
+
if label_widths.last.sum > (@raw_columns * 0.9)
|
|
925
|
+
label_widths.push [label_widths.last.pop]
|
|
926
|
+
end
|
|
927
|
+
end
|
|
928
|
+
|
|
929
|
+
label_widths
|
|
930
|
+
end
|
|
931
|
+
|
|
932
|
+
def calculate_legend_height
|
|
933
|
+
return 0.0 if @hide_legend
|
|
934
|
+
|
|
935
|
+
legend_labels = store.data.map(&:label)
|
|
936
|
+
legend_square_width = @legend_box_size
|
|
937
|
+
label_widths = calculate_legend_label_widths_for_each_line(legend_labels, legend_square_width)
|
|
938
|
+
legend_height = 0.0
|
|
939
|
+
|
|
940
|
+
legend_labels.each_with_index do |legend_label, _index|
|
|
941
|
+
next if legend_label.empty?
|
|
942
|
+
|
|
943
|
+
label_widths.first.shift
|
|
944
|
+
if label_widths.first.empty?
|
|
945
|
+
label_widths.shift
|
|
946
|
+
line_height = [@legend_caps_height, legend_square_width].max + @legend_margin
|
|
947
|
+
unless label_widths.empty?
|
|
948
|
+
# Wrap to next line and shrink available graph dimensions
|
|
949
|
+
legend_height += line_height
|
|
950
|
+
end
|
|
951
|
+
end
|
|
952
|
+
end
|
|
953
|
+
|
|
954
|
+
legend_height + @legend_caps_height
|
|
955
|
+
end
|
|
956
|
+
|
|
841
957
|
# Returns the height of the capital letter 'X' for the current font and
|
|
842
958
|
# size.
|
|
843
959
|
#
|
|
844
960
|
# Not scaled since it deals with dimensions that the regular scaling will
|
|
845
961
|
# handle.
|
|
846
962
|
def calculate_caps_height(font_size)
|
|
847
|
-
metrics = Renderer::Text.metrics('X', font_size)
|
|
963
|
+
metrics = Renderer::Text.metrics('X', @font, font_size)
|
|
848
964
|
metrics.height
|
|
849
965
|
end
|
|
850
966
|
|
|
851
|
-
# Returns the width of a string at this
|
|
967
|
+
# Returns the width of a string at this point size.
|
|
852
968
|
#
|
|
853
969
|
# Not scaled since it deals with dimensions that the regular
|
|
854
970
|
# scaling will handle.
|
|
@@ -856,7 +972,7 @@ module Gruff
|
|
|
856
972
|
text = text.to_s
|
|
857
973
|
return 0 if text.empty?
|
|
858
974
|
|
|
859
|
-
metrics = Renderer::Text.metrics(text, font_size)
|
|
975
|
+
metrics = Renderer::Text.metrics(text, @font, font_size)
|
|
860
976
|
metrics.width
|
|
861
977
|
end
|
|
862
978
|
|
|
@@ -865,19 +981,10 @@ module Gruff
|
|
|
865
981
|
# Try to use a number of horizontal lines that will come out even.
|
|
866
982
|
#
|
|
867
983
|
# TODO Do the same for larger numbers...100, 75, 50, 25
|
|
868
|
-
|
|
869
|
-
(3..7).each do |lines|
|
|
870
|
-
if @spread % lines == 0.0
|
|
871
|
-
@marker_count = lines
|
|
872
|
-
break
|
|
873
|
-
end
|
|
874
|
-
end
|
|
875
|
-
@marker_count ||= 4
|
|
876
|
-
end
|
|
877
|
-
@increment = (@spread > 0 && @marker_count > 0) ? significant(@spread / @marker_count) : 1
|
|
984
|
+
@increment = (@spread > 0 && marker_count > 0) ? significant(@spread / marker_count) : 1
|
|
878
985
|
else
|
|
879
986
|
# TODO: Make this work for negative values
|
|
880
|
-
|
|
987
|
+
self.marker_count = (@spread / @y_axis_increment).to_i
|
|
881
988
|
@increment = @y_axis_increment
|
|
882
989
|
end
|
|
883
990
|
end
|