gruff 0.6.0-java → 0.11.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 (121) 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 +112 -0
  7. data/.travis.yml +24 -15
  8. data/.yardopts +1 -0
  9. data/{History.txt → CHANGELOG.md} +72 -25
  10. data/Gemfile +3 -7
  11. data/README.md +57 -25
  12. data/Rakefile +21 -192
  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 +21 -13
  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 +435 -704
  26. data/lib/gruff/bezier.rb +32 -17
  27. data/lib/gruff/bullet.rb +62 -68
  28. data/lib/gruff/dot.rb +38 -82
  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 +60 -0
  33. data/lib/gruff/line.rb +134 -170
  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 +68 -81
  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 +39 -42
  42. data/lib/gruff/pie.rb +180 -89
  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 +42 -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 +132 -0
  53. data/lib/gruff/renderer/text.rb +53 -0
  54. data/lib/gruff/scatter.rb +163 -182
  55. data/lib/gruff/scene.rb +31 -41
  56. data/lib/gruff/side_bar.rb +81 -65
  57. data/lib/gruff/side_stacked_bar.rb +78 -62
  58. data/lib/gruff/spider.rb +49 -57
  59. data/lib/gruff/stacked_area.rb +40 -32
  60. data/lib/gruff/stacked_bar.rb +86 -53
  61. data/lib/gruff/store/base_data.rb +38 -0
  62. data/lib/gruff/store/custom_data.rb +38 -0
  63. data/lib/gruff/store/store.rb +80 -0
  64. data/lib/gruff/store/xy_data.rb +59 -0
  65. data/lib/gruff/themes.rb +32 -33
  66. data/lib/gruff/version.rb +3 -1
  67. metadata +80 -102
  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_labels_for_null_data.rb +0 -27
  107. data/test/test_legend.rb +0 -68
  108. data/test/test_line.rb +0 -657
  109. data/test/test_mini_bar.rb +0 -33
  110. data/test/test_mini_pie.rb +0 -25
  111. data/test/test_mini_side_bar.rb +0 -36
  112. data/test/test_net.rb +0 -231
  113. data/test/test_photo.rb +0 -41
  114. data/test/test_pie.rb +0 -161
  115. data/test/test_scatter.rb +0 -233
  116. data/test/test_scene.rb +0 -100
  117. data/test/test_side_bar.rb +0 -56
  118. data/test/test_sidestacked_bar.rb +0 -105
  119. data/test/test_spider.rb +0 -226
  120. data/test/test_stacked_area.rb +0 -52
  121. data/test/test_stacked_bar.rb +0 -68
