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,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gruff
4
+ class Renderer::Bezier
5
+ def initialize(args = {})
6
+ @color = args[:color]
7
+ @width = args[:width] || 1.0
8
+ end
9
+
10
+ def render(points)
11
+ draw = Renderer.instance.draw
12
+
13
+ draw.push
14
+ draw.stroke(@color)
15
+ draw.stroke_width(@width)
16
+ draw.fill_opacity(0.0)
17
+ draw.bezier(*points)
18
+ draw.pop
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gruff
4
+ class Renderer::Circle
5
+ def initialize(args = {})
6
+ @color = args[:color]
7
+ @width = args[:width] || 1.0
8
+ end
9
+
10
+ def render(origin_x, origin_y, perim_x, perim_y)
11
+ draw = Renderer.instance.draw
12
+
13
+ draw.push
14
+ draw.fill(@color)
15
+ draw.stroke(@color)
16
+ draw.stroke_width(@width)
17
+ draw.circle(origin_x, origin_y, perim_x, perim_y)
18
+ draw.pop
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gruff
4
+ class Renderer::DashLine
5
+ def initialize(args = {})
6
+ @color = args[:color]
7
+ @width = args[:width]
8
+ end
9
+
10
+ def render(start_x, start_y, end_x, end_y)
11
+ draw = Renderer.instance.draw
12
+
13
+ draw.push
14
+ draw.stroke_color(@color)
15
+ draw.fill_opacity(0.0)
16
+ draw.stroke_dasharray(10, 20)
17
+ draw.stroke_width(@width)
18
+ draw.line(start_x, start_y, end_x, end_y)
19
+ draw.pop
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gruff
4
+ class Renderer::Dot
5
+ def initialize(style, config)
6
+ @style = style
7
+ @color = config[:color]
8
+ @width = config[:width] || 1.0
9
+ end
10
+
11
+ def render(new_x, new_y, circle_radius)
12
+ draw = Renderer.instance.draw
13
+
14
+ # draw.push # TODO
15
+ draw.stroke_width(@width)
16
+ draw.stroke(@color)
17
+ draw.fill(@color)
18
+ if @style.to_s == 'square'
19
+ square(draw, new_x, new_y, circle_radius)
20
+ else
21
+ circle(draw, new_x, new_y, circle_radius)
22
+ end
23
+ # draw.pop # TODO
24
+ end
25
+
26
+ def circle(draw, new_x, new_y, circle_radius)
27
+ draw.circle(new_x, new_y, new_x - circle_radius, new_y)
28
+ end
29
+
30
+ def square(draw, new_x, new_y, circle_radius)
31
+ offset = (circle_radius * 0.8).to_i
32
+ corner1 = new_x - offset
33
+ corner2 = new_y - offset
34
+ corner3 = new_x + offset
35
+ corner4 = new_y + offset
36
+ draw.rectangle(corner1, corner2, corner3, corner4)
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gruff
4
+ class Renderer::Ellipse
5
+ def initialize(args = {})
6
+ @color = args[:color]
7
+ @width = args[:width] || 1.0
8
+ end
9
+
10
+ def render(origin_x, origin_y, width, height, arc_start, arc_end)
11
+ draw = Renderer.instance.draw
12
+
13
+ draw.push
14
+ draw.stroke_width(@width)
15
+ draw.stroke(@color)
16
+ draw.fill('transparent')
17
+ draw.ellipse(origin_x, origin_y, width, height, arc_start, arc_end)
18
+ draw.pop
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gruff
4
+ class Renderer::Line
5
+ EPSILON = 0.001
6
+
7
+ def initialize(args = {})
8
+ @color = args[:color]
9
+ @width = args[:width]
10
+ end
11
+
12
+ def render(start_x, start_y, end_x, end_y)
13
+ # FIXME(uwe): Workaround for Issue #66
14
+ # https://github.com/topfunky/gruff/issues/66
15
+ # https://github.com/rmagick/rmagick/issues/82
16
+ # Remove if the issue gets fixed.
17
+ unless defined?(JRUBY_VERSION)
18
+ start_x += EPSILON
19
+ end_x += EPSILON
20
+ start_y += EPSILON
21
+ end_y += EPSILON
22
+ end
23
+
24
+ draw = Renderer.instance.draw
25
+
26
+ draw.push
27
+ draw.stroke(@color)
28
+ draw.fill(@color)
29
+ draw.stroke_width(@width) if @width
30
+ draw.line(start_x, start_y, end_x, end_y)
31
+ draw.pop
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gruff
4
+ class Renderer::Polygon
5
+ def initialize(args = {})
6
+ @color = args[:color]
7
+ @width = args[:width] || 1.0
8
+ @opacity = args[:opacity] || 1.0
9
+ end
10
+
11
+ def render(points)
12
+ draw = Renderer.instance.draw
13
+
14
+ draw.push
15
+ draw.stroke_width(@width)
16
+ draw.stroke(@color)
17
+ draw.fill(@color)
18
+ draw.fill_opacity(@opacity)
19
+ draw.polygon(*points)
20
+ draw.pop
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gruff
4
+ class Renderer::Polyline
5
+ def initialize(args = {})
6
+ @color = args[:color]
7
+ @width = args[:width]
8
+ end
9
+
10
+ def render(points)
11
+ draw = Renderer.instance.draw
12
+
13
+ draw.push
14
+ draw.stroke(@color)
15
+ draw.fill('transparent')
16
+ draw.stroke_width(@width)
17
+ draw.polyline(*points)
18
+ draw.pop
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gruff
4
+ class Renderer::Rectangle
5
+ def initialize(args = {})
6
+ @color = args[:color]
7
+ end
8
+
9
+ def render(upper_left_x, upper_left_y, lower_right_x, lower_right_y)
10
+ draw = Renderer.instance.draw
11
+
12
+ draw.push
13
+ draw.stroke('transparent')
14
+ draw.fill(@color) if @color
15
+ draw.rectangle(upper_left_x, upper_left_y, lower_right_x, lower_right_y)
16
+ draw.pop
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,127 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'singleton'
4
+
5
+ module Gruff
6
+ # @private
7
+ class Renderer
8
+ include Singleton
9
+
10
+ attr_accessor :draw, :image, :scale
11
+
12
+ class << self
13
+ def setup(columns, rows, font, scale, theme_options)
14
+ draw = Magick::Draw.new
15
+ draw.font = font if font
16
+ # Scale down from 800x600 used to calculate drawing.
17
+ draw.scale(scale, scale)
18
+
19
+ image = Renderer.instance.background(columns, rows, scale, theme_options)
20
+
21
+ Renderer.instance.draw = draw
22
+ Renderer.instance.scale = scale
23
+ Renderer.instance.image = image
24
+ end
25
+
26
+ def setup_transparent_background(columns, rows)
27
+ image = Renderer.instance.render_transparent_background(columns, rows)
28
+ Renderer.instance.image = image
29
+ end
30
+
31
+ def background_image=(image)
32
+ Renderer.instance.image = image
33
+ end
34
+
35
+ def font=(font)
36
+ draw = Renderer.instance.draw
37
+ draw.font = font if font
38
+ end
39
+
40
+ def finish
41
+ draw = Renderer.instance.draw
42
+ image = Renderer.instance.image
43
+
44
+ draw.draw(image)
45
+ end
46
+
47
+ def write(file_name)
48
+ Renderer.instance.image.write(file_name)
49
+ end
50
+
51
+ def to_blob(file_format)
52
+ Renderer.instance.image.to_blob do
53
+ self.format = file_format
54
+ end
55
+ end
56
+ end
57
+
58
+ def background(columns, rows, scale, theme_options)
59
+ case theme_options[:background_colors]
60
+ when Array
61
+ gradated_background(columns, rows, theme_options[:background_colors][0], theme_options[:background_colors][1], theme_options[:background_direction])
62
+ when String
63
+ solid_background(columns, rows, theme_options[:background_colors])
64
+ else
65
+ image_background(scale, *theme_options[:background_image])
66
+ end
67
+ end
68
+
69
+ # Make a new image at the current size with a solid +color+.
70
+ def solid_background(columns, rows, color)
71
+ Magick::Image.new(columns, rows) do
72
+ self.background_color = color
73
+ end
74
+ end
75
+
76
+ # Use with a theme definition method to draw a gradated background.
77
+ def gradated_background(columns, rows, top_color, bottom_color, direct = :top_bottom)
78
+ gradient_fill = begin
79
+ case direct
80
+ when :bottom_top
81
+ Magick::GradientFill.new(0, 0, 100, 0, bottom_color, top_color)
82
+ when :left_right
83
+ Magick::GradientFill.new(0, 0, 0, 100, top_color, bottom_color)
84
+ when :right_left
85
+ Magick::GradientFill.new(0, 0, 0, 100, bottom_color, top_color)
86
+ when :topleft_bottomright
87
+ Magick::GradientFill.new(0, 100, 100, 0, top_color, bottom_color)
88
+ when :topright_bottomleft
89
+ Magick::GradientFill.new(0, 0, 100, 100, bottom_color, top_color)
90
+ else
91
+ Magick::GradientFill.new(0, 0, 100, 0, top_color, bottom_color)
92
+ end
93
+ end
94
+
95
+ image = Magick::Image.new(columns, rows, gradient_fill)
96
+ @gradated_background_retry_count = 0
97
+
98
+ image
99
+ rescue StandardError => e
100
+ @gradated_background_retry_count ||= 0
101
+ GC.start
102
+
103
+ if @gradated_background_retry_count < 3
104
+ @gradated_background_retry_count += 1
105
+ gradated_background(top_color, bottom_color, direct)
106
+ else
107
+ raise e
108
+ end
109
+ end
110
+
111
+ # Use with a theme to use an image (800x600 original) background.
112
+ def image_background(scale, image_path)
113
+ image = Magick::Image.read(image_path)
114
+ if scale != 1.0
115
+ image[0].resize!(scale) # TODO: Resize with new scale (crop if necessary for wide graph)
116
+ end
117
+ image[0]
118
+ end
119
+
120
+ # Use with a theme to make a transparent background
121
+ def render_transparent_background(columns, rows)
122
+ Magick::Image.new(columns, rows) do
123
+ self.background_color = 'transparent'
124
+ end
125
+ end
126
+ end
127
+ end
@@ -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
@@ -1,109 +1,90 @@
1
- require File.dirname(__FILE__) + '/base'
1
+ # frozen_string_literal: true
2
2
 
