gruff 0.15.0 → 0.18.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +21 -5
  3. data/.gitignore +1 -0
  4. data/.rubocop.yml +0 -12
  5. data/CHANGELOG.md +52 -0
  6. data/README.md +14 -3
  7. data/gruff.gemspec +3 -4
  8. data/lib/gruff/accumulator_bar.rb +1 -1
  9. data/lib/gruff/area.rb +6 -4
  10. data/lib/gruff/bar.rb +53 -32
  11. data/lib/gruff/base.rb +297 -186
  12. data/lib/gruff/bezier.rb +4 -2
  13. data/lib/gruff/box.rb +180 -0
  14. data/lib/gruff/bubble.rb +99 -0
  15. data/lib/gruff/bullet.rb +5 -5
  16. data/lib/gruff/candlestick.rb +120 -0
  17. data/lib/gruff/dot.rb +11 -12
  18. data/lib/gruff/font.rb +3 -0
  19. data/lib/gruff/helper/bar_conversion.rb +6 -10
  20. data/lib/gruff/helper/bar_mixin.rb +25 -0
  21. data/lib/gruff/helper/bar_value_label.rb +24 -43
  22. data/lib/gruff/helper/stacked_mixin.rb +19 -1
  23. data/lib/gruff/histogram.rb +9 -6
  24. data/lib/gruff/line.rb +67 -43
  25. data/lib/gruff/mini/legend.rb +15 -11
  26. data/lib/gruff/net.rb +23 -18
  27. data/lib/gruff/patch/string.rb +1 -0
  28. data/lib/gruff/pie.rb +26 -12
  29. data/lib/gruff/renderer/circle.rb +3 -1
  30. data/lib/gruff/renderer/dash_line.rb +3 -2
  31. data/lib/gruff/renderer/dot.rb +28 -15
  32. data/lib/gruff/renderer/line.rb +1 -3
  33. data/lib/gruff/renderer/rectangle.rb +6 -2
  34. data/lib/gruff/renderer/renderer.rb +0 -4
  35. data/lib/gruff/renderer/text.rb +7 -1
  36. data/lib/gruff/scatter.rb +84 -81
  37. data/lib/gruff/side_bar.rb +64 -31
  38. data/lib/gruff/side_stacked_bar.rb +43 -55
  39. data/lib/gruff/spider.rb +52 -14
  40. data/lib/gruff/stacked_area.rb +18 -8
  41. data/lib/gruff/stacked_bar.rb +59 -29
  42. data/lib/gruff/store/xy_data.rb +8 -9
  43. data/lib/gruff/store/xy_pointsizes_data.rb +60 -0
  44. data/lib/gruff/version.rb +1 -1
  45. data/lib/gruff.rb +11 -12
  46. metadata +14 -11
  47. data/lib/gruff/scene.rb +0 -208
  48. data/lib/gruff/store/custom_data.rb +0 -36