@@ -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,42 @@
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
+ @shadow_color = args[:shadow_color]
10
+ @width = args[:width]
11
+ end
12
+
13
+ def render(start_x, start_y, end_x, end_y)
14
+ render_line(start_x, start_y, end_x, end_y, @color)
15
+ render_line(start_x, start_y + 1, end_x, end_y + 1, @shadow_color) if @shadow_color
16
+ end
17
+
18
+ private
19
+
20
+ def render_line(start_x, start_y, end_x, end_y, color)
21
+ # FIXME(uwe): Workaround for Issue #66
22
+ # https://github.com/topfunky/gruff/issues/66
23
+ # https://github.com/rmagick/rmagick/issues/82
24
+ # Remove if the issue gets fixed.
25
+ unless defined?(JRUBY_VERSION)
26
+ start_x += EPSILON
27
+ end_x += EPSILON
28
+ start_y += EPSILON
29
+ end_y += EPSILON
30
+ end
31
+
32
+ draw = Renderer.instance.draw
33
+
34
+ draw.push
35
+ draw.stroke(color)
36
+ draw.fill(color)
37
+ draw.stroke_width(@width) if @width
38
+ draw.line(start_x, start_y, end_x, end_y)
39
+ draw.pop
40
+ end
41
+ end
42
+ 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,132 @@
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
+ 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
+ Renderer.instance.text_renderers = []
25
+ end
26
+
27
+ def setup_transparent_background(columns, rows)
28
+ image = Renderer.instance.render_transparent_background(columns, rows)
29
+ Renderer.instance.image = image
30
+ end
31
+
32
+ def background_image=(image)
33
+ Renderer.instance.image = image
34
+ end
35
+
36
+ def font=(font)
37
+ draw = Renderer.instance.draw
38
+ draw.font = font if font
39
+ end
40
+
41
+ def finish
42
+ draw = Renderer.instance.draw
43
+ image = Renderer.instance.image
44
+
45
+ draw.draw(image)
46
+
47
+ Renderer.instance.text_renderers.each do |renderer|
48
+ renderer.render(renderer.width, renderer.height, renderer.x, renderer.y, renderer.gravity)
49
+ end
50
+ end
51
+
52
+ def write(file_name)
53
+ Renderer.instance.image.write(file_name)
54
+ end
55
+
56
+ def to_blob(file_format)
57
+ Renderer.instance.image.to_blob do
58
+ self.format = file_format
59
+ end
60
+ end
61
+ end
62
+
63
+ def background(columns, rows, scale, theme_options)
64
+ case theme_options[:background_colors]
65
+ when Array
66
+ gradated_background(columns, rows, theme_options[:background_colors][0], theme_options[:background_colors][1], theme_options[:background_direction])
67
+ when String
68
+ solid_background(columns, rows, theme_options[:background_colors])
69
+ else
70
+ image_background(scale, *theme_options[:background_image])
71
+ end
72
+ end
73
+
74
+ # Make a new image at the current size with a solid +color+.
75
+ def solid_background(columns, rows, color)
76
+ Magick::Image.new(columns, rows) do
77
+ self.background_color = color
78
+ end
79
+ end
80
+
81
+ # Use with a theme definition method to draw a gradated background.
82
+ def gradated_background(columns, rows, top_color, bottom_color, direct = :top_bottom)
83
+ gradient_fill = begin
84
+ case direct
85
+ when :bottom_top
86
+ Magick::GradientFill.new(0, 0, 100, 0, bottom_color, top_color)
87
+ when :left_right
88
+ Magick::GradientFill.new(0, 0, 0, 100, top_color, bottom_color)
89
+ when :right_left
90
+ Magick::GradientFill.new(0, 0, 0, 100, bottom_color, top_color)
91
+ when :topleft_bottomright
92
+ Magick::GradientFill.new(0, 100, 100, 0, top_color, bottom_color)
93
+ when :topright_bottomleft
94
+ Magick::GradientFill.new(0, 0, 100, 100, bottom_color, top_color)
95
+ else
96
+ Magick::GradientFill.new(0, 0, 100, 0, top_color, bottom_color)
97
+ end
98
+ end
99
+
100
+ image = Magick::Image.new(columns, rows, gradient_fill)
101
+ @gradated_background_retry_count = 0
102
+
103
+ image
104
+ rescue StandardError => e
105
+ @gradated_background_retry_count ||= 0
106
+ GC.start
107
+
108
+ if @gradated_background_retry_count < 3
109
+ @gradated_background_retry_count += 1
110
+ gradated_background(columns, rows, top_color, bottom_color, direct)
111
+ else
112
+ raise e
113
+ end
114
+ end
115
+
116
+ # Use with a theme to use an image (800x600 original) background.
117
+ def image_background(scale, image_path)
118
+ image = Magick::Image.read(image_path)
119
+ if scale != 1.0
120
+ image[0].resize!(scale) # TODO: Resize with new scale (crop if necessary for wide graph)
121
+ end
122
+ image[0]
123
+ end
124
+
125
+ # Use with a theme to make a transparent background
126
+ def render_transparent_background(columns, rows)
127
+ Magick::Image.new(columns, rows) do
128
+ self.background_color = 'transparent'
129
+ end
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,53 @@
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
+ attr_reader :width, :height, :x, :y, :gravity
15
+ def add_to_render_queue(width, height, x, y, gravity = Magick::NorthGravity)
16
+ @width = width
17
+ @height = height
18
+ @x = x
19
+ @y = y
20
+ @gravity = gravity
21
+
22
+ Renderer.instance.text_renderers << self
23
+ end
24
+
25
+ def render(width, height, x, y, gravity = Magick::NorthGravity)
26
+ draw = Renderer.instance.draw
27
+ image = Renderer.instance.image
28
+ scale = Renderer.instance.scale
29
+
30
+ draw.rotation = @rotation if @rotation
31
+ draw.fill = @font_color
32
+ draw.stroke = 'transparent'
33
+ draw.font = @font if @font
34
+ draw.font_weight = @font_weight
35
+ draw.pointsize = @font_size * scale
36
+ draw.gravity = gravity
37
+ draw.annotate_scaled(image,
38
+ width, height,
39
+ x, y,
40
+ @text, scale)
41
+ draw.rotation = -@rotation if @rotation
42
+ end
43
+
44
+ def self.metrics(text, size, font_weight = Magick::NormalWeight)
45
+ draw = Renderer.instance.draw
46
+ image = Renderer.instance.image
47
+
48
+ draw.font_weight = font_weight
49
+ draw.pointsize = size
50
+ draw.get_type_metrics(image, text.to_s)
51
+ end
52
+ end
53
+ end
@@ -1,109 +1,93 @@
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
- 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
-
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
15
+ # datasets.
16
+ attr_writer :maximum_x_value
51
17
 