3
- # Here's how to set up an XY Scatter Chart
3
+ require 'gruff/base'
4
+
5
+ #
6
+ # Here's how to set up a Gruff::Scatter.
4
7
  #
5
- # g = Gruff::Scatter.new(800)
6
- # g.data(:apples, [1,2,3,4], [4,3,2,1])
7
- # g.data('oranges', [5,7,8], [4,1,7])
8
- # g.write('test/output/scatter.png')
9
- #
8
+ # g = Gruff::Scatter.new(800)
9
+ # g.data :apples, [1,2,3,4], [4,3,2,1]
10
+ # g.data 'oranges', [5,7,8], [4,1,7]
11
+ # g.write('scatter.png')
10
12
  #
11
13
  class Gruff::Scatter < Gruff::Base
12
-
13
14
  # Maximum X Value. The value will get overwritten by the max in the
14
- # datasets.
15
+ # datasets.
15
16
  attr_accessor :maximum_x_value
16
-
17
- # Minimum X Value. The value will get overwritten by the min in the
18
- # datasets.
17
+
18
+ # Minimum X Value. The value will get overwritten by the min in the
19
+ # datasets.
19
20
  attr_accessor :minimum_x_value
20
-
21
- # The number of vertical lines shown for reference
21
+
22
+ # The number of vertical lines shown for reference.
22
23
  attr_accessor :marker_x_count