data/lib/gruff/scene.rb DELETED
@@ -1,208 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'observer'
4
-
5
- # A scene is a non-linear graph that assembles layers together to tell a story.
6
- # Layers are folders with appropriately named files (see below). You can group
7
- # layers and control them together or just set their values individually.
8
- #
9
- # Examples:
10
- #
11
- # * A city scene that changes with the time of day and the weather conditions.
12
- # * A traffic map that shows red lines on streets that are crowded and green on free-flowing ones.
13
- #
14
- # g = Gruff::Scene.new("500x100", "path/to/city_scene_directory")
15
- #
16
- # # Define order of layers, back to front
17
- # g.layers = %w(background haze sky clouds)
18
- #
19
- # # Define groups that will be controlled by the same input
20
- # g.weather_group = %w(clouds)
21
- # g.time_group = %w(background sky)
22
- #
23
- # # Set values for the layers or groups
24
- # g.weather = "cloudy"
25
- # g.time = Time.now
26
- # g.haze = true
27
- #
28
- # # Write the final graph to disk
29
- # g.write "hazy_daytime_city_scene.png"
30
- #
31
- # There are several rules that will magically select a layer when possible.
32
- #
33
- # * Numbered files will be selected according to the closest value that is less than the input value.
34
- # * +'true.png'+ and +'false.png'+ will be used as booleans.
35
- # * Other named files will be used if the input matches the filename (without the filetype extension).
36
- # * If there is a file named +'default.png'+, it will be used unless other input values are set for the corresponding layer.
37
- class Gruff::Scene < Gruff::Base
38
- # An array listing the folder names that will be rendered, from back to front.
39
- #
40
- # @example
41
- # g.layers = %w(sky clouds buildings street people)
42
- attr_reader :layers
43
-
44
- def initialize(target_width, base_dir)
45
- @base_dir = base_dir
46
- @groups = {}
47
- @layers = []
48
- super target_width
49
- end
50
-
51
- def draw
52
- # Join all the custom paths and filter out the empty ones
53
- image_paths = @layers.map(&:path).reject(&:empty?)
54
- images = Magick::ImageList.new(*image_paths)
55
- renderer.background_image = images.flatten_images
56
- end
57
-
58
- def layers=(ordered_list)
59
- ordered_list.each do |layer_name|
60
- @layers << Gruff::Layer.new(@base_dir, layer_name)
61
- end
62
- end
63
-
64
- # Group layers to input values
65
- #
66
- # g.weather_group = ["sky", "sea", "clouds"]
67
- #
68
- # Set input values
69
- #
70
- # g.weather = "cloudy"
71
- #
72
- def method_missing(method_name, *args)
73
- case method_name.to_s
74
- when /^(\w+)_group=$/
75
- add_group Regexp.last_match(1), *args
76
- when /^(\w+)=$/
77
- set_input Regexp.last_match(1), args.first
78
- else
79
- super
80
- end
81
- end
82
-
83
- def respond_to_missing?(method_sym, include_private)
84
- case method_sym.to_s
85
- when /^(\w+)_group=$/, /^(\w+)=$/
86
- true
87
- else
88
- super
89
- end
90
- end
91
-
92
- private
93
-
94
- def add_group(input_name, layer_names)
95
- @groups[input_name] = Gruff::Group.new(input_name, @layers.select { |layer| layer_names.include?(layer.name) })
96
- end
97
-
98
- def set_input(input_name, input_value)
99
- if !@groups[input_name].nil?
100
- @groups[input_name].send_updates(input_value)
101
- elsif (chosen_layer = @layers.find { |layer| layer.name == input_name })
102
- chosen_layer.update input_value
103
- end
104
- end
105
- end
106
-
107
- # @private
108
- class Gruff::Group
109
- include Observable
110
- attr_reader :name
111
-
112
- def initialize(folder_name, layers)
113
- @name = folder_name
114
- layers.each do |layer|
115
- layer.observe self
116
- end
117
- end
118
-
119
- def send_updates(value)
120
- changed
121
- notify_observers value
122
- end
123
- end
124
-
125
- # @private
126
- class Gruff::Layer
127
- attr_reader :name
128
-
129
- def initialize(base_dir, folder_name)
130
- @base_dir = base_dir.to_s
131
- @name = folder_name.to_s
132
- @filenames = Dir.open(File.join(base_dir, folder_name)).entries.grep(/^[^.]+\.png$/).sort
133
- @selected_filename = select_default
134
- end
135
-
136
- # Register this layer so it receives updates from the group
137
- def observe(obj)
138
- obj.add_observer self
139
- end
140
-
141
- # Choose the appropriate filename for this layer, based on the input
142
- def update(value)
143
- @selected_filename = begin
144
- case value.to_s
145
- when /^(true|false)$/
146
- select_boolean value
147
- when /^(\w|\s)+$/
148
- select_string value
149
- when /^-?(\d+\.)?\d+$/
150
- select_numeric value
151
- when /(\d\d):(\d\d):\d\d/
152
- select_time "#{Regexp.last_match(1)}#{Regexp.last_match(2)}"
153
- else
154
- select_default
155
- end
156
- end
157
- # Finally, try to use 'default' if we're still blank
158
- @selected_filename ||= select_default
159
- end
160
-
161
- # Returns the full path to the selected image, or a blank string
162
- def path
163
- unless @selected_filename.nil? || @selected_filename.empty?
164
- return File.join(@base_dir, @name, @selected_filename)
165
- end
166
-
167
- ''
168
- end
169
-
170
- private
171
-
172
- # Match "true.png" or "false.png"
173
- def select_boolean(value)
174
- file_exists_or_blank value.to_s
175
- end
176
-
177
- # Match -5 to _5.png
178
- def select_numeric(value)
179
- file_exists_or_blank value.to_s.gsub('-', '_')
180
- end
181
-
182
- def select_time(value)
183
- times = @filenames.map { |filename| filename.gsub('.png', '') }
184
- times.each_with_index do |time, index|
185
- if (time > value) && (index > 0)
186
- return "#{times[index - 1]}.png"
187
- end
188
- end
189
-
190
- "#{times.last}.png"
191
- end
192
-
193
- # Match "partly cloudy" to "partly_cloudy.png"
194
- def select_string(value)
195
- file_exists_or_blank value.to_s.gsub(' ', '_')
196
- end
197
-
198
- def select_default
199
- @filenames.include?('default.png') ? 'default.png' : ''
200
- end
201
-
202
- # Returns the string "#{filename}.png", if it exists.
203
- #
204
- # Failing that, it returns default.png, or '' if that doesn't exist.
205
- def file_exists_or_blank(filename)
206
- @filenames.include?("#{filename}.png") ? "#{filename}.png" : select_default
207
- end
208
- end
@@ -1,36 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Gruff
4
- class Store
5
- # @private
6
- class CustomData < Struct.new(:label, :points, :color, :custom)
7
- def initialize(label, points, color, custom = nil)
8
- super(label.to_s, Array(points), color, custom)
9
- end
10
-
11
- def empty?
12
- points.empty?
13
- end
14
-
15
- def columns
16
- points.length
17
- end
18
-
19
- def min
20
- points.compact.min
21
- end
22
-
23
- def max
24
- points.compact.max
25
- end
26
-
27
- def normalize(minimum:, spread:)
28
- norm_points = points.map do |point|
29
- point.nil? ? nil : (point.to_f - minimum.to_f) / spread
30
- end
31
-
32
- self.class.new(label, norm_points, color, custom)
33
- end
34
- end
35
- end
36
- end