52
- def setup_drawing
53
- # TODO Need to get x-axis labels working. Current behavior will be to not allow.
54
- @labels = {}
18
+ # Minimum X Value. The value will get overwritten by the min in the
19
+ # datasets.
20
+ attr_writer :minimum_x_value
21
+
22
+ # The number of vertical lines shown for reference.
23
+ attr_writer :marker_x_count
24
+
25
+ # Attributes to allow customising the size of the points.
26
+ attr_writer :circle_radius
27
+ attr_writer :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_writer :disable_significant_rounding_x_axis
32
+
33
+ # Allow enabling vertical lines. When you have a lot of data, they can work great.
34
+ attr_writer :enable_vertical_line_markers
55
35
 
36
+ # Allow using vertical labels in the X axis (and setting the label margin).
37
+ attr_writer :x_label_margin
38
+ attr_writer :use_vertical_x_labels
39
+
40
+ # Allow passing lambdas to format labels.
41
+ attr_writer :y_axis_label_format
42
+ attr_writer :x_axis_label_format
43
+
44
+ def initialize_store
45
+ @store = Gruff::Store.new(Gruff::Store::XYData)
46
+ end
47
+ private :initialize_store
48
+
49
+ def initialize_ivars
56
50
  super
57
51
 
58
- # Translate our values so that we can use the base methods for drawing
59
- # the standard chart stuff
60
- @column_count = @x_spread
52
+ @baseline_x_color = @baseline_y_color = 'red'
53
+ @baseline_x_value = @baseline_y_value = nil
54
+ @circle_radius = nil
55
+ @disable_significant_rounding_x_axis = false
56
+ @enable_vertical_line_markers = false
57
+ @marker_x_count = nil
58
+ @maximum_x_value = @minimum_x_value = nil
59
+ @stroke_width = nil
60
+ @use_vertical_x_labels = false
61
+ @x_axis_label_format = nil
62
+ @x_label_margin = nil
63
+ @y_axis_label_format = nil
61
64
  end
65
+ private :initialize_ivars
62
66
 
63
67
  def draw
64
68
  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?
69
+ return unless data_given?
70
+
71
+ # Check to see if more than one datapoint was given. NaN can result otherwise.
72
+ @x_increment = (@x_spread > 1) ? (@graph_width / (@x_spread - 1).to_f) : @graph_width
73
+
74
+ store.norm_data.each do |data_row|
75
+ data_row.coordinates.each do |x_value, y_value|
76
+ next if y_value.nil? || x_value.nil?
89
77
 
90
78
  new_x = get_x_coord(x_value, @graph_width, @graph_left)
91
- new_y = @graph_top + (@graph_height - data_point * @graph_height)
79
+ new_y = @graph_top + (@graph_height - y_value * @graph_height)
92
80
 
93
81
  # 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)