23
-
24
- #~ # Draw a dashed horizontal line at the given y value
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
-
37
- # Gruff::Scatter takes the same parameters as the Gruff::Line graph
38
- #
39
- # ==== Example
40
- #
41
- # g = Gruff::Scatter.new
42
- #
43
- def initialize(*args)
44
- super(*args)
45
-
46
- @maximum_x_value = @minimum_x_value = nil
47
- @baseline_x_color = @baseline_y_color = 'red'
48
- @baseline_x_value = @baseline_y_value = nil
49
- @marker_x_count = nil
50
- end
51
24
 
52
- def setup_drawing
53
- # TODO Need to get x-axis labels working. Current behavior will be to not allow.
54
- @labels = {}
25
+ # Attributes to allow customising the size of the points.
26
+ attr_accessor :circle_radius
27
+ attr_accessor :stroke_width
28
+
29
+ # Allow disabling the significant rounding when labeling the X axis.
30
+ # This is useful when working with a small range of high values (for example, a date range of months, while seconds as units).
31
+ attr_accessor :disable_significant_rounding_x_axis
32
+
33
+ # Allow enabling vertical lines. When you have a lot of data, they can work great.
34
+ attr_accessor :enable_vertical_line_markers
55
35
 
36
+ # Allow using vertical labels in the X axis (and setting the label margin).
37
+ attr_accessor :x_label_margin
38
+ attr_accessor :use_vertical_x_labels
39
+
40
+ # Allow passing lambdas to format labels.
41
+ attr_accessor :y_axis_label_format
42
+ attr_accessor :x_axis_label_format
43
+
44
+ def initialize_ivars
56
45
  super
