gruff 0.6.0 → 0.7.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/.travis.yml +6 -4
- data/Gemfile +0 -6
- data/README.md +6 -2
- data/Rakefile +1 -1
- data/gruff.gemspec +6 -4
- data/lib/gruff/base.rb +12 -10
- data/lib/gruff/bullet.rb +1 -1
- data/lib/gruff/line.rb +43 -7
- data/lib/gruff/mini/legend.rb +6 -1
- data/lib/gruff/pie.rb +225 -77
- data/lib/gruff/scatter.rb +66 -16
- data/lib/gruff/spider.rb +2 -8
- data/lib/gruff/version.rb +1 -1
- data/test/gruff_test_case.rb +6 -8
- data/test/test_accumulator_bar.rb +1 -1
- data/test/test_bar.rb +2 -2
- data/test/test_base.rb +26 -1
- data/test/test_bullet.rb +2 -2
- data/test/test_dot.rb +2 -2
- data/test/test_line.rb +18 -1
- data/test/test_pie.rb +36 -3
- data/test/test_scatter.rb +41 -4
- data/test/test_sidestacked_bar.rb +1 -1
- metadata +14 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1f51ad044a490cf08a5bc23565b9a840e8f25ec7
|
4
|
+
data.tar.gz: e491d7b01929ec882b9e9760838fe021440eab14
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0e70c18694a2d3dd2339e9374e8bd26628ce738fa854d38bde0f7b7fe8b7f07298e2617b50a8e2816bcb23cd1c777617fa5bff326ff821dc7baf9e0b902a61be
|
7
|
+
data.tar.gz: d458e987e717773484d57f85f9e9754c54aea06ceccf7bb6839d19413e79cd0605faa858912d3bee9aafef90b7c6c238a33174ea879e86de17a0d8cb69abc3a1
|
data/.travis.yml
CHANGED
@@ -1,17 +1,19 @@
|
|
1
1
|
language: ruby
|
2
2
|
sudo: false
|
3
3
|
rvm:
|
4
|
-
- "1.8.7"
|
5
4
|
- "1.9.3"
|
6
5
|
- "2.0.0"
|
7
6
|
- "2.1"
|
8
7
|
- "2.2"
|
9
|
-
-
|
10
|
-
- jruby-
|
11
|
-
- jruby-
|
8
|
+
- "2.3.1"
|
9
|
+
- jruby-1.7.25
|
10
|
+
- jruby-9.0.5.0
|
11
|
+
- jruby-9.1.2.0
|
12
12
|
- jruby-head
|
13
13
|
- rbx
|
14
14
|
- rbx-2
|
15
|
+
before_install:
|
16
|
+
- gem query -i -n ^bundler$ > /dev/null || gem install --no-ri --no-rdoc bundler
|
15
17
|
notifications:
|
16
18
|
email:
|
17
19
|
- uwe@kubosch.no
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# Gruff Graphs
|
2
2
|
|
3
|
-
[](https://travis-ci.org/topfunky/gruff)
|
4
|
+
[](https://badge.fury.io/rb/gruff)
|
5
5
|
|
6
6
|
A library for making beautiful graphs.
|
7
7
|
|
@@ -117,6 +117,10 @@ In progress!
|
|
117
117
|
|
118
118
|
http://www.rubydoc.info/github/topfunky/gruff/frames
|
119
119
|
|
120
|
+
## Supported Ruby Versions
|
121
|
+
|
122
|
+
We aim to support all Ruby implementations supporting Ruby language level 1.9.3
|
123
|
+
or later. Currently we are running CI for MRI, JRuby, and Rubinius.
|
120
124
|
|
121
125
|
## Contributing
|
122
126
|
|
data/Rakefile
CHANGED
data/gruff.gemspec
CHANGED
@@ -15,16 +15,18 @@ Gem::Specification.new do |s|
|
|
15
15
|
s.homepage = %q{https://github.com/topfunky/gruff}
|
16
16
|
s.require_paths = %w(lib)
|
17
17
|
s.summary = %q{Beautiful graphs for one or multiple datasets.}
|
18
|
+
s.license = 'MIT'
|
18
19
|
s.test_files = s.files.grep(%r{^(test|spec|features)/})
|
19
20
|
s.executables = s.files.grep(%r{^bin/}).map { |f| File.basename(f) }
|
20
21
|
s.specification_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
22
|
+
s.required_ruby_version = ['>= 1.9.3', '< 3']
|
23
|
+
|
21
24
|
if defined? JRUBY_VERSION
|
22
25
|
s.platform = 'java'
|
23
|
-
s.add_dependency 'rmagick4j', '>= 0.3.9'
|
26
|
+
s.add_dependency 'rmagick4j', '~> 0.3', '>= 0.3.9'
|
24
27
|
else
|
25
|
-
s.add_dependency 'rmagick', '>= 2.13.4'
|
28
|
+
s.add_dependency 'rmagick', '~> 2.13', '>= 2.13.4'
|
26
29
|
end
|
27
30
|
s.add_development_dependency('rake')
|
28
|
-
s.add_development_dependency('
|
29
|
-
s.license = 'MIT'
|
31
|
+
s.add_development_dependency('minitest-reporters')
|
30
32
|
end
|
data/lib/gruff/base.rb
CHANGED
@@ -82,7 +82,8 @@ module Gruff
|
|
82
82
|
# A label for the left side of the graph
|
83
83
|
attr_accessor :y_axis_label
|
84
84
|
|
85
|
-
#
|
85
|
+
# Manually set increment of the vertical marking lines
|
86
|
+
attr_accessor :x_axis_increment
|
86
87
|
|
87
88
|
# Manually set increment of the horizontal marking lines
|
88
89
|
attr_accessor :y_axis_increment
|
@@ -118,7 +119,7 @@ module Gruff
|
|
118
119
|
attr_reader :font
|
119
120
|
|
120
121
|
# Same as font but for the title.
|
121
|
-
|
122
|
+
attr_accessor :title_font
|
122
123
|
|
123
124
|
# Specifies whether to draw the title bolded or not.
|
124
125
|
attr_accessor :bold_title
|
@@ -242,19 +243,23 @@ module Gruff
|
|
242
243
|
@raw_columns = 800.0
|
243
244
|
@raw_rows = 800.0 * (@rows/@columns)
|
244
245
|
@column_count = 0
|
246
|
+
@data = Array.new
|
245
247
|
@marker_count = nil
|
246
248
|
@maximum_value = @minimum_value = nil
|
247
249
|
@has_data = false
|
248
|
-
@
|
250
|
+
@increment = nil
|
249
251
|
@labels = Hash.new
|
252
|
+
@label_formatting = nil
|
250
253
|
@labels_seen = Hash.new
|
251
254
|
@sort = false
|
255
|
+
@sorted_drawing = false
|
252
256
|
@title = nil
|
257
|
+
@title_font = nil
|
253
258
|
|
254
259
|
@scale = @columns / @raw_columns
|
255
260
|
|
256
261
|
vera_font_path = File.expand_path('Vera.ttf', ENV['MAGICK_FONT_PATH'])
|
257
|
-
@font = File.
|
262
|
+
@font = File.exist?(vera_font_path) ? vera_font_path : nil
|
258
263
|
@bold_title = true
|
259
264
|
|
260
265
|
@marker_font_size = 21.0
|
@@ -280,6 +285,8 @@ module Gruff
|
|
280
285
|
@additional_line_colors = []
|
281
286
|
@theme_options = {}
|
282
287
|
|
288
|
+
@use_data_label = false
|
289
|
+
@x_axis_increment = nil
|
283
290
|
@x_axis_label = @y_axis_label = nil
|
284
291
|
@y_axis_increment = nil
|
285
292
|
@stacked = nil
|
@@ -297,11 +304,6 @@ module Gruff
|
|
297
304
|
@d.font = @font
|
298
305
|
end
|
299
306
|
|
300
|
-
# Sets the title font to the font at +font_path+
|
301
|
-
def title_font=(font_path)
|
302
|
-
@title_font = font_path
|
303
|
-
end
|
304
|
-
|
305
307
|
# Add a color to the list of available colors for lines.
|
306
308
|
#
|
307
309
|
# Example:
|
@@ -836,7 +838,7 @@ module Gruff
|
|
836
838
|
# TODO: See if index.odd? is the best stragegy
|
837
839
|
y_offset += @label_stagger_height if index.odd?
|
838
840
|
|
839
|
-
label_text =
|
841
|
+
label_text = labels[index].to_s
|
840
842
|
|
841
843
|
# TESTME
|
842
844
|
# FIXME: Consider chart types other than bar
|
data/lib/gruff/bullet.rb
CHANGED
@@ -58,7 +58,7 @@ class Gruff::Bullet < Gruff::Base
|
|
58
58
|
@margin = 30.0
|
59
59
|
@thickness = @raw_rows / 6.0
|
60
60
|
@right_margin = @margin
|
61
|
-
@graph_left = @title_width * 1.3
|
61
|
+
@graph_left = (@title && (@title_width * 1.3)) || @margin
|
62
62
|
@graph_width = @raw_columns - @graph_left - @right_margin
|
63
63
|
@graph_height = @thickness * 3.0
|
64
64
|
|
data/lib/gruff/line.rb
CHANGED
@@ -25,6 +25,9 @@ class Gruff::Line < Gruff::Base
|
|
25
25
|
attr_accessor :line_width
|
26
26
|
attr_accessor :dot_radius
|
27
27
|
|
28
|
+
# default is a circle, other options include square
|
29
|
+
attr_accessor :dot_style
|
30
|
+
|
28
31
|
# Hide parts of the graph to fit more datapoints, or for a different appearance.
|
29
32
|
attr_accessor :hide_dots, :hide_lines
|
30
33
|
|
@@ -67,7 +70,7 @@ class Gruff::Line < Gruff::Base
|
|
67
70
|
# g = Gruff::Line.new(400, false) # 400px wide, no lines (for backwards compatibility)
|
68
71
|
#
|
69
72
|
# g = Gruff::Line.new(false) # Defaults to 800px wide, no lines (for backwards compatibility)
|
70
|
-
#
|
73
|
+
#
|
71
74
|
# The preferred way is to call hide_dots or hide_lines instead.
|
72
75
|
def initialize(*args)
|
73
76
|
raise ArgumentError, 'Wrong number of arguments' if args.length > 2
|
@@ -84,6 +87,10 @@ class Gruff::Line < Gruff::Base
|
|
84
87
|
@hide_dots = @hide_lines = false
|
85
88
|
@maximum_x_value = nil
|
86
89
|
@minimum_x_value = nil
|
90
|
+
|
91
|
+
@dot_style = 'circle'
|
92
|
+
|
93
|
+
@show_vertical_markers = false
|
87
94
|
end
|
88
95
|
|
89
96
|
# This method allows one to plot a dataset with both X and Y data.
|
@@ -101,7 +108,7 @@ class Gruff::Line < Gruff::Base
|
|
101
108
|
# color: hex number indicating the line color as an RGB triplet
|
102
109
|
#
|
103
110
|
# Notes:
|
104
|
-
# -if (x_data_points.length != y_data_points.length) an error is
|
111
|
+
# -if (x_data_points.length != y_data_points.length) an error is
|
105
112
|
# returned.
|
106
113
|
# -if the color argument is nil, the next color from the default theme will
|
107
114
|
# be used.
|
@@ -115,7 +122,7 @@ class Gruff::Line < Gruff::Base
|
|
115
122
|
# g.dataxy("Bapples", [1,3,4,5,7,9], [1, 1, 2, 2, 3, 3])
|
116
123
|
# g.dataxy("Capples", [[1,1],[2,3],[3,4],[4,5],[5,7],[6,9]])
|
117
124
|
# #you can still use the old data method too if you want:
|
118
|
-
# g.data("Capples", [1, 1, 2, 2, 3, 3])
|
125
|
+
# g.data("Capples", [1, 1, 2, 2, 3, 3])
|
119
126
|
# #labels will be drawn at the x locations of the keys passed in.
|
120
127
|
# In this example the lables are drawn at x positions 2, 4, and 6:
|
121
128
|
# g.labels = {0 => '2003', 2 => '2004', 4 => '2005', 6 => '2006'}
|
@@ -178,7 +185,7 @@ class Gruff::Line < Gruff::Base
|
|
178
185
|
|
179
186
|
return unless @has_data
|
180
187
|
|
181
|
-
# Check to see if more than one datapoint was given. NaN can result otherwise.
|
188
|
+
# Check to see if more than one datapoint was given. NaN can result otherwise.
|
182
189
|
@x_increment = (@column_count > 1) ? (@graph_width / (@column_count - 1).to_f) : @graph_width
|
183
190
|
|
184
191
|
@reference_lines.each_value do |curr_reference_line|
|
@@ -246,9 +253,12 @@ class Gruff::Line < Gruff::Base
|
|
246
253
|
@d = @d.line(prev_x, prev_y, new_x, new_y)
|
247
254
|
elsif @one_point
|
248
255
|
# Show a circle if there's just one_point
|
249
|
-
@d = @
|
256
|
+
@d = DotRenderers.renderer(@dot_style).render(@d, new_x, new_y, circle_radius)
|
257
|
+
end
|
258
|
+
|
259
|
+
unless @hide_dots
|
260
|
+
@d = DotRenderers.renderer(@dot_style).render(@d, new_x, new_y, circle_radius)
|
250
261
|
end
|
251
|
-
@d = @d.circle(new_x, new_y, new_x - circle_radius, new_y) unless @hide_dots
|
252
262
|
|
253
263
|
prev_x, prev_y = new_x, new_y
|
254
264
|
end
|
@@ -281,7 +291,7 @@ class Gruff::Line < Gruff::Base
|
|
281
291
|
|
282
292
|
@reference_lines.each_value do |curr_reference_line|
|
283
293
|
|
284
|
-
# We only care about horizontal markers ... for normalization.
|
294
|
+
# We only care about horizontal markers ... for normalization.
|
285
295
|
# Vertical markers won't have a :value, they will have an :index
|
286
296
|
|
287
297
|
curr_reference_line[:norm_value] = ((curr_reference_line[:value].to_f - @minimum_value) / @spread.to_f) if (curr_reference_line.key?(:value))
|
@@ -326,4 +336,30 @@ class Gruff::Line < Gruff::Base
|
|
326
336
|
one_point
|
327
337
|
end
|
328
338
|
|
339
|
+
module DotRenderers
|
340
|
+
class Circle
|
341
|
+
def render(d, new_x, new_y, circle_radius)
|
342
|
+
d.circle(new_x, new_y, new_x - circle_radius, new_y)
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
346
|
+
class Square
|
347
|
+
def render(d, new_x, new_y, circle_radius)
|
348
|
+
offset = (circle_radius * 0.8).to_i
|
349
|
+
corner_1 = new_x - offset
|
350
|
+
corner_2 = new_y - offset
|
351
|
+
corner_3 = new_x + offset
|
352
|
+
corner_4 = new_y + offset
|
353
|
+
d.rectangle(corner_1, corner_2, corner_3, corner_4)
|
354
|
+
end
|
355
|
+
end
|
356
|
+
|
357
|
+
def self.renderer(style)
|
358
|
+
if style.to_s == 'square'
|
359
|
+
Square.new
|
360
|
+
else
|
361
|
+
Circle.new
|
362
|
+
end
|
363
|
+
end
|
364
|
+
end
|
329
365
|
end
|
data/lib/gruff/mini/legend.rb
CHANGED
@@ -4,6 +4,12 @@ module Gruff
|
|
4
4
|
|
5
5
|
attr_accessor :hide_mini_legend, :legend_position
|
6
6
|
|
7
|
+
def initialize(*)
|
8
|
+
@hide_mini_legend = false
|
9
|
+
@legend_position = nil
|
10
|
+
super
|
11
|
+
end
|
12
|
+
|
7
13
|
##
|
8
14
|
# The canvas needs to be bigger so we can put the legend beneath it.
|
9
15
|
|
@@ -45,7 +51,6 @@ module Gruff
|
|
45
51
|
return if @hide_mini_legend
|
46
52
|
|
47
53
|
legend_square_width = 40.0 # small square with color of this item
|
48
|
-
legend_square_margin = 10.0
|
49
54
|
@legend_left_margin = 100.0
|
50
55
|
legend_top_margin = 40.0
|
51
56
|
|
data/lib/gruff/pie.rb
CHANGED
@@ -17,107 +17,255 @@ class Gruff::Pie < Gruff::Base
|
|
17
17
|
|
18
18
|
# Can be used to make the pie start cutting slices at the top (-90.0)
|
19
19
|
# or at another angle. Default is 0.0, which starts at 3 o'clock.
|
20
|
-
|
20
|
+
attr_writer :zero_degree
|
21
|
+
|
21
22
|
# Do not show labels for slices that are less than this percent. Use 0 to always show all labels.
|
22
23
|
# Defaults to 0
|
23
|
-
|
24
|
+
attr_writer :hide_labels_less_than
|
25
|
+
|
24
26
|
# Affect the distance between the percentages and the pie chart
|
25
27
|
# Defaults to 0.15
|
26
|
-
|
28
|
+
attr_writer :text_offset_percentage
|
29
|
+
|
27
30
|
## Use values instead of percentages
|
28
31
|
attr_accessor :show_values_as_labels
|
29
32
|
|
30
33
|
def initialize_ivars
|
31
34
|
super
|
32
|
-
|
33
|
-
@hide_labels_less_than = 0.0
|
34
|
-
@text_offset_percentage = DEFAULT_TEXT_OFFSET_PERCENTAGE
|
35
|
+
|
35
36
|
@show_values_as_labels = false
|
36
37
|
end
|
37
38
|
|
39
|
+
def zero_degree
|
40
|
+
@zero_degree ||= 0.0
|
41
|
+
end
|
42
|
+
|
43
|
+
def hide_labels_less_than
|
44
|
+
@hide_labels_less_than ||= 0.0
|
45
|
+
end
|
46
|
+
|
47
|
+
def text_offset_percentage
|
48
|
+
@text_offset_percentage ||= DEFAULT_TEXT_OFFSET_PERCENTAGE
|
49
|
+
end
|
50
|
+
|
51
|
+
def options
|
52
|
+
{
|
53
|
+
:zero_degree => zero_degree,
|
54
|
+
:hide_labels_less_than => hide_labels_less_than,
|
55
|
+
:text_offset_percentage => text_offset_percentage,
|
56
|
+
:show_values_as_labels => show_values_as_labels
|
57
|
+
}
|
58
|
+
end
|
59
|
+
|
38
60
|
def draw
|
39
|
-
|
40
|
-
|
61
|
+
hide_line_markers
|
62
|
+
|
41
63
|
super
|
42
64
|
|
43
|
-
return unless
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
data = (@sort ? @data.sort{ |a, b| a[DATA_VALUES_INDEX].first <=> b[DATA_VALUES_INDEX].first } : @data)
|
54
|
-
data.each do |data_row|
|
55
|
-
if data_row[DATA_VALUES_INDEX].first > 0
|
56
|
-
@d = @d.stroke data_row[DATA_COLOR_INDEX]
|
57
|
-
@d = @d.fill 'transparent'
|
58
|
-
@d.stroke_width(radius) # stroke width should be equal to radius. we'll draw centered on (radius / 2)
|
59
|
-
|
60
|
-
current_degrees = (data_row[DATA_VALUES_INDEX].first / total_sum) * 360.0
|
61
|
-
|
62
|
-
# ellipse will draw the the stroke centered on the first two parameters offset by the second two.
|
63
|
-
# therefore, in order to draw a circle of the proper diameter we must center the stroke at
|
64
|
-
# half the radius for both x and y
|
65
|
-
@d = @d.ellipse(center_x, center_y,
|
66
|
-
radius / 2.0, radius / 2.0,
|
67
|
-
prev_degrees, prev_degrees + current_degrees + 0.5) # <= +0.5 'fudge factor' gets rid of the ugly gaps
|
68
|
-
|
69
|
-
half_angle = prev_degrees + ((prev_degrees + current_degrees) - prev_degrees) / 2
|
70
|
-
|
71
|
-
label_val = ((data_row[DATA_VALUES_INDEX].first / total_sum) * 100.0).round
|
72
|
-
unless label_val < @hide_labels_less_than
|
73
|
-
# RMagick must use sprintf with the string and % has special significance.
|
74
|
-
label_string = @show_values_as_labels ? data_row[DATA_VALUES_INDEX].first.to_s : label_val.to_s + '%'
|
75
|
-
@d = draw_label(center_x,center_y, half_angle,
|
76
|
-
radius + (radius * @text_offset_percentage),
|
77
|
-
label_string)
|
78
|
-
end
|
79
|
-
|
80
|
-
prev_degrees += current_degrees
|
65
|
+
return unless data_given?
|
66
|
+
|
67
|
+
slices.each do |slice|
|
68
|
+
if slice.value > 0
|
69
|
+
set_stroke_color slice
|
70
|
+
set_fill_color
|
71
|
+
set_stroke_width
|
72
|
+
set_drawing_points_for slice
|
73
|
+
process_label_for slice
|
74
|
+
update_chart_degrees_with slice.degrees
|
81
75
|
end
|
82
76
|
end
|
83
77
|
|
84
|
-
|
85
|
-
|
78
|
+
trigger_final_draw
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
def slices
|
84
|
+
@slices ||= begin
|
85
|
+
slices = @data.map { |data| slice_class.new(data, options) }
|
86
|
+
|
87
|
+
slices.sort_by(&:value) if @sort
|
88
|
+
|
89
|
+
total = slices.map(&:value).inject(:+).to_f
|
90
|
+
slices.each { |slice| slice.total = total }
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# General Helper Methods
|
95
|
+
|
96
|
+
def hide_line_markers
|
97
|
+
@hide_line_markers = true
|
98
|
+
end
|
99
|
+
|
100
|
+
def data_given?
|
101
|
+
@has_data
|
102
|
+
end
|
103
|
+
|
104
|
+
def update_chart_degrees_with(degrees)
|
105
|
+
@chart_degrees = chart_degrees + degrees
|
106
|
+
end
|
107
|
+
|
108
|
+
def slice_class
|
109
|
+
PieSlice
|
110
|
+
end
|
111
|
+
|
112
|
+
# Spatial Value-Related Methods
|
113
|
+
|
114
|
+
def chart_degrees
|
115
|
+
@chart_degrees ||= zero_degree
|
116
|
+
end
|
117
|
+
|
118
|
+
def graph_height
|
119
|
+
@graph_height
|
120
|
+
end
|
121
|
+
|
122
|
+
def graph_width
|
123
|
+
@graph_width
|
124
|
+
end
|
125
|
+
|
126
|
+
def diameter
|
127
|
+
graph_height
|
128
|
+
end
|
129
|
+
|
130
|
+
def half_width
|
131
|
+
graph_width / 2.0
|
132
|
+
end
|
133
|
+
|
134
|
+
def half_height
|
135
|
+
graph_height / 2.0
|
136
|
+
end
|
137
|
+
|
138
|
+
def radius
|
139
|
+
@radius ||= ([graph_width, graph_height].min / 2.0) * 0.8
|
140
|
+
end
|
141
|
+
|
142
|
+
def center_x
|
143
|
+
@center_x ||= @graph_left + half_width
|
144
|
+
end
|
145
|
+
|
146
|
+
def center_y
|
147
|
+
@center_y ||= @graph_top + half_height - 10
|
148
|
+
end
|
149
|
+
|
150
|
+
def distance_from_center
|
151
|
+
20.0
|
152
|
+
end
|
153
|
+
|
154
|
+
def radius_offset
|
155
|
+
radius + (radius * text_offset_percentage) + distance_from_center
|
156
|
+
end
|
157
|
+
|
158
|
+
def ellipse_factor
|
159
|
+
radius_offset * text_offset_percentage
|
160
|
+
end
|
161
|
+
|
162
|
+
# Label-Related Methods
|
163
|
+
|
164
|
+
def process_label_for(slice)
|
165
|
+
if slice.percentage >= hide_labels_less_than
|
166
|
+
x, y = label_coordinates_for slice
|
167
|
+
|
168
|
+
@d = draw_label(x, y, slice.label)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def label_coordinates_for(slice)
|
173
|
+
angle = chart_degrees + slice.degrees / 2
|
174
|
+
|
175
|
+
[x_label_coordinate(angle), y_label_coordinate(angle)]
|
176
|
+
end
|
177
|
+
|
178
|
+
def x_label_coordinate(angle)
|
179
|
+
center_x + ((radius_offset + ellipse_factor) * Math.cos(deg2rad(angle)))
|
180
|
+
end
|
181
|
+
|
182
|
+
def y_label_coordinate(angle)
|
183
|
+
center_y + (radius_offset * Math.sin(deg2rad(angle)))
|
184
|
+
end
|
185
|
+
|
186
|
+
# Drawing-Related Methods
|
187
|
+
|
188
|
+
def set_stroke_width
|
189
|
+
@d.stroke_width(radius)
|
190
|
+
end
|
191
|
+
|
192
|
+
def set_stroke_color(slice)
|
193
|
+
@d = @d.stroke slice.color
|
194
|
+
end
|
195
|
+
|
196
|
+
def set_fill_color
|
197
|
+
@d = @d.fill 'transparent'
|
198
|
+
end
|
199
|
+
|
200
|
+
def set_drawing_points_for(slice)
|
201
|
+
@d = @d.ellipse(
|
202
|
+
center_x,
|
203
|
+
center_y,
|
204
|
+
radius / 2.0,
|
205
|
+
radius / 2.0,
|
206
|
+
chart_degrees,
|
207
|
+
chart_degrees + slice.degrees + 0.5
|
208
|
+
)
|
209
|
+
end
|
210
|
+
|
211
|
+
def trigger_final_draw
|
86
212
|
@d.draw(@base_image)
|
87
213
|
end
|
88
214
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
def draw_label(center_x, center_y, angle, radius, amount)
|
95
|
-
# TODO Don't use so many hard-coded numbers
|
96
|
-
r_offset = 20.0 # The distance out from the center of the pie to get point
|
97
|
-
x_offset = center_x # + 15.0 # The label points need to be tweaked slightly
|
98
|
-
y_offset = center_y # This one doesn't though
|
99
|
-
radius_offset = (radius + r_offset)
|
100
|
-
ellipse_factor = radius_offset * @text_offset_percentage
|
101
|
-
x = x_offset + ((radius_offset + ellipse_factor) * Math.cos(deg2rad(angle)))
|
102
|
-
y = y_offset + (radius_offset * Math.sin(deg2rad(angle)))
|
103
|
-
|
104
|
-
# Draw label
|
105
|
-
@d.fill = @font_color
|
106
|
-
@d.font = @font if @font
|
107
|
-
@d.pointsize = scale_fontsize(@marker_font_size)
|
108
|
-
@d.stroke = 'transparent'
|
215
|
+
def configure_label_styling
|
216
|
+
@d.fill = @font_color
|
217
|
+
@d.font = @font if @font
|
218
|
+
@d.pointsize = scale_fontsize(@marker_font_size)
|
219
|
+
@d.stroke = 'transparent'
|
109
220
|
@d.font_weight = BoldWeight
|
110
|
-
@d.gravity
|
111
|
-
@d.annotate_scaled( @base_image,
|
112
|
-
0, 0,
|
113
|
-
x, y,
|
114
|
-
amount, @scale)
|
221
|
+
@d.gravity = CenterGravity
|
115
222
|
end
|
116
223
|
|
117
|
-
def
|
118
|
-
|
119
|
-
|
120
|
-
|
224
|
+
def draw_label(x, y, value)
|
225
|
+
configure_label_styling
|
226
|
+
|
227
|
+
@d.annotate_scaled(
|
228
|
+
@base_image,
|
229
|
+
0,
|
230
|
+
0,
|
231
|
+
x,
|
232
|
+
y,
|
233
|
+
value,
|
234
|
+
@scale
|
235
|
+
)
|
121
236
|
end
|
122
237
|
|
238
|
+
# Helper Classes
|
239
|
+
|
240
|
+
class PieSlice < Struct.new(:data_array, :options)
|
241
|
+
attr_accessor :total
|
242
|
+
|
243
|
+
def name
|
244
|
+
data_array[0]
|
245
|
+
end
|
246
|
+
|
247
|
+
def value
|
248
|
+
data_array[1].first
|
249
|
+
end
|
250
|
+
|
251
|
+
def color
|
252
|
+
data_array[2]
|
253
|
+
end
|
254
|
+
|
255
|
+
def size
|
256
|
+
@size ||= value / total
|
257
|
+
end
|
258
|
+
|
259
|
+
def percentage
|
260
|
+
@percentage ||= (size * 100.0).round
|
261
|
+
end
|
262
|
+
|
263
|
+
def degrees
|
264
|
+
@degrees ||= size * 360.0
|
265
|
+
end
|
266
|
+
|
267
|
+
def label
|
268
|
+
options[:show_values_as_labels] ? value.to_s : "#{percentage}%"
|
269
|
+
end
|
270
|
+
end
|
123
271
|
end
|
data/lib/gruff/scatter.rb
CHANGED
@@ -32,6 +32,25 @@ class Gruff::Scatter < Gruff::Base
|
|
32
32
|
|
33
33
|
#~ # Color of the horizontal baseline
|
34
34
|
#~ attr_accessor :baseline_x_color
|
35
|
+
|
36
|
+
# Attributes to allow customising the size of the points
|
37
|
+
attr_accessor :circle_radius
|
38
|
+
attr_accessor :stroke_width
|
39
|
+
|
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)
|
42
|
+
attr_accessor :disable_significant_rounding_x_axis
|
43
|
+
|
44
|
+
# Allow enabling vertical lines. When you have a lot of data, they can work great
|
45
|
+
attr_accessor :enable_vertical_line_markers
|
46
|
+
|
47
|
+
# Allow using vertical labels in the X axis (and setting the label margin)
|
48
|
+
attr_accessor :x_label_margin
|
49
|
+
attr_accessor :use_vertical_x_labels
|
50
|
+
|
51
|
+
# Allow passing lambdas to format labels
|
52
|
+
attr_accessor :y_axis_label_format
|
53
|
+
attr_accessor :x_axis_label_format
|
35
54
|
|
36
55
|
|
37
56
|
# Gruff::Scatter takes the same parameters as the Gruff::Line graph
|
@@ -40,13 +59,21 @@ class Gruff::Scatter < Gruff::Base
|
|
40
59
|
#
|
41
60
|
# g = Gruff::Scatter.new
|
42
61
|
#
|
43
|
-
def initialize(*
|
44
|
-
super
|
45
|
-
|
46
|
-
@maximum_x_value = @minimum_x_value = nil
|
62
|
+
def initialize(*)
|
63
|
+
super
|
64
|
+
|
47
65
|
@baseline_x_color = @baseline_y_color = 'red'
|
48
66
|
@baseline_x_value = @baseline_y_value = nil
|
67
|
+
@circle_radius = nil
|
68
|
+
@disable_significant_rounding_x_axis = false
|
69
|
+
@enable_vertical_line_markers = false
|
49
70
|
@marker_x_count = nil
|
71
|
+
@maximum_x_value = @minimum_x_value = nil
|
72
|
+
@stroke_width = nil
|
73
|
+
@use_vertical_x_labels = false
|
74
|
+
@x_axis_label_format = nil
|
75
|
+
@x_label_margin = nil
|
76
|
+
@y_axis_label_format = nil
|
50
77
|
end
|
51
78
|
|
52
79
|
def setup_drawing
|
@@ -94,9 +121,9 @@ class Gruff::Scatter < Gruff::Base
|
|
94
121
|
@d = @d.stroke data_row[DATA_COLOR_INDEX]
|
95
122
|
@d = @d.fill data_row[DATA_COLOR_INDEX]
|
96
123
|
@d = @d.stroke_opacity 1.0
|
97
|
-
@d = @d.stroke_width clip_value_if_greater_than(@columns / (@norm_data.first[1].size * 4), 5.0)
|
124
|
+
@d = @d.stroke_width @stroke_width || clip_value_if_greater_than(@columns / (@norm_data.first[1].size * 4), 5.0)
|
98
125
|
|
99
|
-
circle_radius = clip_value_if_greater_than(@columns / (@norm_data.first[1].size * 2.5), 5.0)
|
126
|
+
circle_radius = @circle_radius || clip_value_if_greater_than(@columns / (@norm_data.first[1].size * 2.5), 5.0)
|
100
127
|
@d = @d.circle(new_x, new_y, new_x - circle_radius, new_y)
|
101
128
|
end
|
102
129
|
end
|
@@ -173,7 +200,7 @@ protected
|
|
173
200
|
@x_spread = @x_spread > 0 ? @x_spread : 1
|
174
201
|
end
|
175
202
|
|
176
|
-
def normalize(force
|
203
|
+
def normalize(force=nil)
|
177
204
|
if @norm_data.nil? || force
|
178
205
|
@norm_data = []
|
179
206
|
return unless @has_data
|
@@ -212,7 +239,10 @@ protected
|
|
212
239
|
end
|
213
240
|
@marker_x_count ||= 4
|
214
241
|
end
|
215
|
-
@x_increment = (@x_spread > 0) ?
|
242
|
+
@x_increment = (@x_spread > 0) ? (@x_spread / @marker_x_count) : 1
|
243
|
+
unless @disable_significant_rounding_x_axis
|
244
|
+
@x_increment = significant(@x_increment)
|
245
|
+
end
|
216
246
|
else
|
217
247
|
# TODO Make this work for negative values
|
218
248
|
@maximum_x_value = [@maximum_value.ceil, @x_axis_increment].max
|
@@ -228,15 +258,17 @@ protected
|
|
228
258
|
# Draw vertical line markers and annotate with numbers
|
229
259
|
(0..@marker_x_count).each do |index|
|
230
260
|
|
231
|
-
# TODO Fix the vertical lines.
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
261
|
+
# TODO Fix the vertical lines, and enable them by default. Not pretty when they don't match up with top y-axis line
|
262
|
+
if @enable_vertical_line_markers
|
263
|
+
x = @graph_left + @graph_width - index.to_f * @increment_x_scaled
|
264
|
+
@d = @d.stroke(@marker_color)
|
265
|
+
@d = @d.stroke_width 1
|
266
|
+
@d = @d.line(x, @graph_top, x, @graph_bottom)
|
267
|
+
end
|
236
268
|
|
237
269
|
unless @hide_line_numbers
|
238
270
|
marker_label = index * @x_increment + @minimum_x_value.to_f
|
239
|
-
y_offset = @graph_bottom + LABEL_MARGIN
|
271
|
+
y_offset = @graph_bottom + (@x_label_margin || LABEL_MARGIN)
|
240
272
|
x_offset = get_x_coord(index.to_f, @increment_x_scaled, @graph_left)
|
241
273
|
|
242
274
|
@d.fill = @font_color
|
@@ -244,16 +276,34 @@ protected
|
|
244
276
|
@d.stroke('transparent')
|
245
277
|
@d.pointsize = scale_fontsize(@marker_font_size)
|
246
278
|
@d.gravity = NorthGravity
|
247
|
-
|
279
|
+
@d.rotation = -90.0 if @use_vertical_x_labels
|
248
280
|
@d = @d.annotate_scaled(@base_image,
|
249
281
|
1.0, 1.0,
|
250
282
|
x_offset, y_offset,
|
251
|
-
|
283
|
+
vertical_label(marker_label, @x_increment), @scale)
|
284
|
+
@d.rotation = 90.0 if @use_vertical_x_labels
|
252
285
|
end
|
253
286
|
end
|
254
287
|
|
255
288
|
@d = @d.stroke_antialias true
|
256
289
|
end
|
290
|
+
|
291
|
+
|
292
|
+
def label(value, increment)
|
293
|
+
if @y_axis_label_format
|
294
|
+
@y_axis_label_format.call(value)
|
295
|
+
else
|
296
|
+
super
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
def vertical_label(value, increment)
|
301
|
+
if @x_axis_label_format
|
302
|
+
@x_axis_label_format.call(value)
|
303
|
+
else
|
304
|
+
label(value, increment)
|
305
|
+
end
|
306
|
+
end
|
257
307
|
|
258
308
|
private
|
259
309
|
|
data/lib/gruff/spider.rb
CHANGED
@@ -24,7 +24,7 @@ class Gruff::Spider < Gruff::Base
|
|
24
24
|
def initialize(max_value, target_width = 800)
|
25
25
|
super(target_width)
|
26
26
|
@max_value = max_value
|
27
|
-
@hide_legend = true
|
27
|
+
@hide_legend = true
|
28
28
|
@rotation = 0
|
29
29
|
end
|
30
30
|
|
@@ -36,20 +36,14 @@ class Gruff::Spider < Gruff::Base
|
|
36
36
|
return unless @has_data
|
37
37
|
|
38
38
|
# Setup basic positioning
|
39
|
-
diameter = @graph_height
|
40
39
|
radius = @graph_height / 2.0
|
41
|
-
top_x = @graph_left + (@graph_width - diameter) / 2.0
|
42
40
|
center_x = @graph_left + (@graph_width / 2.0)
|
43
41
|
center_y = @graph_top + (@graph_height / 2.0) - 25 # Move graph up a bit
|
44
42
|
|
45
43
|
@unit_length = radius / @max_value
|
46
44
|
|
47
|
-
total_sum = sums_for_spider
|
48
|
-
prev_degrees = 0.0
|
49
45
|
additive_angle = (2 * Math::PI)/ @data.size
|
50
46
|
|
51
|
-
current_angle = rotation * Math::PI / 180.0
|
52
|
-
|
53
47
|
# Draw axes
|
54
48
|
draw_axes(center_x, center_y, radius, additive_angle) unless hide_axes
|
55
49
|
|
@@ -125,7 +119,7 @@ private
|
|
125
119
|
end
|
126
120
|
|
127
121
|
def sums_for_spider
|
128
|
-
@data.inject(0.0) {|sum, data_row| sum
|
122
|
+
@data.inject(0.0) {|sum, data_row| sum + data_row[DATA_VALUES_INDEX].first}
|
129
123
|
end
|
130
124
|
|
131
125
|
end
|
data/lib/gruff/version.rb
CHANGED
data/test/gruff_test_case.rb
CHANGED
@@ -2,7 +2,6 @@ $:.unshift(File.dirname(__FILE__) + '/../lib/')
|
|
2
2
|
|
3
3
|
RMAGICK_BYPASS_VERSION_TEST = true
|
4
4
|
|
5
|
-
require 'test/unit'
|
6
5
|
require 'gruff'
|
7
6
|
require 'fileutils'
|
8
7
|
|
@@ -10,10 +9,9 @@ TEST_OUTPUT_DIR = File.dirname(__FILE__) + "/output#{'_java' if RUBY_PLATFORM ==
|
|
10
9
|
FileUtils.mkdir_p(TEST_OUTPUT_DIR)
|
11
10
|
FileUtils.rm_f Dir[TEST_OUTPUT_DIR + '/*']
|
12
11
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
end
|
12
|
+
require 'minitest/autorun'
|
13
|
+
require 'minitest/reporters'
|
14
|
+
Minitest::Reporters.use!
|
17
15
|
|
18
16
|
class Gruff::Base
|
19
17
|
alias :write_org :write
|
@@ -23,7 +21,7 @@ class Gruff::Base
|
|
23
21
|
extension = filename.slice(/\.[^\.]*$/)
|
24
22
|
testfilename = File.join(TEST_OUTPUT_DIR, basefilename) + extension
|
25
23
|
counter = 0
|
26
|
-
while File.
|
24
|
+
while File.exist?(testfilename)
|
27
25
|
counter += 1
|
28
26
|
testfilename = File.join(TEST_OUTPUT_DIR, basefilename) + "-#{counter}#{extension}"
|
29
27
|
end
|
@@ -31,7 +29,7 @@ class Gruff::Base
|
|
31
29
|
end
|
32
30
|
end
|
33
31
|
|
34
|
-
class GruffTestCase < Test
|
32
|
+
class GruffTestCase < Minitest::Test
|
35
33
|
def setup
|
36
34
|
srand 42
|
37
35
|
@datasets = [
|
@@ -106,7 +104,7 @@ class GruffTestCase < Test::Unit::TestCase
|
|
106
104
|
basefilename = filename.split('.')[0..-2].join('.')
|
107
105
|
extension = filename.slice(/\..*$/)
|
108
106
|
counter = 0
|
109
|
-
while File.
|
107
|
+
while File.exist? testfilename
|
110
108
|
counter += 1
|
111
109
|
testfilename = [TEST_OUTPUT_DIR, basefilename].join('/') + "-#{counter}#{extension}"
|
112
110
|
end
|
@@ -40,7 +40,7 @@ class TestGruffAccumulatorBar < GruffTestCase
|
|
40
40
|
end
|
41
41
|
|
42
42
|
def test_too_many_args
|
43
|
-
|
43
|
+
assert_raises(Gruff::IncorrectNumberOfDatasetsException) {
|
44
44
|
g = Gruff::AccumulatorBar.new
|
45
45
|
g.data 'First', [1,1,1]
|
46
46
|
g.data 'Too Many', [1,1,1]
|
data/test/test_bar.rb
CHANGED
@@ -259,11 +259,11 @@ class TestGruffBar < GruffTestCase
|
|
259
259
|
def test_spacing_factor_does_not_accept_values_lt_0_and_gt_1
|
260
260
|
g = Gruff::Bar.new
|
261
261
|
|
262
|
-
|
262
|
+
assert_raises ArgumentError do
|
263
263
|
g.spacing_factor = 1.01
|
264
264
|
end
|
265
265
|
|
266
|
-
|
266
|
+
assert_raises ArgumentError do
|
267
267
|
g.spacing_factor = -0.01
|
268
268
|
end
|
269
269
|
end
|
data/test/test_base.rb
CHANGED
@@ -4,5 +4,30 @@ require File.dirname(__FILE__) + "/gruff_test_case"
|
|
4
4
|
|
5
5
|
class TestGruffBase < GruffTestCase
|
6
6
|
|
7
|
+
def setup
|
8
|
+
@sample_numeric_labels = {
|
9
|
+
0 => 6,
|
10
|
+
1 => 15,
|
11
|
+
2 => 24,
|
12
|
+
3 => 30,
|
13
|
+
4 => 4,
|
14
|
+
5 => 12,
|
15
|
+
6 => 21,
|
16
|
+
7 => 28,
|
17
|
+
}
|
18
|
+
end
|
7
19
|
|
8
|
-
|
20
|
+
def test_labels_can_be_any_object
|
21
|
+
g = Gruff::Bar.new
|
22
|
+
g.title = 'Bar Graph With Manual Colors'
|
23
|
+
g.legend_margin = 50
|
24
|
+
g.labels = @sample_numeric_labels
|
25
|
+
g.data(:Art, [0, 5, 8, 15], '#990000')
|
26
|
+
g.data(:Philosophy, [10, 3, 2, 8], '#009900')
|
27
|
+
g.data(:Science, [2, 15, 8, 11], '#990099')
|
28
|
+
|
29
|
+
g.minimum_value = 0
|
30
|
+
|
31
|
+
g.write('test/output/bar_object_labels.png')
|
32
|
+
end
|
33
|
+
end
|
data/test/test_bullet.rb
CHANGED
@@ -13,13 +13,13 @@ class TestGruffBullet < GruffTestCase
|
|
13
13
|
def test_bullet_graph
|
14
14
|
g = Gruff::Bullet.new
|
15
15
|
g.title = "Monthly Revenue"
|
16
|
-
g.data
|
16
|
+
g.data(*@data_args)
|
17
17
|
g.write("test/output/bullet_greyscale.png")
|
18
18
|
end
|
19
19
|
|
20
20
|
def test_no_options
|
21
21
|
g = Gruff::Bullet.new
|
22
|
-
g.data
|
22
|
+
g.data(*@data_args)
|
23
23
|
g.write("test/output/bullet_no_options.png")
|
24
24
|
end
|
25
25
|
|
data/test/test_dot.rb
CHANGED
@@ -234,11 +234,11 @@ class TestGruffDot < GruffTestCase
|
|
234
234
|
g.hide_legend = true
|
235
235
|
g.title = 'Full speed ahead'
|
236
236
|
g.labels = (0..10).inject({}) { |memo, i| memo.merge({ i => (i*10).to_s}) }
|
237
|
-
g.data(:apples,
|
237
|
+
g.data(:apples, [1.7, 0.8, 0.1, 1.9, 1.4, 0.6, 1.1, 0.7, 1.4, 0.2])
|
238
238
|
g.y_axis_increment = 1.0
|
239
239
|
g.x_axis_label = 'Score (%)'
|
240
240
|
g.y_axis_label = 'Students'
|
241
|
-
write_test_file g, '
|
241
|
+
write_test_file g, 'enhancements_dot.png'
|
242
242
|
end
|
243
243
|
|
244
244
|
|
data/test/test_line.rb
CHANGED
@@ -192,6 +192,23 @@ class TestGruffLine < GruffTestCase
|
|
192
192
|
end
|
193
193
|
|
194
194
|
|
195
|
+
def test_dot_style_square
|
196
|
+
g = Gruff::Line.new
|
197
|
+
g.title = 'Square points'
|
198
|
+
g.labels = {
|
199
|
+
0 => 'June',
|
200
|
+
10 => 'July',
|
201
|
+
30 => 'August',
|
202
|
+
50 => 'September',
|
203
|
+
}
|
204
|
+
g.dot_style = :square
|
205
|
+
g.data('many points', (0..50).collect { |i| rand(100) })
|
206
|
+
g.x_axis_label = 'Months'
|
207
|
+
|
208
|
+
# Default theme
|
209
|
+
g.write('test/output/line_dot_style_square.png')
|
210
|
+
end
|
211
|
+
|
195
212
|
def test_similar_high_end_values
|
196
213
|
@dataset = %w(29.43 29.459 29.498 29.53 29.548 29.589 29.619 29.66 29.689 29.849 29.878 29.74 29.769 29.79 29.808 29.828).collect { |i| i.to_f }
|
197
214
|
|
@@ -577,7 +594,7 @@ class TestGruffLine < GruffTestCase
|
|
577
594
|
g.title = 'Line Chart WEBP'
|
578
595
|
g.write('line_webp.webp')
|
579
596
|
rescue Magick::ImageMagickError
|
580
|
-
assert_match
|
597
|
+
assert_match(/no encode delegate for this image format .*\.webp/, $!.message)
|
581
598
|
end
|
582
599
|
|
583
600
|
private
|
data/test/test_pie.rb
CHANGED
@@ -127,7 +127,8 @@ class TestGruffPie < GruffTestCase
|
|
127
127
|
|
128
128
|
|
129
129
|
def test_tiny_simple_pie
|
130
|
-
|
130
|
+
r = Random.new(297427)
|
131
|
+
@datasets = (1..5).map {|n| ['Auto', [r.rand(100)]]}
|
131
132
|
|
132
133
|
g = setup_basic_graph 200
|
133
134
|
g.hide_legend = true
|
@@ -140,13 +141,25 @@ class TestGruffPie < GruffTestCase
|
|
140
141
|
write_test_file g, "pie_simple.png"
|
141
142
|
end
|
142
143
|
|
143
|
-
|
144
|
+
def test_pie_with_adjusted_text_offset_percentage
|
144
145
|
g = setup_basic_graph
|
145
146
|
g.title = "Adjusted Text Offset Percentage"
|
146
147
|
g.text_offset_percentage = 0.03
|
147
148
|
g.write "test/output/pie_adjusted_text_offset_percentage.png"
|
148
149
|
end
|
149
150
|
|
151
|
+
def test_subclassed_pie_with_custom_labels
|
152
|
+
CustomLabeledPie.new(800).tap do |graph|
|
153
|
+
graph.title = "Subclassed Pie with Custom Lables"
|
154
|
+
|
155
|
+
@datasets.map { |set| set << set.join(': ') }.each do |data|
|
156
|
+
graph.data(data[0], data[1], :label => data[2])
|
157
|
+
end
|
158
|
+
|
159
|
+
graph.write 'test/output/pie_subclass_custom_labels.png'
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
150
163
|
protected
|
151
164
|
|
152
165
|
def setup_basic_graph(size=800)
|
@@ -157,5 +170,25 @@ protected
|
|
157
170
|
end
|
158
171
|
return g
|
159
172
|
end
|
160
|
-
|
173
|
+
|
174
|
+
# Example Gruff::Pie Subclass demonstrating custom labels
|
175
|
+
class CustomLabeledPie < Gruff::Pie
|
176
|
+
def data(name, data_points = [], options = {})
|
177
|
+
super(name, data_points, options[:color])
|
178
|
+
|
179
|
+
@data.each { |data_array| data_array << options[:label] }
|
180
|
+
end
|
181
|
+
|
182
|
+
private
|
183
|
+
|
184
|
+
def slice_class
|
185
|
+
CustomLabeledSlice
|
186
|
+
end
|
187
|
+
|
188
|
+
class CustomLabeledSlice < ::Gruff::Pie::PieSlice
|
189
|
+
def label
|
190
|
+
data_array[3] || super
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
161
194
|
end
|
data/test/test_scatter.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
require File.dirname(__FILE__) + '/gruff_test_case'
|
4
4
|
|
5
|
-
class TestGruffScatter < Test
|
5
|
+
class TestGruffScatter < Minitest::Test
|
6
6
|
|
7
7
|
def setup
|
8
8
|
@datasets = [
|
@@ -31,6 +31,43 @@ class TestGruffScatter < Test::Unit::TestCase
|
|
31
31
|
g.write('test/output/scatter_many.png')
|
32
32
|
end
|
33
33
|
|
34
|
+
def test_custom_label_format
|
35
|
+
g = Gruff::Scatter.new('1000x500')
|
36
|
+
g.top_margin = 0
|
37
|
+
g.hide_legend = true
|
38
|
+
g.hide_title = true
|
39
|
+
g.marker_font_size = 10
|
40
|
+
g.theme = {
|
41
|
+
:colors => ['#12a702', '#aedaa9'],
|
42
|
+
:marker_color => '#dddddd',
|
43
|
+
:font_color => 'black',
|
44
|
+
:background_colors => 'white'
|
45
|
+
}
|
46
|
+
|
47
|
+
# Points style
|
48
|
+
g.circle_radius = 1
|
49
|
+
g.stroke_width = 0.01
|
50
|
+
|
51
|
+
# Axis labels
|
52
|
+
g.x_label_margin = 40
|
53
|
+
g.bottom_margin = 60
|
54
|
+
g.disable_significant_rounding_x_axis = true
|
55
|
+
g.use_vertical_x_labels = true
|
56
|
+
g.enable_vertical_line_markers = true
|
57
|
+
g.marker_x_count = 50 # One label every 2 days
|
58
|
+
g.x_axis_label_format = lambda do |value|
|
59
|
+
DateTime.strptime(value.to_i.to_s,'%s').strftime('%d.%m.%Y')
|
60
|
+
end
|
61
|
+
g.y_axis_increment = 1
|
62
|
+
|
63
|
+
# Fake data (100 days, random times of day between 5 and 16)
|
64
|
+
r = Random.new(269155)
|
65
|
+
y_values = (0..100).map { 5 + r.rand(12) }
|
66
|
+
x_values = (0..100).map { |i| Date.today.strftime('%s').to_i + i*3600*24 }
|
67
|
+
g.data('many points', x_values, y_values)
|
68
|
+
g.write('test/output/scatter_custom_label_format.png')
|
69
|
+
end
|
70
|
+
|
34
71
|
# Done
|
35
72
|
def test_no_data
|
36
73
|
g = Gruff::Scatter.new(400)
|
@@ -65,7 +102,7 @@ class TestGruffScatter < Test::Unit::TestCase
|
|
65
102
|
]
|
66
103
|
|
67
104
|
@datasets.each do |data|
|
68
|
-
|
105
|
+
assert_raises ArgumentError do
|
69
106
|
g.data(*data)
|
70
107
|
end
|
71
108
|
end
|
@@ -82,7 +119,7 @@ class TestGruffScatter < Test::Unit::TestCase
|
|
82
119
|
]
|
83
120
|
|
84
121
|
@datasets.each do |data|
|
85
|
-
|
122
|
+
assert_raises ArgumentError do
|
86
123
|
g.data(*data)
|
87
124
|
end
|
88
125
|
end
|
@@ -98,7 +135,7 @@ class TestGruffScatter < Test::Unit::TestCase
|
|
98
135
|
]
|
99
136
|
|
100
137
|
@datasets.each do |data|
|
101
|
-
|
138
|
+
assert_raises ArgumentError do
|
102
139
|
g.data(*data)
|
103
140
|
end
|
104
141
|
end
|
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.
|
4
|
+
version: 0.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Geoffrey Grosenbach
|
@@ -9,12 +9,15 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2016-06-01 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rmagick
|
16
16
|
requirement: !ruby/object:Gem::Requirement
|
17
17
|
requirements:
|
18
|
+
- - "~>"
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '2.13'
|
18
21
|
- - ">="
|
19
22
|
- !ruby/object:Gem::Version
|
20
23
|
version: 2.13.4
|
@@ -22,6 +25,9 @@ dependencies:
|
|
22
25
|
prerelease: false
|
23
26
|
version_requirements: !ruby/object:Gem::Requirement
|
24
27
|
requirements:
|
28
|
+
- - "~>"
|
29
|
+
- !ruby/object:Gem::Version
|
30
|
+
version: '2.13'
|
25
31
|
- - ">="
|
26
32
|
- !ruby/object:Gem::Version
|
27
33
|
version: 2.13.4
|
@@ -40,7 +46,7 @@ dependencies:
|
|
40
46
|
- !ruby/object:Gem::Version
|
41
47
|
version: '0'
|
42
48
|
- !ruby/object:Gem::Dependency
|
43
|
-
name:
|
49
|
+
name: minitest-reporters
|
44
50
|
requirement: !ruby/object:Gem::Requirement
|
45
51
|
requirements:
|
46
52
|
- - ">="
|
@@ -166,7 +172,10 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
166
172
|
requirements:
|
167
173
|
- - ">="
|
168
174
|
- !ruby/object:Gem::Version
|
169
|
-
version:
|
175
|
+
version: 1.9.3
|
176
|
+
- - "<"
|
177
|
+
- !ruby/object:Gem::Version
|
178
|
+
version: '3'
|
170
179
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
171
180
|
requirements:
|
172
181
|
- - ">="
|
@@ -174,7 +183,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
174
183
|
version: '0'
|
175
184
|
requirements: []
|
176
185
|
rubyforge_project:
|
177
|
-
rubygems_version: 2.
|
186
|
+
rubygems_version: 2.5.1
|
178
187
|
signing_key:
|
179
188
|
specification_version: 4
|
180
189
|
summary: Beautiful graphs for one or multiple datasets.
|