82
+ stroke_width = @stroke_width || clip_value_if_greater_than(@columns / (store.norm_data.first[1].size * 4), 5.0)
83
+ circle_radius = @circle_radius || clip_value_if_greater_than(@columns / (store.norm_data.first[1].size * 2.5), 5.0)
84
+ Gruff::Renderer::Circle.new(color: data_row.color, width: stroke_width).render(new_x, new_y, new_x - circle_radius, new_y)
101
85
  end
102
86
  end
103
87
 
104
- @d.draw(@base_image)
88
+ Gruff::Renderer.finish
105
89
  end
106
-
90
+
107
91
  # The first parameter is the name of the dataset. The next two are the
108
92
  # x and y axis data points contain in their own array in that respective
109
93
  # order. The final parameter is the color.
@@ -114,95 +98,83 @@ class Gruff::Scatter < Gruff::Base
114
98
  # If the color argument is nil, the next color from the default theme will
115
99
  # be used.
116
100
  #
117
- # NOTE: If you want to use a preset theme, you must set it before calling
118
- # data().
101
+ # @note If you want to use a preset theme, you must set it before calling {#data}.
119
102
  #
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.
103
+ # @param name [String, Symbol] containing the name of the dataset.
104
+ # @param x_data_points [Array] An Array of of x-axis data points.
105
+ # @param y_data_points [Array] An Array of of y-axis data points.
106
+ # @param color [String] The hex string for the color of the dataset. Defaults to nil.
125
107
  #
126
- # ==== Exceptions
127
- # Data points contain nil values::
108
+ #
109
+ # @raise [ArgumentError] Data points contain nil values.
128
110
  # 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::
111
+ # contains a +nil+ value. The graph will not make an assumption
112
+ # as how to graph +nil+.
113
+ # @raise [ArgumentError] +x_data_points+ is empty.
132
114
  # 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
115
+ # @raise [ArgumentError] +y_data_points+ is empty.
116
+ # This error is raised when the array for the y-axis points are empty.
117
+ # @raise [ArgumentError] +x_data_points.length != y_data_points.length+.
118
+ # Error means that the x and y axis point arrays do not match in length.
137
119
  #
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')
120
+ # @example
121
+ # g = Gruff::Scatter.new
122
+ # g.data(:apples, [1,2,3], [3,2,1])
123
+ # g.data('oranges', [1,1,1], [2,3,4])
124
+ # g.data('bitter_melon', [3,5,6], [6,7,8], '#000000')
143
125
  #
144
- def data(name, x_data_points=[], y_data_points=[], color=nil)
145
-
126
+ def data(name, x_data_points = [], y_data_points = [], color = nil)
127
+ # make sure it's an array
128
+ x_data_points = Array(x_data_points)
129
+ y_data_points = Array(y_data_points)
130
+
146
131
  raise ArgumentError, 'Data Points contain nil Value!' if x_data_points.include?(nil) || y_data_points.include?(nil)
147
132
  raise ArgumentError, 'x_data_points is empty!' if x_data_points.empty?
148
133
  raise ArgumentError, 'y_data_points is empty!' if y_data_points.empty?
149
134
  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
135
+
136
+ # Call the existing data routine for the x/y axis data
137
+ store.add(name, y_data_points, color, x_data_points)
166
138
  end
167
-
168
- protected
169
-
139
+
140
+ alias dataxy data
141
+
142
+ private
143
+
144
+ def setup_data
145
+ # Update the global min/max values for the x data
146
+ @maximum_x_value ||= store.max_x
147
+ @minimum_x_value ||= store.min_x
148
+
149
+ super
150
+ end
151
+
152
+ def setup_drawing
153
+ # TODO: Need to get x-axis labels working. Current behavior will be to not allow.
154
+ @labels = {}
155
+
156
+ super
157
+ end
158
+
170
159
  def calculate_spread #:nodoc:
171
160
  super
172
161
  @x_spread = @maximum_x_value.to_f - @minimum_x_value.to_f
173
162
  @x_spread = @x_spread > 0 ? @x_spread : 1
174
163
  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