57
46
 
58
- # Translate our values so that we can use the base methods for drawing
59
- # the standard chart stuff
60
- @column_count = @x_spread
47
+ @baseline_x_color = @baseline_y_color = 'red'
48
+ @baseline_x_value = @baseline_y_value = nil
49
+ @circle_radius = nil
50
+ @disable_significant_rounding_x_axis = false
51
+ @enable_vertical_line_markers = false
52
+ @marker_x_count = nil
53
+ @maximum_x_value = @minimum_x_value = nil
54
+ @stroke_width = nil
55
+ @use_vertical_x_labels = false
56
+ @x_axis_label_format = nil
57
+ @x_label_margin = nil
58
+ @y_axis_label_format = nil
59
+
60
+ @store = Gruff::Store.new(Gruff::Store::XYData)
61
61
  end
62
+ private :initialize_ivars
62
63
 
63
64
  def draw
64
65
  super
65
- return unless @has_data
66
-
67
- # Check to see if more than one datapoint was given. NaN can result otherwise.
68
- @x_increment = (@column_count > 1) ? (@graph_width / (@column_count - 1).to_f) : @graph_width
69
-
70
- #~ if (defined?(@norm_y_baseline)) then
71
- #~ level = @graph_top + (@graph_height - @norm_baseline * @graph_height)
72
- #~ @d = @d.push
73
- #~ @d.stroke_color @baseline_color
74
- #~ @d.fill_opacity 0.0
75
- #~ @d.stroke_dasharray(10, 20)
76
- #~ @d.stroke_width 5
77
- #~ @d.line(@graph_left, level, @graph_left + @graph_width, level)
78
- #~ @d = @d.pop
79
- #~ end
80
-
81
- #~ if (defined?(@norm_x_baseline)) then
82
-
83
- #~ end
84
-
85
- @norm_data.each do |data_row|
86
- data_row[DATA_VALUES_INDEX].each_with_index do |data_point, index|
87
- x_value = data_row[DATA_VALUES_X_INDEX][index]
88
- next if data_point.nil? || x_value.nil?
66
+ return unless data_given?
67
+
68
+ # Check to see if more than one datapoint was given. NaN can result otherwise.
69
+ @x_increment = (@x_spread > 1) ? (@graph_width / (@x_spread - 1).to_f) : @graph_width
70
+
71
+ store.norm_data.each do |data_row|
72
+ data_row.coordinates.each do |x_value, y_value|
73
+ next if y_value.nil? || x_value.nil?
89
74
 
90
75
  new_x = get_x_coord(x_value, @graph_width, @graph_left)
91
- new_y = @graph_top + (@graph_height - data_point * @graph_height)
76
+ new_y = @graph_top + (@graph_height - y_value * @graph_height)
92
77
 
