gruff 0.15.0-java → 0.16.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.
@@ -39,6 +39,7 @@ private
39
39
  @label_formatting = nil
40
40
  @show_labels_for_bar_values = false
41
41
  @hide_labels = false
42
+ @minimum_value = 0.0
42
43
  end
43
44
 
44
45
  def setup_data
@@ -46,47 +47,68 @@ private
46
47
  super
47
48
  end
48
49
 
50
+ def setup_graph_measurements
51
+ super
52
+ return if @hide_line_markers
53
+
54
+ if @show_labels_for_bar_values
55
+ proc_text_metrics = ->(text) { text_metrics(@marker_font, text) }
56
+
57
+ if maximum_value >= 0
58
+ _, metrics = Gruff::BarValueLabel.metrics(maximum_value, @label_formatting, proc_text_metrics)
59
+ @graph_top += metrics.height
60
+ end
61
+
62
+ @graph_height = @graph_bottom - @graph_top
63
+ end
64
+ end
65
+
49
66
  # Draws a bar graph, but multiple sets are stacked on top of each other.
50
67
  def draw_graph
51
68
  # Setup spacing.
52
69
  #
53
70
  # Columns sit stacked.
54
- bar_width = @graph_width / column_count.to_f
71
+ bar_width = @graph_width / column_count
55
72
  padding = (bar_width * (1 - @bar_spacing)) / 2
56
73
 
57
74
  height = Array.new(column_count, 0)
58
- stack_bar_value_label = Gruff::BarValueLabel::StackedBar.new
75
+ length = Array.new(column_count, @graph_bottom)
76
+ stack_bar_value_labels = Gruff::BarValueLabel::StackedBar.new
59
77
 
60
78
  store.norm_data.each_with_index do |data_row, row_index|
61
79
  data_row.points.each_with_index do |data_point, point_index|
62
- next if data_point == 0
80
+ temp1 = @graph_top + (@graph_height - (data_point * @graph_height) - height[point_index])
81
+ temp2 = @graph_top + @graph_height - height[point_index]
82
+ difference = temp2 - temp1
83
+ difference = 0 if difference < 0
63
84
 
64
85
  # Use incremented x and scaled y
65
86
  left_x = @graph_left + (bar_width * point_index) + padding
66
- left_y = @graph_top + (@graph_height -
67
- data_point * @graph_height -
68
- height[point_index]) + @segment_spacing
69
- right_x = left_x + bar_width * @bar_spacing
70
- right_y = @graph_top + @graph_height - height[point_index]
87
+ left_y = length[point_index] - difference
88
+ right_x = left_x + (bar_width * @bar_spacing)
89
+ right_y = length[point_index]
90
+ right_y -= @segment_spacing if row_index != store.columns - 1
71
91
 
72
92
  # update the total height of the current stacked bar
93
+ length[point_index] -= difference
73
94
  height[point_index] += (data_point * @graph_height)
74
95
 
75
96
  rect_renderer = Gruff::Renderer::Rectangle.new(renderer, color: data_row.color)
76
97
  rect_renderer.render(left_x, left_y, right_x, right_y)
77
98
 
78
99
  # Calculate center based on bar_width and current row
79
- label_center = left_x + bar_width * @bar_spacing / 2.0
100
+ label_center = left_x + (bar_width * @bar_spacing / 2.0)
80
101
  draw_label(label_center, point_index)
81
102
 
82
103
  bar_value_label = Gruff::BarValueLabel::Bar.new([left_x, left_y, right_x, right_y], store.data[row_index].points[point_index])
83
- stack_bar_value_label.add(bar_value_label, point_index)
104
+ stack_bar_value_labels.add(bar_value_label, point_index)
84
105
  end
85
106
  end
86
107
 
87
108
  if @show_labels_for_bar_values
88
- stack_bar_value_label.prepare_rendering(@label_formatting, bar_width) do |x, y, text|
89
- draw_value_label(x, y, text)
109
+ proc_text_metrics = ->(text) { text_metrics(@marker_font, text) }
110
+ stack_bar_value_labels.prepare_rendering(@label_formatting, proc_text_metrics) do |x, y, text, _text_width, text_height|
111
+ draw_value_label(bar_width * @bar_spacing, text_height, x, y, text)
90
112
  end
91
113
  end
92
114
  end
@@ -96,10 +118,10 @@ private
96
118
  end
97
119
 
98
120
  def hide_left_label_area?
