gruff 0.5.1-java → 0.10.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.
Files changed (120) hide show
  1. checksums.yaml +5 -5
  2. data/.editorconfig +14 -0
  3. data/.github/ISSUE_TEMPLATE.md +18 -0
  4. data/.gitignore +3 -0
  5. data/.rubocop.yml +109 -0
  6. data/.rubocop_todo.yml +130 -0
  7. data/.travis.yml +24 -12
  8. data/.yardopts +1 -0
  9. data/{History.txt → CHANGELOG.md} +61 -25
  10. data/Gemfile +3 -7
  11. data/README.md +57 -25
  12. data/Rakefile +6 -201
  13. data/assets/plastik/blue.png +0 -0
  14. data/assets/plastik/green.png +0 -0
  15. data/assets/plastik/red.png +0 -0
  16. data/docker/Dockerfile +14 -0
  17. data/docker/build.sh +4 -0
  18. data/docker/launch.sh +4 -0
  19. data/gruff.gemspec +19 -14
  20. data/init.rb +2 -0
  21. data/lib/gruff.rb +26 -2
  22. data/lib/gruff/accumulator_bar.rb +18 -8
  23. data/lib/gruff/area.rb +33 -19
  24. data/lib/gruff/bar.rb +76 -45
  25. data/lib/gruff/base.rb +337 -613
  26. data/lib/gruff/bezier.rb +34 -19
  27. data/lib/gruff/bullet.rb +51 -62
  28. data/lib/gruff/dot.rb +38 -62
  29. data/lib/gruff/helper/bar_conversion.rb +47 -0
  30. data/lib/gruff/helper/bar_value_label_mixin.rb +30 -0
  31. data/lib/gruff/helper/stacked_mixin.rb +23 -0
  32. data/lib/gruff/histogram.rb +59 -0
  33. data/lib/gruff/line.rb +130 -150
  34. data/lib/gruff/mini/bar.rb +17 -10
  35. data/lib/gruff/mini/legend.rb +24 -36
  36. data/lib/gruff/mini/pie.rb +18 -12
  37. data/lib/gruff/mini/side_bar.rb +26 -12
  38. data/lib/gruff/net.rb +60 -84
  39. data/lib/gruff/patch/rmagick.rb +33 -0
  40. data/lib/gruff/patch/string.rb +10 -0
  41. data/lib/gruff/photo_bar.rb +27 -30
  42. data/lib/gruff/pie.rb +190 -93
  43. data/lib/gruff/renderer/bezier.rb +21 -0
  44. data/lib/gruff/renderer/circle.rb +21 -0
  45. data/lib/gruff/renderer/dash_line.rb +22 -0
  46. data/lib/gruff/renderer/dot.rb +39 -0
  47. data/lib/gruff/renderer/ellipse.rb +21 -0
  48. data/lib/gruff/renderer/line.rb +34 -0
  49. data/lib/gruff/renderer/polygon.rb +23 -0
  50. data/lib/gruff/renderer/polyline.rb +21 -0
  51. data/lib/gruff/renderer/rectangle.rb +19 -0
  52. data/lib/gruff/renderer/renderer.rb +127 -0
  53. data/lib/gruff/renderer/text.rb +42 -0
  54. data/lib/gruff/scatter.rb +156 -180
  55. data/lib/gruff/scene.rb +31 -41
  56. data/lib/gruff/side_bar.rb +77 -63
  57. data/lib/gruff/side_stacked_bar.rb +77 -60
  58. data/lib/gruff/spider.rb +37 -50
  59. data/lib/gruff/stacked_area.rb +32 -30
  60. data/lib/gruff/stacked_bar.rb +87 -49
  61. data/lib/gruff/store/base_data.rb +34 -0
  62. data/lib/gruff/store/custom_data.rb +34 -0
  63. data/lib/gruff/store/store.rb +80 -0
  64. data/lib/gruff/store/xy_data.rb +55 -0
  65. data/lib/gruff/themes.rb +32 -33
  66. data/lib/gruff/version.rb +3 -1
  67. metadata +88 -94
  68. data/Manifest.txt +0 -81
  69. data/RELEASE.md +0 -30
  70. data/assets/bubble.png +0 -0
  71. data/assets/city_scene/background/0000.png +0 -0
  72. data/assets/city_scene/background/0600.png +0 -0
  73. data/assets/city_scene/background/2000.png +0 -0
  74. data/assets/city_scene/clouds/cloudy.png +0 -0
  75. data/assets/city_scene/clouds/partly_cloudy.png +0 -0
  76. data/assets/city_scene/clouds/stormy.png +0 -0
  77. data/assets/city_scene/grass/default.png +0 -0
  78. data/assets/city_scene/haze/true.png +0 -0
  79. data/assets/city_scene/number_sample/1.png +0 -0
  80. data/assets/city_scene/number_sample/2.png +0 -0
  81. data/assets/city_scene/number_sample/default.png +0 -0
  82. data/assets/city_scene/sky/0000.png +0 -0
  83. data/assets/city_scene/sky/0200.png +0 -0
  84. data/assets/city_scene/sky/0400.png +0 -0
  85. data/assets/city_scene/sky/0600.png +0 -0
  86. data/assets/city_scene/sky/0800.png +0 -0
  87. data/assets/city_scene/sky/1000.png +0 -0
  88. data/assets/city_scene/sky/1200.png +0 -0
  89. data/assets/city_scene/sky/1400.png +0 -0
  90. data/assets/city_scene/sky/1500.png +0 -0
  91. data/assets/city_scene/sky/1700.png +0 -0
  92. data/assets/city_scene/sky/2000.png +0 -0
  93. data/assets/pc306715.jpg +0 -0
  94. data/lib/gruff/bar_conversion.rb +0 -46
  95. data/lib/gruff/deprecated.rb +0 -39
  96. data/lib/gruff/stacked_mixin.rb +0 -23
  97. data/test/gruff_test_case.rb +0 -154
  98. data/test/image_compare.rb +0 -58
  99. data/test/test_accumulator_bar.rb +0 -51
  100. data/test/test_area.rb +0 -134
  101. data/test/test_bar.rb +0 -505
  102. data/test/test_base.rb +0 -8
  103. data/test/test_bezier.rb +0 -33
  104. data/test/test_bullet.rb +0 -26
  105. data/test/test_dot.rb +0 -263
  106. data/test/test_legend.rb +0 -68
  107. data/test/test_line.rb +0 -657
  108. data/test/test_mini_bar.rb +0 -33
  109. data/test/test_mini_pie.rb +0 -25
  110. data/test/test_mini_side_bar.rb +0 -36
  111. data/test/test_net.rb +0 -231
  112. data/test/test_photo.rb +0 -41
  113. data/test/test_pie.rb +0 -154
  114. data/test/test_scatter.rb +0 -233
  115. data/test/test_scene.rb +0 -100
  116. data/test/test_side_bar.rb +0 -56
  117. data/test/test_sidestacked_bar.rb +0 -105
  118. data/test/test_spider.rb +0 -226
  119. data/test/test_stacked_area.rb +0 -52
  120. data/test/test_stacked_bar.rb +0 -52
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Magick
4
+ class Draw
5
+ # Additional method to scale annotation text since Draw.scale doesn't.
6
+ def annotate_scaled(img, width, height, x, y, text, scale)
7
+ scaled_width = (width * scale) >= 1 ? (width * scale) : 1
8
+ scaled_height = (height * scale) >= 1 ? (height * scale) : 1
9
+
10
+ annotate(img,
11
+ scaled_width, scaled_height,
12
+ x * scale, y * scale,
13
+ text.gsub('%', '%%'))
14
+ end
15
+
16
+ remove_method :stroke_opacity
17
+ def stroke_opacity(_opacity)
18
+ raise '#stroke_opacity method has different behavior between RMagick and RMagick4J. Should not use this method.'
19
+ end
20
+
21
+ if defined? JRUBY_VERSION
22
+ # FIXME(uwe): We should NOT need to implement this method.
23
+ # Remove this method as soon as RMagick4J Issue #16 is fixed.
24
+ # https://github.com/Serabe/RMagick4J/issues/16
25
+ def fill=(fill)
26
+ fill = { white: '#FFFFFF' }[fill.to_sym] || fill
27
+ @draw.fill = Magick4J.ColorDatabase.query_default(fill)
28
+ self
29
+ end
30
+ # EMXIF
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ class String
4
+ THOUSAND_SEPARATOR = ','
5
+
6
+ #Taken from http://codesnippets.joyent.com/posts/show/330
7
+ def commify(delimiter = THOUSAND_SEPARATOR)
8
+ gsub(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{delimiter}")
9
+ end
10
+ end
@@ -1,11 +1,12 @@
1
- require File.dirname(__FILE__) + '/base'
1
+ # frozen_string_literal: true
2
+
3
+ require 'gruff/base'
2
4
 
3
5
  # EXPERIMENTAL!
4
6
  #
5
7
  # Doesn't work yet.
6
8
  #
7
9
  class Gruff::PhotoBar < Gruff::Base
8
-
9
10
  # TODO
10
11
  #
11
12
  # define base and cap in yml
@@ -25,12 +26,12 @@ class Gruff::PhotoBar < Gruff::Base
25
26
 
26
27
  def draw
27
28
  super
28
- return unless @has_data
29
+ return unless data_given?
29
30
 
30
- return # TODO Remove for further development
31
+ return # TODO: Remove for further development
32
+
33
+ init_photo_bar_graphics
31
34
 
32
- init_photo_bar_graphics()
33
-
34
35
  #Draw#define_clip_path()
35
36
  #Draw#clip_path(pathname)
36
37
  #Draw#composite....with bar graph image OverCompositeOp
@@ -38,44 +39,41 @@ class Gruff::PhotoBar < Gruff::Base
38
39
  # See also
39
40
  #
40
41
  # Draw.pattern # define an image to tile as the filling of a draw object
41
- #
42
+ #
42
43
 
43
44
  # Setup spacing.
44
45
  #
45
46
  # Columns sit side-by-side.
46
47
  spacing_factor = 0.9
47
- @bar_width = @norm_data[0][DATA_COLOR_INDEX].columns
48
+ bar_width = store.norm_data[0].color.columns
48
49
 
49
- @norm_data.each_with_index do |data_row, row_index|
50
-
51
- data_row[DATA_VALUES_INDEX].each_with_index do |data_point, point_index|
50
+ store.norm_data.each_with_index do |data_row, row_index|
51
+ data_row.points.each_with_index do |data_point, point_index|
52
52
  data_point = 0 if data_point.nil?
53
53
  # Use incremented x and scaled y
54
- left_x = @graph_left + (@bar_width * (row_index + point_index + ((@data.length - 1) * point_index)))
54
+ left_x = @graph_left + (bar_width * (row_index + point_index + ((store.length - 1) * point_index)))
55
55
  left_y = @graph_top + (@graph_height - data_point * @graph_height) + 1
56
- right_x = left_x + @bar_width * spacing_factor
56
+ right_x = left_x + bar_width * spacing_factor
57
57
  right_y = @graph_top + @graph_height - 1
58
-
59
- bar_image_width = data_row[DATA_COLOR_INDEX].columns
58
+
59
+ bar_image_width = data_row.color.columns
60
60
  bar_image_height = right_y.to_f - left_y.to_f
61
-
61
+
62
62
  # Crop to scale for data
63
- bar_image = data_row[DATA_COLOR_INDEX].crop(0, 0, bar_image_width, bar_image_height)
64
-
65
- @d.gravity = NorthWestGravity
66
- @d = @d.composite(left_x, left_y, bar_image_width, bar_image_height, bar_image)
67
-
63
+ bar_image = data_row.color.crop(0, 0, bar_image_width, bar_image_height)
64
+
65
+ @d.gravity = Magick::NorthWestGravity
66
+ @d.composite(left_x, left_y, bar_image_width, bar_image_height, bar_image)
67
+
68
68
  # Calculate center based on bar_width and current row
69
- label_center = @graph_left + (@data.length * @bar_width * point_index) + (@data.length * @bar_width / 2.0)
69
+ label_center = @graph_left + (store.length * bar_width * point_index) + (store.length * bar_width / 2.0)
70
70
  draw_label(label_center, point_index)
71
71
  end
72
-
73
72
  end
74
73
 
75
- @d.draw(@base_image)
74
+ Gruff::Renderer.finish
76
75
  end
77
76
 
78
-
79
77
  # Return the chosen theme or the default
80
78
  def theme
81
79
  @theme || 'plastik'
@@ -85,16 +83,15 @@ protected
85
83
 
86
84
  # Sets up colors with a list of images that will be used.
87
85
  # Images should be 340px tall
88
- def init_photo_bar_graphics
89
- color_list = Array.new
86
+ def init_photo_bar_graphics
87
+ color_list = []
90
88
  theme_dir = File.dirname(__FILE__) + '/../../assets/' + theme
91
89
 
92
90
  Dir.open(theme_dir).each do |file|
93
91
  next unless /\.png$/.match(file)
94
- color_list << Image.read("#{theme_dir}/#{file}").first
92
+
93
+ color_list << Magick::Image.read("#{theme_dir}/#{file}").first
95
94
  end
96
95
  @colors = color_list
97
96
  end
98
-
99
97
  end
100
-
@@ -1,124 +1,221 @@
1
- require File.dirname(__FILE__) + '/base'
1
+ # frozen_string_literal: true
2
2
 
3
- ##
4
- # Here's how to make a Pie graph:
3
+ require 'gruff/base'
4
+
5
+ #
6
+ # Here's how to make a Gruff::Pie.
5
7
  #
6
8
  # g = Gruff::Pie.new
7
9
  # g.title = "Visual Pie Graph Test"
8
10
  # g.data 'Fries', 20
9
11
  # g.data 'Hamburgers', 50
10
- # g.write("test/output/pie_keynote.png")
12
+ # g.write("pie_keynote.png")
13
+ #
14
+ # To control where the pie chart starts creating slices, use {#zero_degree=}.
11
15
  #
12
- # To control where the pie chart starts creating slices, use #zero_degree.
13
-
14
16
  class Gruff::Pie < Gruff::Base
15
-
16
- TEXT_OFFSET_PERCENTAGE = 0.15
17
+ DEFAULT_TEXT_OFFSET_PERCENTAGE = 0.15
17
18
 
18
19
  # Can be used to make the pie start cutting slices at the top (-90.0)
19
- # or at another angle. Default is 0.0, which starts at 3 o'clock.
20
- attr_accessor :zero_degree
20
+ # or at another angle. Default is +0.0+, which starts at 3 o'clock.
21
+ attr_writer :zero_degree
22
+
21
23
  # Do not show labels for slices that are less than this percent. Use 0 to always show all labels.
22
- # Defaults to 0
23
- attr_accessor :hide_labels_less_than
24
+ # Defaults to +0+.
25
+ attr_writer :hide_labels_less_than
26
+
27
+ # Affect the distance between the percentages and the pie chart.
28
+ # Defaults to +0.15+.
29
+ attr_writer :text_offset_percentage
30
+
31
+ ## Use values instead of percentages.
32
+ attr_accessor :show_values_as_labels
24
33
 
25
34
  def initialize_ivars
26
35
  super
27
- @zero_degree = 0.0
28
- @hide_labels_less_than = 0.0
36
+
37
+ @show_values_as_labels = false
38
+
39
+ @store = Gruff::Store.new(Gruff::Store::CustomData)
40
+ end
41
+ private :initialize_ivars
42
+
43
+ def zero_degree
44
+ @zero_degree ||= 0.0
45
+ end
46
+
47
+ def hide_labels_less_than
48
+ @hide_labels_less_than ||= 0.0
49
+ end
50
+
51
+ def text_offset_percentage
52
+ @text_offset_percentage ||= DEFAULT_TEXT_OFFSET_PERCENTAGE
53
+ end
54
+
55
+ def options
56
+ {
57
+ zero_degree: zero_degree,
58
+ hide_labels_less_than: hide_labels_less_than,
59
+ text_offset_percentage: text_offset_percentage,
60
+ show_values_as_labels: show_values_as_labels
61
+ }
29
62
  end
30
63
 
31
64
  def draw
32
- @hide_line_markers = true
33
-
65
+ hide_line_markers
66
+
34
67
  super
35
68
 
36
- return unless @has_data
37
-
38
- diameter = @graph_height
39
- radius = ([@graph_width, @graph_height].min / 2.0) * 0.8
40
- center_x = @graph_left + (@graph_width / 2.0)
41
- center_y = @graph_top + (@graph_height / 2.0) - 10 # Move graph up a bit
42
- total_sum = sums_for_pie()
43
- prev_degrees = @zero_degree
44
-
45
- # Use full data since we can easily calculate percentages
46
- data = (@sort ? @data.sort{ |a, b| a[DATA_VALUES_INDEX].first <=> b[DATA_VALUES_INDEX].first } : @data)
47
- data.each do |data_row|
48
- if data_row[DATA_VALUES_INDEX].first > 0
49
- @d = @d.stroke data_row[DATA_COLOR_INDEX]
50
- @d = @d.fill 'transparent'
51
- @d.stroke_width(radius) # stroke width should be equal to radius. we'll draw centered on (radius / 2)
52
-
53
- current_degrees = (data_row[DATA_VALUES_INDEX].first / total_sum) * 360.0
54
-
55
- # ellipse will draw the the stroke centered on the first two parameters offset by the second two.
56
- # therefore, in order to draw a circle of the proper diameter we must center the stroke at
57
- # half the radius for both x and y
58
- @d = @d.ellipse(center_x, center_y,
59
- radius / 2.0, radius / 2.0,
60
- prev_degrees, prev_degrees + current_degrees + 0.5) # <= +0.5 'fudge factor' gets rid of the ugly gaps
61
-
62
- half_angle = prev_degrees + ((prev_degrees + current_degrees) - prev_degrees) / 2
63
-
64
- label_val = ((data_row[DATA_VALUES_INDEX].first / total_sum) * 100.0).round
65
- unless label_val < @hide_labels_less_than
66
- # RMagick must use sprintf with the string and % has special significance.
67
- label_string = label_val.to_s + '%'
68
- @d = draw_label(center_x,center_y, half_angle,
69
- radius + (radius * TEXT_OFFSET_PERCENTAGE),
70
- label_string)
71
- end
72
-
73
- prev_degrees += current_degrees
69
+ return unless data_given?
70
+
71
+ slices.each do |slice|
72
+ if slice.value > 0
73
+ Gruff::Renderer::Ellipse.new(color: slice.color, width: radius)
74
+ .render(center_x, center_y, radius / 2.0, radius / 2.0, chart_degrees, chart_degrees + slice.degrees + 0.5)
75
+ process_label_for slice
76
+ update_chart_degrees_with slice.degrees
74
77
  end
75
78
  end
76
79
 
77
- # TODO debug a circle where the text is drawn...
78
-
79
- @d.draw(@base_image)
80
+ Gruff::Renderer.finish
80
81
  end
81
82
 
82
83
  private
83
84
 
84
- ##
85
- # Labels are drawn around a slightly wider ellipse to give room for
86
- # labels on the left and right.
87
- def draw_label(center_x, center_y, angle, radius, amount)
88
- # TODO Don't use so many hard-coded numbers
89
- r_offset = 20.0 # The distance out from the center of the pie to get point
90
- x_offset = center_x # + 15.0 # The label points need to be tweaked slightly
91
- y_offset = center_y # This one doesn't though
92
- radius_offset = (radius + r_offset)
93
- ellipse_factor = radius_offset * TEXT_OFFSET_PERCENTAGE
94
- x = x_offset + ((radius_offset + ellipse_factor) * Math.cos(angle.deg2rad))
95
- y = y_offset + (radius_offset * Math.sin(angle.deg2rad))
96
-
97
- # Draw label
98
- @d.fill = @font_color
99
- @d.font = @font if @font
100
- @d.pointsize = scale_fontsize(@marker_font_size)
101
- @d.stroke = 'transparent'
102
- @d.font_weight = BoldWeight
103
- @d.gravity = CenterGravity
104
- @d.annotate_scaled( @base_image,
105
- 0, 0,
106
- x, y,
107
- amount, @scale)
108
- end
109
-
110
- def sums_for_pie
111
- total_sum = 0.0
112
- @data.collect {|data_row| total_sum += data_row[DATA_VALUES_INDEX].first }
113
- total_sum
85
+ def slices
86
+ @slices ||= begin
87
+ slices = store.data.map { |data| slice_class.new(data, options) }
88
+
89
+ slices.sort_by(&:value) if @sort
90
+
91
+ total = slices.map(&:value).inject(:+).to_f
92
+ slices.each { |slice| slice.total = total }
93
+ end
114
94
  end
115
95
 
116
- end
96
+ # General Helper Methods
117
97
 
118
- class Float
119
- # Used for degree => radian conversions
120
- def deg2rad
121
- self * (Math::PI/180.0)
98
+ def hide_line_markers
99
+ @hide_line_markers = true
122
100
  end
123
- end
124
101
 
102
+ def update_chart_degrees_with(degrees)
103
+ @chart_degrees = chart_degrees + degrees
104
+ end
105
+
106
+ def slice_class
107
+ PieSlice
108
+ end
109
+
110
+ # Spatial Value-Related Methods
111
+
112
+ def chart_degrees
113
+ @chart_degrees ||= zero_degree
114
+ end
115
+
116
+ attr_reader :graph_height
117
+
118
+ attr_reader :graph_width
119
+
120
+ def diameter
121
+ graph_height
122
+ end
123
+
124
+ def half_width
125
+ graph_width / 2.0
126
+ end
127
+
128
+ def half_height
129
+ graph_height / 2.0
130
+ end
131
+
132
+ def radius
133
+ @radius ||= ([graph_width, graph_height].min / 2.0) * 0.8
134
+ end
135
+
136
+ def center_x
137
+ @center_x ||= @graph_left + half_width
138
+ end
139
+
140
+ def center_y
141
+ @center_y ||= @graph_top + half_height - 10
142
+ end
143
+
144
+ def distance_from_center
145
+ 20.0
146
+ end
147
+
148
+ def radius_offset
149
+ radius + (radius * text_offset_percentage) + distance_from_center
150
+ end
151
+
152
+ def ellipse_factor
153
+ radius_offset * text_offset_percentage
154
+ end
155
+
156
+ # Label-Related Methods
157
+
158
+ def process_label_for(slice)
159
+ if slice.percentage >= hide_labels_less_than
160
+ x, y = label_coordinates_for slice
161
+
162
+ draw_label(x, y, slice.label)
163
+ end
164
+ end
165
+
166
+ def label_coordinates_for(slice)
167
+ angle = chart_degrees + slice.degrees / 2
168
+
169
+ [x_label_coordinate(angle), y_label_coordinate(angle)]
170
+ end
171
+
172
+ def x_label_coordinate(angle)
173
+ center_x + ((radius_offset + ellipse_factor) * Math.cos(deg2rad(angle)))
174
+ end
175
+
176
+ def y_label_coordinate(angle)
177
+ center_y + (radius_offset * Math.sin(deg2rad(angle)))
178
+ end
179
+
180
+ # Drawing-Related Methods
181
+
182
+ def draw_label(x, y, value)
183
+ text_renderer = Gruff::Renderer::Text.new(value, font: @font, size: @marker_font_size, color: @font_color, weight: Magick::BoldWeight)
184
+ text_renderer.render(0, 0, x, y, Magick::CenterGravity)
185
+ end
186
+
187
+ # Helper Classes
188
+ #
189
+ # @private
190
+ class PieSlice < Struct.new(:data_array, :options)
191
+ attr_accessor :total
192
+
193
+ def name
194
+ data_array[0]
195
+ end
196
+
197
+ def value
198
+ data_array[1].first
199
+ end
200
+
201
+ def color
202
+ data_array[2]
203
+ end
204
+
205
+ def size
206
+ @size ||= value / total
207
+ end
208
+
209
+ def percentage
210
+ @percentage ||= (size * 100.0).round
211
+ end
212
+
213
+ def degrees
214
+ @degrees ||= size * 360.0
215
+ end
216
+
217
+ def label
218
+ options[:show_values_as_labels] ? value.to_s : "#{percentage}%"
219
+ end
220
+ end
221
+ end