93
78
  # Reset each time to avoid thin-line errors
94
- @d = @d.stroke data_row[DATA_COLOR_INDEX]
95
- @d = @d.fill data_row[DATA_COLOR_INDEX]
96
- @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)
98
-
99
- circle_radius = clip_value_if_greater_than(@columns / (@norm_data.first[1].size * 2.5), 5.0)
100
- @d = @d.circle(new_x, new_y, new_x - circle_radius, new_y)
79
+ stroke_width = @stroke_width || clip_value_if_greater_than(@columns / (store.norm_data.first[1].size * 4), 5.0)
80
+ circle_radius = @circle_radius || clip_value_if_greater_than(@columns / (store.norm_data.first[1].size * 2.5), 5.0)
81
+ Gruff::Renderer::Circle.new(color: data_row.color, width: stroke_width).render(new_x, new_y, new_x - circle_radius, new_y)
101
82
  end
102
83
  end
103
84
 
104
- @d.draw(@base_image)
85
+ Gruff::Renderer.finish
105
86
  end
106
-
87
+
107
88
  # The first parameter is the name of the dataset. The next two are the
108
89
  # x and y axis data points contain in their own array in that respective
109
90
  # order. The final parameter is the color.
@@ -114,95 +95,83 @@ class Gruff::Scatter < Gruff::Base
114
95
  # If the color argument is nil, the next color from the default theme will
115
96
  # be used.
116
97
  #
117
- # NOTE: If you want to use a preset theme, you must set it before calling
118
- # data().
98
+ # @note If you want to use a preset theme, you must set it before calling {#data}.
99
+ #
100
+ # @param name [String, Symbol] containing the name of the dataset.
101
+ # @param x_data_points [Array] An Array of of x-axis data points.
102
+ # @param y_data_points [Array] An Array of of y-axis data points.
103
+ # @param color [String] The hex string for the color of the dataset. Defaults to nil.
119
104
  #
120
- # ==== Parameters
121
- # name:: String or Symbol containing the name of the dataset.
122
- # x_data_points:: An Array of of x-axis data points.
123
- # y_data_points:: An Array of of y-axis data points.
124
- # color:: The hex string for the color of the dataset. Defaults to nil.
125
105
  #
126
- # ==== Exceptions
127
- # Data points contain nil values::
106
+ # @raise [ArgumentError] Data points contain nil values.
128
107
  # This error will get raised if either the x or y axis data points array
129
- # contains a <tt>nil</tt> value. The graph will not make an assumption
130
- # as how to graph <tt>nil</tt>
131
- # x_data_points is empty::
108
+ # contains a +nil+ value. The graph will not make an assumption
109
+ # as how to graph +nil+.
110
+ # @raise [ArgumentError] +x_data_points+ is empty.
132
111
  # This error is raised when the array for the x-axis points are empty
133
- # y_data_points is empty::
134
- # This error is raised when the array for the y-axis points are empty
135
- # x_data_points.length != y_data_points.length::
136
- # Error means that the x and y axis point arrays do not match in length
112
+ # @raise [ArgumentError] +y_data_points+ is empty.
113
+ # This error is raised when the array for the y-axis points are empty.
114
+ # @raise [ArgumentError] +x_data_points.length != y_data_points.length+.
115
+ # Error means that the x and y axis point arrays do not match in length.
137
116
  #
138
- # ==== Examples
139
- # g = Gruff::Scatter.new
140
- # g.data(:apples, [1,2,3], [3,2,1])
141
- # g.data('oranges', [1,1,1], [2,3,4])
142
- # g.data('bitter_melon', [3,5,6], [6,7,8], '#000000')
117
+ # @example
118
+ # g = Gruff::Scatter.new
119
+ # g.data(:apples, [1,2,3], [3,2,1])
120
+ # g.data('oranges', [1,1,1], [2,3,4])
121
+ # g.data('bitter_melon', [3,5,6], [6,7,8], '#000000')
143
122
  #