164
+
165
+ def normalize
166
+ return unless data_given?
167
+
168
+ store.normalize(minimum_x: @minimum_x_value, spread_x: @x_spread, minimum_y: minimum_value, spread_y: @spread)
195
169
  end
196
-
170
+
197
171
  def draw_line_markers
198
172
  # do all of the stuff for the horizontal lines on the y-axis
199
173
  super
200
174
  return if @hide_line_markers
201
-
202
- @d = @d.stroke_antialias false
203
175
 
204
176
  if @x_axis_increment.nil?
205
- # TODO Do the same for larger numbers...100, 75, 50, 25
177
+ # TODO: Do the same for larger numbers...100, 75, 50, 25
206
178
  if @marker_x_count.nil?
207
179
  (3..7).each do |lines|
208
180
  if @x_spread % lines == 0.0
@@ -212,53 +184,62 @@ protected
212
184
  end
213
185
  @marker_x_count ||= 4
214
186
  end
215
- @x_increment = (@x_spread > 0) ? significant(@x_spread / @marker_x_count) : 1
187
+ @x_increment = (@x_spread > 0) ? (@x_spread / @marker_x_count) : 1
188
+ unless @disable_significant_rounding_x_axis
189
+ @x_increment = significant(@x_increment)
190
+ end
216
191
  else
217
- # TODO Make this work for negative values
218
- @maximum_x_value = [@maximum_value.ceil, @x_axis_increment].max
192
+ # TODO: Make this work for negative values
193
+ @maximum_x_value = [maximum_value.ceil, @x_axis_increment].max
219
194
  @minimum_x_value = @minimum_x_value.floor
220
195
  calculate_spread
221
- normalize(true)
222
-
196
+ normalize
197
+
223
198
  @marker_count = (@x_spread / @x_axis_increment).to_i
224
199
  @x_increment = @x_axis_increment
225
200
  end
226
- @increment_x_scaled = @graph_width.to_f / (@x_spread / @x_increment)
201
+ increment_x_scaled = @graph_width.to_f / (@x_spread / @x_increment)
227
202
 
228
203
  # Draw vertical line markers and annotate with numbers
229
204
  (0..@marker_x_count).each do |index|
205
+ # TODO: Fix the vertical lines, and enable them by default. Not pretty when they don't match up with top y-axis line
206
+ if @enable_vertical_line_markers
207
+ x = @graph_left + @graph_width - index.to_f * increment_x_scaled
230
208
 
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)
209
+ line_renderer = Gruff::Renderer::Line.new(color: @marker_color, shadow_color: @marker_shadow_color)
210
+ line_renderer.render(x, @graph_top, x, @graph_bottom)
211
+ end
236
212
 
237
213
  unless @hide_line_numbers
238
214
  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)
215
+ y_offset = @graph_bottom + (@x_label_margin || LABEL_MARGIN)
216
+ x_offset = get_x_coord(index.to_f, increment_x_scaled, @graph_left)
217
+
218
+ label = vertical_label(marker_label, @x_increment)
219
+ rotation = -90.0 if @use_vertical_x_labels
220
+ text_renderer = Gruff::Renderer::Text.new(label, font: @font, size: @marker_font_size, color: @font_color, rotation: rotation)
221
+ text_renderer.add_to_render_queue(1.0, 1.0, x_offset, y_offset)
252
222
  end
253
223
  end
254
-
255
- @d = @d.stroke_antialias true
256
224
  end
257
-
258
- private
259
-
225
+
226
+ def label(value, increment)
227
+ if @y_axis_label_format
228
+ @y_axis_label_format.call(value)
229
+ else
230
+ super
231
+ end
232
+ end
233
+
234
+ def vertical_label(value, increment)
235
+ if @x_axis_label_format
236
+ @x_axis_label_format.call(value)
237
+ else
238
+ label(value, increment)
239
+ end
240
+ end
241
+
260
242
  def get_x_coord(x_data_point, width, offset) #:nodoc:
261
243
  x_data_point * width + offset
262
244
  end
263
-
264
- end # end Gruff::Scatter
245
+ end