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