144
- def data(name, x_data_points=[], y_data_points=[], color=nil)
145
-
123
+ def data(name, x_data_points = [], y_data_points = [], color = nil)
124
+ # make sure it's an array
125
+ x_data_points = Array(x_data_points)
126
+ y_data_points = Array(y_data_points)
127
+
146
128
  raise ArgumentError, 'Data Points contain nil Value!' if x_data_points.include?(nil) || y_data_points.include?(nil)
147
129
  raise ArgumentError, 'x_data_points is empty!' if x_data_points.empty?
148
130
  raise ArgumentError, 'y_data_points is empty!' if y_data_points.empty?
149
131
  raise ArgumentError, 'x_data_points.length != y_data_points.length!' if x_data_points.length != y_data_points.length
150
-
151
- # Call the existing data routine for the y axis data
152
- super(name, y_data_points, color)
153
-
154
- #append the x data to the last entry that was just added in the @data member
155
- last_elem = @data.length()-1
156
- @data[last_elem] << x_data_points
157
-
158
- if @maximum_x_value.nil? && @minimum_x_value.nil?
159
- @maximum_x_value = @minimum_x_value = x_data_points.first
160
- end
161
-
162
- @maximum_x_value = x_data_points.max > @maximum_x_value ?
163
- x_data_points.max : @maximum_x_value
164
- @minimum_x_value = x_data_points.min < @minimum_x_value ?
165
- x_data_points.min : @minimum_x_value
132
+
133
+ # Call the existing data routine for the x/y axis data
134
+ store.add(name, y_data_points, color, x_data_points)
135
+ end
136
+
137
+ alias dataxy data
138
+
139
+ private
140
+
141
+ def setup_data
142
+ # Update the global min/max values for the x data
143
+ @maximum_x_value ||= store.max_x
144
+ @minimum_x_value ||= store.min_x
145
+
146
+ super
166
147
  end
167
-
168
- protected
169
-
148
+
149
+ def setup_drawing
150
+ # TODO: Need to get x-axis labels working. Current behavior will be to not allow.
151
+ @labels = {}
152
+
153
+ super
154
+ end
155
+
170
156
  def calculate_spread #:nodoc:
171
157
  super
172
158
  @x_spread = @maximum_x_value.to_f - @minimum_x_value.to_f
173
159
  @x_spread = @x_spread > 0 ? @x_spread : 1
174
160
  end
175
-
176
- def normalize(force=@xy_normalize)
177
- if @norm_data.nil? || force
178
- @norm_data = []
179
- return unless @has_data
180
-
181
- @data.each do |data_row|
182
- norm_data_points = [data_row[DATA_LABEL_INDEX]]
183
- norm_data_points << data_row[DATA_VALUES_INDEX].map do |r|
184
- (r.to_f - @minimum_value.to_f) / @spread
185
- end
186
- norm_data_points << data_row[DATA_COLOR_INDEX]
187
- norm_data_points << data_row[DATA_VALUES_X_INDEX].map do |r|
188
- (r.to_f - @minimum_x_value.to_f) / @x_spread
189
- end
190
- @norm_data << norm_data_points
191
- end
192
- end
193
- #~ @norm_y_baseline = (@baseline_y_value.to_f / @maximum_value.to_f) if @baseline_y_value
194
- #~ @norm_x_baseline = (@baseline_x_value.to_f / @maximum_x_value.to_f) if @baseline_x_value
161
+
162
+ def normalize
163
+ return unless data_given?
164
+
165
+ store.normalize(minimum_x: @minimum_x_value, spread_x: @x_spread, minimum_y: minimum_value, spread_y: @spread)
195
166
  end
196
-
167
+
197
168
  def draw_line_markers
198
169
  # do all of the stuff for the horizontal lines on the y-axis
199
170
  super
200
171
  return if @hide_line_markers
201
-
202
- @d = @d.stroke_antialias false
203
172
 
204
173
  if @x_axis_increment.nil?
205
- # TODO Do the same for larger numbers...100, 75, 50, 25
174
+ # TODO: Do the same for larger numbers...100, 75, 50, 25
206
175
  if @marker_x_count.nil?
207
176
  (3..7).each do |lines|
208
177
  if @x_spread % lines == 0.0
