gruff 0.8.0 → 0.9.0
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/ISSUE_TEMPLATE.md +18 -0
- data/.gitignore +3 -0
- data/.rubocop.yml +93 -0
- data/.rubocop_todo.yml +23 -810
- data/.travis.yml +4 -4
- data/.yardopts +1 -0
- data/CHANGELOG.md +22 -0
- data/Gemfile +3 -1
- data/README.md +44 -21
- data/Rakefile +2 -206
- data/docker/Dockerfile +14 -0
- data/docker/build.sh +4 -0
- data/docker/launch.sh +4 -0
- data/gruff.gemspec +11 -8
- data/init.rb +2 -0
- data/lib/gruff.rb +23 -0
- data/lib/gruff/accumulator_bar.rb +6 -6
- data/lib/gruff/area.rb +13 -17
- data/lib/gruff/bar.rb +58 -41
- data/lib/gruff/base.rb +243 -566
- data/lib/gruff/bezier.rb +12 -14
- data/lib/gruff/bullet.rb +39 -57
- data/lib/gruff/dot.rb +25 -59
- data/lib/gruff/{bar_conversion.rb → helper/bar_conversion.rb} +13 -12
- data/lib/gruff/helper/bar_value_label_mixin.rb +30 -0
- data/lib/gruff/{stacked_mixin.rb → helper/stacked_mixin.rb} +7 -6
- data/lib/gruff/line.rb +95 -177
- data/lib/gruff/mini/bar.rb +6 -7
- data/lib/gruff/mini/legend.rb +16 -32
- data/lib/gruff/mini/pie.rb +6 -7
- data/lib/gruff/mini/side_bar.rb +4 -5
- data/lib/gruff/net.rb +37 -65
- data/lib/gruff/patch/rmagick.rb +33 -0
- data/lib/gruff/patch/string.rb +8 -0
- data/lib/gruff/photo_bar.rb +19 -19
- data/lib/gruff/pie.rb +22 -73
- data/lib/gruff/renderer/bezier.rb +21 -0
- data/lib/gruff/renderer/circle.rb +21 -0
- data/lib/gruff/renderer/dash_line.rb +22 -0
- data/lib/gruff/renderer/dot.rb +39 -0
- data/lib/gruff/renderer/ellipse.rb +21 -0
- data/lib/gruff/renderer/line.rb +34 -0
- data/lib/gruff/renderer/polygon.rb +23 -0
- data/lib/gruff/renderer/polyline.rb +21 -0
- data/lib/gruff/renderer/rectangle.rb +19 -0
- data/lib/gruff/renderer/renderer.rb +127 -0
- data/lib/gruff/renderer/text.rb +42 -0
- data/lib/gruff/scatter.rb +85 -156
- data/lib/gruff/scene.rb +22 -30
- data/lib/gruff/side_bar.rb +62 -58
- data/lib/gruff/side_stacked_bar.rb +47 -43
- data/lib/gruff/spider.rb +19 -36
- data/lib/gruff/stacked_area.rb +17 -21
- data/lib/gruff/stacked_bar.rb +50 -24
- data/lib/gruff/store/base_data.rb +34 -0
- data/lib/gruff/store/custom_data.rb +34 -0
- data/lib/gruff/store/store.rb +80 -0
- data/lib/gruff/store/xy_data.rb +55 -0
- data/lib/gruff/themes.rb +3 -3
- data/lib/gruff/version.rb +3 -1
- metadata +41 -30
- data/Manifest.txt +0 -81
- data/assets/bubble.png +0 -0
- data/assets/city_scene/background/0000.png +0 -0
- data/assets/city_scene/background/0600.png +0 -0
- data/assets/city_scene/background/2000.png +0 -0
- data/assets/city_scene/clouds/cloudy.png +0 -0
- data/assets/city_scene/clouds/partly_cloudy.png +0 -0
- data/assets/city_scene/clouds/stormy.png +0 -0
- data/assets/city_scene/grass/default.png +0 -0
- data/assets/city_scene/haze/true.png +0 -0
- data/assets/city_scene/number_sample/1.png +0 -0
- data/assets/city_scene/number_sample/2.png +0 -0
- data/assets/city_scene/number_sample/default.png +0 -0
- data/assets/city_scene/sky/0000.png +0 -0
- data/assets/city_scene/sky/0200.png +0 -0
- data/assets/city_scene/sky/0400.png +0 -0
- data/assets/city_scene/sky/0600.png +0 -0
- data/assets/city_scene/sky/0800.png +0 -0
- data/assets/city_scene/sky/1000.png +0 -0
- data/assets/city_scene/sky/1200.png +0 -0
- data/assets/city_scene/sky/1400.png +0 -0
- data/assets/city_scene/sky/1500.png +0 -0
- data/assets/city_scene/sky/1700.png +0 -0
- data/assets/city_scene/sky/2000.png +0 -0
- data/assets/pc306715.jpg +0 -0
- data/lib/gruff/deprecated.rb +0 -38
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Gruff
|
4
|
+
class Renderer::Text
|
5
|
+
def initialize(text, args = {})
|
6
|
+
@text = text.to_s
|
7
|
+
@font = args[:font]
|
8
|
+
@font_size = args[:size]
|
9
|
+
@font_color = args[:color]
|
10
|
+
@font_weight = args[:weight] || Magick::NormalWeight
|
11
|
+
@rotation = args[:rotation]
|
12
|
+
end
|
13
|
+
|
14
|
+
def render(width, height, x, y, gravity = Magick::NorthGravity)
|
15
|
+
draw = Renderer.instance.draw
|
16
|
+
image = Renderer.instance.image
|
17
|
+
scale = Renderer.instance.scale
|
18
|
+
|
19
|
+
draw.rotation = @rotation if @rotation
|
20
|
+
draw.fill = @font_color
|
21
|
+
draw.stroke = 'transparent'
|
22
|
+
draw.font = @font if @font
|
23
|
+
draw.font_weight = @font_weight
|
24
|
+
draw.pointsize = @font_size * scale
|
25
|
+
draw.gravity = gravity
|
26
|
+
draw.annotate_scaled(image,
|
27
|
+
width, height,
|
28
|
+
x, y,
|
29
|
+
@text, scale)
|
30
|
+
draw.rotation = -@rotation if @rotation
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.metrics(text, size, font_weight = Magick::NormalWeight)
|
34
|
+
draw = Renderer.instance.draw
|
35
|
+
image = Renderer.instance.image
|
36
|
+
|
37
|
+
draw.font_weight = font_weight
|
38
|
+
draw.pointsize = size
|
39
|
+
draw.get_type_metrics(image, text.to_s)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/lib/gruff/scatter.rb
CHANGED
@@ -1,15 +1,15 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'gruff/base'
|
2
4
|
|
3
5
|
# Here's how to set up an XY Scatter Chart
|
4
6
|
#
|
5
|
-
#
|
6
|
-
#
|
7
|
-
#
|
8
|
-
#
|
9
|
-
#
|
7
|
+
# g = Gruff::Scatter.new(800)
|
8
|
+
# g.data(:apples, [1,2,3,4], [4,3,2,1])
|
9
|
+
# g.data('oranges', [5,7,8], [4,1,7])
|
10
|
+
# g.write('test/output/scatter.png')
|
10
11
|
#
|
11
12
|
class Gruff::Scatter < Gruff::Base
|
12
|
-
|
13
13
|
# Maximum X Value. The value will get overwritten by the max in the
|
14
14
|
# datasets.
|
15
15
|
attr_accessor :maximum_x_value
|
@@ -18,47 +18,29 @@ class Gruff::Scatter < Gruff::Base
|
|
18
18
|
# datasets.
|
19
19
|
attr_accessor :minimum_x_value
|
20
20
|
|
21
|
-
# The number of vertical lines shown for reference
|
21
|
+
# The number of vertical lines shown for reference.
|
22
22
|
attr_accessor :marker_x_count
|
23
23
|
|
24
|
-
|
25
|
-
#~ attr_accessor :baseline_y_value
|
26
|
-
|
27
|
-
#~ # Color of the horizontal baseline
|
28
|
-
#~ attr_accessor :baseline_y_color
|
29
|
-
|
30
|
-
#~ # Draw a dashed horizontal line at the given y value
|
31
|
-
#~ attr_accessor :baseline_x_value
|
32
|
-
|
33
|
-
#~ # Color of the horizontal baseline
|
34
|
-
#~ attr_accessor :baseline_x_color
|
35
|
-
|
36
|
-
# Attributes to allow customising the size of the points
|
24
|
+
# Attributes to allow customising the size of the points.
|
37
25
|
attr_accessor :circle_radius
|
38
26
|
attr_accessor :stroke_width
|
39
27
|
|
40
|
-
# Allow disabling the significant rounding when labeling the X axis
|
41
|
-
# This is useful when working with a small range of high values (for example, a date range of months, while seconds as units)
|
28
|
+
# Allow disabling the significant rounding when labeling the X axis.
|
29
|
+
# This is useful when working with a small range of high values (for example, a date range of months, while seconds as units).
|
42
30
|
attr_accessor :disable_significant_rounding_x_axis
|
43
31
|
|
44
|
-
# Allow enabling vertical lines. When you have a lot of data, they can work great
|
32
|
+
# Allow enabling vertical lines. When you have a lot of data, they can work great.
|
45
33
|
attr_accessor :enable_vertical_line_markers
|
46
34
|
|
47
|
-
# Allow using vertical labels in the X axis (and setting the label margin)
|
35
|
+
# Allow using vertical labels in the X axis (and setting the label margin).
|
48
36
|
attr_accessor :x_label_margin
|
49
37
|
attr_accessor :use_vertical_x_labels
|
50
38
|
|
51
|
-
# Allow passing lambdas to format labels
|
39
|
+
# Allow passing lambdas to format labels.
|
52
40
|
attr_accessor :y_axis_label_format
|
53
41
|
attr_accessor :x_axis_label_format
|
54
42
|
|
55
|
-
|
56
|
-
#
|
57
|
-
# ==== Example
|
58
|
-
#
|
59
|
-
# g = Gruff::Scatter.new
|
60
|
-
#
|
61
|
-
def initialize(*)
|
43
|
+
def initialize_ivars
|
62
44
|
super
|
63
45
|
|
64
46
|
@baseline_x_color = @baseline_y_color = 'red'
|
@@ -73,61 +55,33 @@ class Gruff::Scatter < Gruff::Base
|
|
73
55
|
@x_axis_label_format = nil
|
74
56
|
@x_label_margin = nil
|
75
57
|
@y_axis_label_format = nil
|
76
|
-
end
|
77
58
|
|
78
|
-
|
79
|
-
# TODO Need to get x-axis labels working. Current behavior will be to not allow.
|
80
|
-
@labels = {}
|
81
|
-
|
82
|
-
super
|
83
|
-
|
84
|
-
# Translate our values so that we can use the base methods for drawing
|
85
|
-
# the standard chart stuff
|
86
|
-
@column_count = @x_spread
|
59
|
+
@store = Gruff::Store.new(Gruff::Store::XYData)
|
87
60
|
end
|
61
|
+
private :initialize_ivars
|
88
62
|
|
89
63
|
def draw
|
90
64
|
super
|
91
|
-
return unless
|
65
|
+
return unless data_given?
|
92
66
|
|
93
67
|
# Check to see if more than one datapoint was given. NaN can result otherwise.
|
94
|
-
@x_increment = (@
|
68
|
+
@x_increment = (@x_spread > 1) ? (@graph_width / (@x_spread - 1).to_f) : @graph_width
|
95
69
|
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
#~ @d.stroke_color @baseline_color
|
100
|
-
#~ @d.fill_opacity 0.0
|
101
|
-
#~ @d.stroke_dasharray(10, 20)
|
102
|
-
#~ @d.stroke_width 5
|
103
|
-
#~ @d.line(@graph_left, level, @graph_left + @graph_width, level)
|
104
|
-
#~ @d = @d.pop
|
105
|
-
#~ end
|
106
|
-
|
107
|
-
#~ if (defined?(@norm_x_baseline)) then
|
108
|
-
|
109
|
-
#~ end
|
110
|
-
|
111
|
-
@norm_data.each do |data_row|
|
112
|
-
data_row[DATA_VALUES_INDEX].each_with_index do |data_point, index|
|
113
|
-
x_value = data_row[DATA_VALUES_X_INDEX][index]
|
114
|
-
next if data_point.nil? || x_value.nil?
|
70
|
+
store.norm_data.each do |data_row|
|
71
|
+
data_row.coordinates.each do |x_value, y_value|
|
72
|
+
next if y_value.nil? || x_value.nil?
|
115
73
|
|
116
74
|
new_x = get_x_coord(x_value, @graph_width, @graph_left)
|
117
|
-
new_y = @graph_top + (@graph_height -
|
75
|
+
new_y = @graph_top + (@graph_height - y_value * @graph_height)
|
118
76
|
|
119
77
|
# Reset each time to avoid thin-line errors
|
120
|
-
@
|
121
|
-
|
122
|
-
|
123
|
-
@d = @d.stroke_width @stroke_width || clip_value_if_greater_than(@columns / (@norm_data.first[1].size * 4), 5.0)
|
124
|
-
|
125
|
-
circle_radius = @circle_radius || clip_value_if_greater_than(@columns / (@norm_data.first[1].size * 2.5), 5.0)
|
126
|
-
@d = @d.circle(new_x, new_y, new_x - circle_radius, new_y)
|
78
|
+
stroke_width = @stroke_width || clip_value_if_greater_than(@columns / (store.norm_data.first[1].size * 4), 5.0)
|
79
|
+
circle_radius = @circle_radius || clip_value_if_greater_than(@columns / (store.norm_data.first[1].size * 2.5), 5.0)
|
80
|
+
Gruff::Renderer::Circle.new(color: data_row.color, width: stroke_width).render(new_x, new_y, new_x - circle_radius, new_y)
|
127
81
|
end
|
128
82
|
end
|
129
83
|
|
130
|
-
|
84
|
+
Gruff::Renderer.finish
|
131
85
|
end
|
132
86
|
|
133
87
|
# The first parameter is the name of the dataset. The next two are the
|
@@ -140,57 +94,63 @@ class Gruff::Scatter < Gruff::Base
|
|
140
94
|
# If the color argument is nil, the next color from the default theme will
|
141
95
|
# be used.
|
142
96
|
#
|
143
|
-
#
|
144
|
-
#
|
97
|
+
# @note If you want to use a preset theme, you must set it before calling {#data}.
|
98
|
+
#
|
99
|
+
# @param name [String, Symbol] containing the name of the dataset.
|
100
|
+
# @param x_data_points [Array] An Array of of x-axis data points.
|
101
|
+
# @param y_data_points [Array] An Array of of y-axis data points.
|
102
|
+
# @param color [String] The hex string for the color of the dataset. Defaults to nil.
|
145
103
|
#
|
146
|
-
# ==== Parameters
|
147
|
-
# name:: String or Symbol containing the name of the dataset.
|
148
|
-
# x_data_points:: An Array of of x-axis data points.
|
149
|
-
# y_data_points:: An Array of of y-axis data points.
|
150
|
-
# color:: The hex string for the color of the dataset. Defaults to nil.
|
151
104
|
#
|
152
|
-
#
|
153
|
-
# Data points contain nil values::
|
105
|
+
# @raise [ArgumentError] Data points contain nil values.
|
154
106
|
# This error will get raised if either the x or y axis data points array
|
155
|
-
# contains a
|
156
|
-
# as how to graph
|
157
|
-
# x_data_points is empty
|
107
|
+
# contains a +nil+ value. The graph will not make an assumption
|
108
|
+
# as how to graph +nil+.
|
109
|
+
# @raise [ArgumentError] +x_data_points+ is empty.
|
158
110
|
# This error is raised when the array for the x-axis points are empty
|
159
|
-
# y_data_points is empty
|
160
|
-
# This error is raised when the array for the y-axis points are empty
|
161
|
-
# x_data_points.length != y_data_points.length
|
162
|
-
# Error means that the x and y axis point arrays do not match in length
|
111
|
+
# @raise [ArgumentError] +y_data_points+ is empty.
|
112
|
+
# This error is raised when the array for the y-axis points are empty.
|
113
|
+
# @raise [ArgumentError] +x_data_points.length != y_data_points.length+.
|
114
|
+
# Error means that the x and y axis point arrays do not match in length.
|
163
115
|
#
|
164
|
-
#
|
165
|
-
#
|
166
|
-
#
|
167
|
-
#
|
168
|
-
#
|
116
|
+
# @example
|
117
|
+
# g = Gruff::Scatter.new
|
118
|
+
# g.data(:apples, [1,2,3], [3,2,1])
|
119
|
+
# g.data('oranges', [1,1,1], [2,3,4])
|
120
|
+
# g.data('bitter_melon', [3,5,6], [6,7,8], '#000000')
|
169
121
|
#
|
170
122
|
def data(name, x_data_points = [], y_data_points = [], color = nil)
|
123
|
+
# make sure it's an array
|
124
|
+
x_data_points = Array(x_data_points)
|
125
|
+
y_data_points = Array(y_data_points)
|
126
|
+
|
171
127
|
raise ArgumentError, 'Data Points contain nil Value!' if x_data_points.include?(nil) || y_data_points.include?(nil)
|
172
128
|
raise ArgumentError, 'x_data_points is empty!' if x_data_points.empty?
|
173
129
|
raise ArgumentError, 'y_data_points is empty!' if y_data_points.empty?
|
174
130
|
raise ArgumentError, 'x_data_points.length != y_data_points.length!' if x_data_points.length != y_data_points.length
|
175
131
|
|
176
|
-
# Call the existing data routine for the y axis data
|
177
|
-
|
132
|
+
# Call the existing data routine for the x/y axis data
|
133
|
+
store.add(name, y_data_points, color, x_data_points)
|
134
|
+
end
|
178
135
|
|
179
|
-
|
180
|
-
last_elem = @data.length() - 1
|
181
|
-
@data[last_elem] << x_data_points
|
136
|
+
alias dataxy data
|
182
137
|
|
183
|
-
|
184
|
-
|
185
|
-
|
138
|
+
private
|
139
|
+
|
140
|
+
def setup_data
|
141
|
+
# Update the global min/max values for the x data
|
142
|
+
@maximum_x_value ||= store.max_x
|
143
|
+
@minimum_x_value ||= store.min_x
|
186
144
|
|
187
|
-
|
188
|
-
x_data_points.max : @maximum_x_value
|
189
|
-
@minimum_x_value = x_data_points.min < @minimum_x_value ?
|
190
|
-
x_data_points.min : @minimum_x_value
|
145
|
+
super
|
191
146
|
end
|
192
147
|
|
193
|
-
|
148
|
+
def setup_drawing
|
149
|
+
# TODO: Need to get x-axis labels working. Current behavior will be to not allow.
|
150
|
+
@labels = {}
|
151
|
+
|
152
|
+
super
|
153
|
+
end
|
194
154
|
|
195
155
|
def calculate_spread #:nodoc:
|
196
156
|
super
|
@@ -198,25 +158,10 @@ protected
|
|
198
158
|
@x_spread = @x_spread > 0 ? @x_spread : 1
|
199
159
|
end
|
200
160
|
|
201
|
-
def normalize
|
202
|
-
|
203
|
-
@norm_data = []
|
204
|
-
return unless @has_data
|
161
|
+
def normalize
|
162
|
+
return unless data_given?
|
205
163
|
|
206
|
-
|
207
|
-
norm_data_points = [data_row[DATA_LABEL_INDEX]]
|
208
|
-
norm_data_points << data_row[DATA_VALUES_INDEX].map do |r|
|
209
|
-
(r.to_f - @minimum_value.to_f) / @spread
|
210
|
-
end
|
211
|
-
norm_data_points << data_row[DATA_COLOR_INDEX]
|
212
|
-
norm_data_points << data_row[DATA_VALUES_X_INDEX].map do |r|
|
213
|
-
(r.to_f - @minimum_x_value.to_f) / @x_spread
|
214
|
-
end
|
215
|
-
@norm_data << norm_data_points
|
216
|
-
end
|
217
|
-
end
|
218
|
-
#~ @norm_y_baseline = (@baseline_y_value.to_f / @maximum_value.to_f) if @baseline_y_value
|
219
|
-
#~ @norm_x_baseline = (@baseline_x_value.to_f / @maximum_x_value.to_f) if @baseline_x_value
|
164
|
+
store.normalize(minimum_x: @minimum_x_value, spread_x: @x_spread, minimum_y: minimum_value, spread_y: @spread)
|
220
165
|
end
|
221
166
|
|
222
167
|
def draw_line_markers
|
@@ -224,10 +169,8 @@ protected
|
|
224
169
|
super
|
225
170
|
return if @hide_line_markers
|
226
171
|
|
227
|
-
@d = @d.stroke_antialias false
|
228
|
-
|
229
172
|
if @x_axis_increment.nil?
|
230
|
-
# TODO Do the same for larger numbers...100, 75, 50, 25
|
173
|
+
# TODO: Do the same for larger numbers...100, 75, 50, 25
|
231
174
|
if @marker_x_count.nil?
|
232
175
|
(3..7).each do |lines|
|
233
176
|
if @x_spread % lines == 0.0
|
@@ -242,47 +185,36 @@ protected
|
|
242
185
|
@x_increment = significant(@x_increment)
|
243
186
|
end
|
244
187
|
else
|
245
|
-
# TODO Make this work for negative values
|
246
|
-
@maximum_x_value = [
|
188
|
+
# TODO: Make this work for negative values
|
189
|
+
@maximum_x_value = [maximum_value.ceil, @x_axis_increment].max
|
247
190
|
@minimum_x_value = @minimum_x_value.floor
|
248
191
|
calculate_spread
|
249
|
-
normalize
|
192
|
+
normalize
|
250
193
|
|
251
194
|
@marker_count = (@x_spread / @x_axis_increment).to_i
|
252
195
|
@x_increment = @x_axis_increment
|
253
196
|
end
|
254
|
-
|
197
|
+
increment_x_scaled = @graph_width.to_f / (@x_spread / @x_increment)
|
255
198
|
|
256
199
|
# Draw vertical line markers and annotate with numbers
|
257
200
|
(0..@marker_x_count).each do |index|
|
258
|
-
# TODO Fix the vertical lines, and enable them by default. Not pretty when they don't match up with top y-axis line
|
201
|
+
# TODO: Fix the vertical lines, and enable them by default. Not pretty when they don't match up with top y-axis line
|
259
202
|
if @enable_vertical_line_markers
|
260
|
-
x = @graph_left + @graph_width - index.to_f *
|
261
|
-
|
262
|
-
@d = @d.stroke_width 1
|
263
|
-
@d = @d.line(x, @graph_top, x, @graph_bottom)
|
203
|
+
x = @graph_left + @graph_width - index.to_f * increment_x_scaled
|
204
|
+
Gruff::Renderer::Line.new(color: @marker_color).render(x, @graph_top, x, @graph_bottom)
|
264
205
|
end
|
265
206
|
|
266
207
|
unless @hide_line_numbers
|
267
208
|
marker_label = index * @x_increment + @minimum_x_value.to_f
|
268
209
|
y_offset = @graph_bottom + (@x_label_margin || LABEL_MARGIN)
|
269
|
-
x_offset = get_x_coord(index.to_f,
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
@d.gravity = NorthGravity
|
276
|
-
@d.rotation = -90.0 if @use_vertical_x_labels
|
277
|
-
@d = @d.annotate_scaled(@base_image,
|
278
|
-
1.0, 1.0,
|
279
|
-
x_offset, y_offset,
|
280
|
-
vertical_label(marker_label, @x_increment), @scale)
|
281
|
-
@d.rotation = 90.0 if @use_vertical_x_labels
|
210
|
+
x_offset = get_x_coord(index.to_f, increment_x_scaled, @graph_left)
|
211
|
+
|
212
|
+
label = vertical_label(marker_label, @x_increment)
|
213
|
+
rotation = -90.0 if @use_vertical_x_labels
|
214
|
+
text_renderer = Gruff::Renderer::Text.new(label, font: @font, size: @marker_font_size, color: @font_color, rotation: rotation)
|
215
|
+
text_renderer.render(1.0, 1.0, x_offset, y_offset)
|
282
216
|
end
|
283
217
|
end
|
284
|
-
|
285
|
-
@d = @d.stroke_antialias true
|
286
218
|
end
|
287
219
|
|
288
220
|
def label(value, increment)
|
@@ -301,10 +233,7 @@ protected
|
|
301
233
|
end
|
302
234
|
end
|
303
235
|
|
304
|
-
private
|
305
|
-
|
306
236
|
def get_x_coord(x_data_point, width, offset) #:nodoc:
|
307
237
|
x_data_point * width + offset
|
308
238
|
end
|
309
|
-
|
310
|
-
end # end Gruff::Scatter
|
239
|
+
end
|
data/lib/gruff/scene.rb
CHANGED
@@ -1,7 +1,8 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'observer'
|
4
|
+
require 'gruff/base'
|
3
5
|
|
4
|
-
##
|
5
6
|
# A scene is a non-linear graph that assembles layers together to tell a story.
|
6
7
|
# Layers are folders with appropriately named files (see below). You can group
|
7
8
|
# layers and control them together or just set their values individually.
|
@@ -11,8 +12,6 @@ require File.dirname(__FILE__) + '/base'
|
|
11
12
|
# * A city scene that changes with the time of day and the weather conditions.
|
12
13
|
# * A traffic map that shows red lines on streets that are crowded and green on free-flowing ones.
|
13
14
|
#
|
14
|
-
# Usage:
|
15
|
-
#
|
16
15
|
# g = Gruff::Scene.new("500x100", "path/to/city_scene_directory")
|
17
16
|
#
|
18
17
|
# # Define order of layers, back to front
|
@@ -30,20 +29,17 @@ require File.dirname(__FILE__) + '/base'
|
|
30
29
|
# # Write the final graph to disk
|
31
30
|
# g.write "hazy_daytime_city_scene.png"
|
32
31
|
#
|
33
|
-
#
|
34
32
|
# There are several rules that will magically select a layer when possible.
|
35
33
|
#
|
36
34
|
# * Numbered files will be selected according to the closest value that is less than the input value.
|
37
|
-
# * 'true.png' and 'false.png' will be used as booleans.
|
35
|
+
# * +'true.png'+ and +'false.png'+ will be used as booleans.
|
38
36
|
# * Other named files will be used if the input matches the filename (without the filetype extension).
|
39
|
-
# * If there is a file named 'default.png'
|
40
|
-
|
37
|
+
# * If there is a file named +'default.png'+, it will be used unless other input values are set for the corresponding layer.
|
41
38
|
class Gruff::Scene < Gruff::Base
|
42
|
-
|
43
|
-
# An array listing the foldernames that will be rendered, from back to front.
|
44
|
-
#
|
45
|
-
# g.layers = %w(sky clouds buildings street people)
|
39
|
+
# An array listing the folder names that will be rendered, from back to front.
|
46
40
|
#
|
41
|
+
# @example
|
42
|
+
# g.layers = %w(sky clouds buildings street people)
|
47
43
|
attr_reader :layers
|
48
44
|
|
49
45
|
def initialize(target_width, base_dir)
|
@@ -55,9 +51,9 @@ class Gruff::Scene < Gruff::Base
|
|
55
51
|
|
56
52
|
def draw
|
57
53
|
# Join all the custom paths and filter out the empty ones
|
58
|
-
image_paths = @layers.map
|
54
|
+
image_paths = @layers.map(&:path).reject(&:empty?)
|
59
55
|
images = Magick::ImageList.new(*image_paths)
|
60
|
-
|
56
|
+
Gruff::Renderer.background_image = images.flatten_images
|
61
57
|
end
|
62
58
|
|
63
59
|
def layers=(ordered_list)
|
@@ -77,10 +73,10 @@ class Gruff::Scene < Gruff::Base
|
|
77
73
|
def method_missing(method_name, *args)
|
78
74
|
case method_name.to_s
|
79
75
|
when /^(\w+)_group=$/
|
80
|
-
add_group
|
76
|
+
add_group Regexp.last_match(1), *args
|
81
77
|
return
|
82
78
|
when /^(\w+)=$/
|
83
|
-
set_input
|
79
|
+
set_input Regexp.last_match(1), args.first
|
84
80
|
return
|
85
81
|
end
|
86
82
|
super
|
@@ -93,19 +89,16 @@ private
|
|
93
89
|
end
|
94
90
|
|
95
91
|
def set_input(input_name, input_value)
|
96
|
-
if
|
92
|
+
if !@groups[input_name].nil?
|
97
93
|
@groups[input_name].send_updates(input_value)
|
98
|
-
|
99
|
-
|
100
|
-
chosen_layer.update input_value
|
101
|
-
end
|
94
|
+
elsif chosen_layer = @layers.find { |layer| layer.name == input_name }
|
95
|
+
chosen_layer.update input_value
|
102
96
|
end
|
103
97
|
end
|
104
|
-
|
105
98
|
end
|
106
99
|
|
100
|
+
# @private
|
107
101
|
class Gruff::Group
|
108
|
-
|
109
102
|
include Observable
|
110
103
|
attr_reader :name
|
111
104
|
|
@@ -120,11 +113,10 @@ class Gruff::Group
|
|
120
113
|
changed
|
121
114
|
notify_observers value
|
122
115
|
end
|
123
|
-
|
124
116
|
end
|
125
117
|
|
118
|
+
# @private
|
126
119
|
class Gruff::Layer
|
127
|
-
|
128
120
|
attr_reader :name
|
129
121
|
|
130
122
|
def initialize(base_dir, folder_name)
|
@@ -149,7 +141,7 @@ class Gruff::Layer
|
|
149
141
|
when /^-?(\d+\.)?\d+$/
|
150
142
|
select_numeric value
|
151
143
|
when /(\d\d):(\d\d):\d\d/
|
152
|
-
select_time "#{
|
144
|
+
select_time "#{Regexp.last_match(1)}#{Regexp.last_match(2)}"
|
153
145
|
else
|
154
146
|
select_default
|
155
147
|
end
|
@@ -185,7 +177,8 @@ private
|
|
185
177
|
return "#{times[index - 1]}.png"
|
186
178
|
end
|
187
179
|
end
|
188
|
-
|
180
|
+
|
181
|
+
"#{times.last}.png"
|
189
182
|
end
|
190
183
|
|
191
184
|
# Match "partly cloudy" to "partly_cloudy.png"
|
@@ -194,7 +187,7 @@ private
|
|
194
187
|
end
|
195
188
|
|
196
189
|
def select_default
|
197
|
-
@filenames.include?(
|
190
|
+
@filenames.include?('default.png') ? 'default.png' : ''
|
198
191
|
end
|
199
192
|
|
200
193
|
# Returns the string "#{filename}.png", if it exists.
|
@@ -203,5 +196,4 @@ private
|
|
203
196
|
def file_exists_or_blank(filename)
|
204
197
|
@filenames.include?("#{filename}.png") ? "#{filename}.png" : select_default
|
205
198
|
end
|
206
|
-
|
207
199
|
end
|