99
- @hide_line_markers
121
+ @hide_line_markers && @y_axis_label.nil?
100
122
  end
101
123
 
102
124
  def hide_bottom_label_area?
103
- hide_labels?
125
+ hide_labels? && @x_axis_label.nil? && @legend_at_bottom == false
104
126
  end
105
127
  end
data/lib/gruff/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Gruff
4
- VERSION = '0.15.0'
4
+ VERSION = '0.16.0'
5
5
  end
data/lib/gruff.rb CHANGED
@@ -2,13 +2,13 @@
2
2
 
3
3
  require 'rmagick'
4
4
 
5
- require 'gruff/patch/rmagick'
6
- require 'gruff/patch/string'
7
- require 'gruff/renderer/renderer'
8
- require 'gruff/store/store'
9
- require 'gruff/font'
10
- require 'gruff/base'
11
- require 'gruff/version'
5
+ require_relative 'gruff/patch/rmagick'
6
+ require_relative 'gruff/patch/string'
7
+ require_relative 'gruff/renderer/renderer'
8
+ require_relative 'gruff/store/store'
9
+ require_relative 'gruff/font'
10
+ require_relative 'gruff/base'
11
+ require_relative 'gruff/version'
12
12
 
13
13
  ##
14
14
  # = Gruff. Graphs.
@@ -29,14 +29,15 @@ module Gruff
29
29
  autoload :Area, Gruff.libpath('area')
30
30
  autoload :Bar, Gruff.libpath('bar')
31
31
  autoload :Bezier, Gruff.libpath('bezier')
32
+ autoload :BoxPlot, Gruff.libpath('box_plot')
32
33
  autoload :Bullet, Gruff.libpath('bullet')
34
+ autoload :Candlestick, Gruff.libpath('candlestick')
33
35
  autoload :Dot, Gruff.libpath('dot')
34
36
  autoload :Histogram, Gruff.libpath('histogram')
35
37
  autoload :Line, Gruff.libpath('line')
36
38
  autoload :Net, Gruff.libpath('net')
37
39
  autoload :Pie, Gruff.libpath('pie')
38
40
  autoload :Scatter, Gruff.libpath('scatter')
39
- autoload :Scene, Gruff.libpath('scene')
40
41
  autoload :SideBar, Gruff.libpath('side_bar')
41
42
  autoload :SideStackedBar, Gruff.libpath('side_stacked_bar')
42
43
  autoload :Spider, Gruff.libpath('spider')
@@ -61,7 +62,6 @@ module Gruff
61
62
 
62
63
  class Store
63
64
  autoload :BasicData, Gruff.libpath('store/basic_data')
64
- autoload :CustomData, Gruff.libpath('store/custom_data')
65
65
  autoload :XYData, Gruff.libpath('store/xy_data')
66
66
  end
67
67
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gruff
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.15.0
4
+ version: 0.16.0
5
5
  platform: java
6
6
  authors:
7
7
  - Geoffrey Grosenbach
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2022-04-17 00:00:00.000000000 Z
12
+ date: 2022-05-02 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  requirement: !ruby/object:Gem::Requirement
@@ -123,7 +123,9 @@ files:
123
123
  - lib/gruff/bar.rb
124
124
  - lib/gruff/base.rb
125
125
  - lib/gruff/bezier.rb
126
+ - lib/gruff/box_plot.rb
126
127
  - lib/gruff/bullet.rb
128
+ - lib/gruff/candlestick.rb
127
129
  - lib/gruff/dot.rb
128
130
  - lib/gruff/font.rb
129
131
  - lib/gruff/helper/bar_conversion.rb
@@ -151,14 +153,12 @@ files:
151
153
  - lib/gruff/renderer/renderer.rb
152
154
  - lib/gruff/renderer/text.rb
153
155
  - lib/gruff/scatter.rb
154
- - lib/gruff/scene.rb
155
156
  - lib/gruff/side_bar.rb
156
157
  - lib/gruff/side_stacked_bar.rb
157
158
  - lib/gruff/spider.rb
158
159
  - lib/gruff/stacked_area.rb
159
160
  - lib/gruff/stacked_bar.rb
160
161
  - lib/gruff/store/basic_data.rb
161
- - lib/gruff/store/custom_data.rb
162
162
  - lib/gruff/store/store.rb
163
163
  - lib/gruff/store/xy_data.rb
164
164
  - lib/gruff/themes.rb
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