@@ -212,53 +181,60 @@ protected
212
181
  end
213
182
  @marker_x_count ||= 4
214
183
  end
215
- @x_increment = (@x_spread > 0) ? significant(@x_spread / @marker_x_count) : 1
184
+ @x_increment = (@x_spread > 0) ? (@x_spread / @marker_x_count) : 1
185
+ unless @disable_significant_rounding_x_axis
186
+ @x_increment = significant(@x_increment)
187
+ end
216
188
  else
217
- # TODO Make this work for negative values
218
- @maximum_x_value = [@maximum_value.ceil, @x_axis_increment].max
189
+ # TODO: Make this work for negative values
190
+ @maximum_x_value = [maximum_value.ceil, @x_axis_increment].max
219
191
  @minimum_x_value = @minimum_x_value.floor
220
192
  calculate_spread
221
- normalize(true)
222
-
193
+ normalize
194
+
223
195
  @marker_count = (@x_spread / @x_axis_increment).to_i
224
196
  @x_increment = @x_axis_increment
225
197
  end
226
- @increment_x_scaled = @graph_width.to_f / (@x_spread / @x_increment)
198
+ increment_x_scaled = @graph_width.to_f / (@x_spread / @x_increment)
227
199
 
228
200
  # Draw vertical line markers and annotate with numbers
229
201
  (0..@marker_x_count).each do |index|
230
-
231
- # TODO Fix the vertical lines. Not pretty when they don't match up with top y-axis line
232
- # x = @graph_left + @graph_width - index.to_f * @increment_x_scaled
233
- # @d = @d.stroke(@marker_color)
234
- # @d = @d.stroke_width 1
235
- # @d = @d.line(x, @graph_top, x, @graph_bottom)
202
+ # TODO: Fix the vertical lines, and enable them by default. Not pretty when they don't match up with top y-axis line
203
+ if @enable_vertical_line_markers
204
+ x = @graph_left + @graph_width - index.to_f * increment_x_scaled
205
+ Gruff::Renderer::Line.new(color: @marker_color).render(x, @graph_top, x, @graph_bottom)
206
+ end
236
207
 
237
208
  unless @hide_line_numbers
238
209
  marker_label = index * @x_increment + @minimum_x_value.to_f
239
- y_offset = @graph_bottom + LABEL_MARGIN
240
- x_offset = get_x_coord(index.to_f, @increment_x_scaled, @graph_left)
241
-
242
- @d.fill = @font_color
243
- @d.font = @font if @font
244
- @d.stroke('transparent')
245
- @d.pointsize = scale_fontsize(@marker_font_size)
246
- @d.gravity = NorthGravity
247
-
248
- @d = @d.annotate_scaled(@base_image,
249
- 1.0, 1.0,
250
- x_offset, y_offset,
251
- label(marker_label, @x_increment), @scale)
210
+ y_offset = @graph_bottom + (@x_label_margin || LABEL_MARGIN)
211
+ x_offset = get_x_coord(index.to_f, increment_x_scaled, @graph_left)
212
+
213
+ label = vertical_label(marker_label, @x_increment)
214
+ rotation = -90.0 if @use_vertical_x_labels
215
+ text_renderer = Gruff::Renderer::Text.new(label, font: @font, size: @marker_font_size, color: @font_color, rotation: rotation)
216
+ text_renderer.render(1.0, 1.0, x_offset, y_offset)
252
217
  end
253
218
  end
254
-
255
- @d = @d.stroke_antialias true
256
219
  end
257
-
258
- private
259
-
220
+
221
+ def label(value, increment)
222
+ if @y_axis_label_format
223
+ @y_axis_label_format.call(value)
224
+ else
225
+ super
226
+ end
227
+ end
228
+
229
+ def vertical_label(value, increment)
230
+ if @x_axis_label_format
231
+ @x_axis_label_format.call(value)
232
+ else
233
+ label(value, increment)
234
+ end
235
+ end
236
+
260
237
  def get_x_coord(x_data_point, width, offset) #:nodoc:
261
238
  x_data_point * width + offset
262
239
  end
263
-
264
- end # end Gruff::Scatter
240
+ end