prawn-graph 0.0.4 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +16 -0
  3. data/.gitignore +9 -0
  4. data/.rspec +2 -0
  5. data/.rubocop.yml +1168 -0
  6. data/.travis.yml +17 -0
  7. data/CODE_OF_CONDUCT.md +49 -0
  8. data/CONTRIBUTORS.md +6 -0
  9. data/Gemfile +4 -0
  10. data/LICENSE.txt +21 -0
  11. data/README.md +142 -0
  12. data/Rakefile +7 -43
  13. data/bin/console +14 -0
  14. data/bin/setup +8 -0
  15. data/lib/prawn-graph.rb +16 -0
  16. data/lib/prawn/graph/calculations.rb +1 -0
  17. data/lib/prawn/graph/calculations/layout_calculator.rb +108 -0
  18. data/lib/prawn/graph/chart_components.rb +2 -0
  19. data/lib/prawn/graph/chart_components/canvas.rb +138 -0
  20. data/lib/prawn/graph/chart_components/series_renderer.rb +173 -0
  21. data/lib/prawn/graph/charts.rb +4 -0
  22. data/lib/prawn/graph/charts/bar.rb +18 -0
  23. data/lib/prawn/graph/charts/base.rb +69 -0
  24. data/lib/prawn/graph/charts/legacy.rb +4 -0
  25. data/lib/prawn/graph/charts/legacy/bar.rb +28 -0
  26. data/lib/prawn/graph/charts/legacy/base.rb +193 -0
  27. data/lib/prawn/graph/charts/legacy/grid.rb +51 -0
  28. data/lib/prawn/graph/charts/legacy/line.rb +39 -0
  29. data/lib/prawn/graph/charts/line.rb +18 -0
  30. data/lib/prawn/graph/extension.rb +59 -0
  31. data/lib/prawn/graph/series.rb +79 -0
  32. data/lib/prawn/graph/theme.rb +41 -0
  33. data/lib/prawn/graph/version.rb +5 -0
  34. data/prawn-graph.gemspec +42 -0
  35. metadata +156 -80
  36. data/README.markdown +0 -64
  37. data/examples/example_helper.rb +0 -10
  38. data/examples/graph/advanced_bar_chart.rb +0 -22
  39. data/examples/graph/bar_chart.pdf +0 -185
  40. data/examples/graph/bar_chart.rb +0 -18
  41. data/examples/graph/line_chart.pdf +0 -219
  42. data/examples/graph/line_chart.rb +0 -18
  43. data/examples/graph/themed_bar_chart.rb +0 -18
  44. data/examples/graph/themed_line_chart.rb +0 -18
  45. data/lib/prawn/graph.rb +0 -94
  46. data/lib/prawn/graph/bar.rb +0 -64
  47. data/lib/prawn/graph/base.rb +0 -231
  48. data/lib/prawn/graph/chart.rb +0 -4
  49. data/lib/prawn/graph/errors.rb +0 -7
  50. data/lib/prawn/graph/grid.rb +0 -50
  51. data/lib/prawn/graph/line.rb +0 -74
  52. data/lib/prawn/graph/themes.rb +0 -116
  53. data/lib/prawn/graph/themes/37signals.yml +0 -14
  54. data/lib/prawn/graph/themes/keynote.yml +0 -14
  55. data/lib/prawn/graph/themes/monochome.yml +0 -8
  56. data/lib/prawn/graph/themes/odeo.yml +0 -14
@@ -0,0 +1,2 @@
1
+ require_relative "chart_components/series_renderer"
2
+ require_relative "chart_components/canvas"
@@ -0,0 +1,138 @@
1
+ module Prawn
2
+ module Graph
3
+ module ChartComponents
4
+ # A Prawn::Graph::Canvas represents the area on which a graph will be drawn. Think of it
5
+ # as the container in which your chart / graph will be sized to fit within.
6
+ #
7
+ class Canvas
8
+ attr_reader :layout, :series, :prawn, :theme
9
+
10
+ # @param series [Array[Prawn::Graph::Series]]
11
+ # @param prawn [Prawn::Document]
12
+ # @param options [Hash]
13
+ # @return [Prawn::Graph::Canvas] a canvas ready to be drawn on the provided +prawn+ document.
14
+ #
15
+ def initialize(series, prawn, options = {}, &block)
16
+ @series = series
17
+ verify_series_are_ok!
18
+ @options = options.merge({ series_count: series.size })
19
+ @prawn = prawn
20
+ @theme = Prawn::Graph::Theme::Default
21
+ @layout = Prawn::Graph::Calculations::LayoutCalculator.new([prawn.bounds.width, prawn.bounds.height], @options, @theme).calculate
22
+
23
+ yield self if block_given?
24
+ end
25
+
26
+ # Fires off the actual drawing of the chart on the provided canvas.
27
+ # @return [nil]
28
+ #
29
+ def draw
30
+ prawn.bounding_box(position, :width => layout.canvas_width, :height => layout.canvas_height, padding: 0) do
31
+ prawn.save_graphics_state do
32
+ apply_theme!
33
+ render_title_area!
34
+ render_series_keys!
35
+ render_graph_area!
36
+ end
37
+ end
38
+ end
39
+
40
+ # The coordinates which the canvas will be drawn at
41
+ # @return [Array] [X-Coord, Y-Coord]
42
+ #
43
+ def position
44
+ @options[:at] || [0,0]
45
+ end
46
+
47
+ private
48
+
49
+ def apply_theme!
50
+ prawn.fill_color @theme.default
51
+ prawn.stroke_color @theme.default
52
+ prawn.font_size @theme.font_sizes.default
53
+ end
54
+
55
+ def plot_series!
56
+ bar_charts = series.collect{ |s| s if s.type == :bar }.compact
57
+ others = series - bar_charts
58
+
59
+ BarChartRenderer.render(bar_charts, self, theme.series[0..(bar_charts.size - 1)]) unless bar_charts.empty?
60
+
61
+ i = bar_charts.size
62
+ others.each do |series|
63
+ SeriesRenderer.render(series, self, theme.series[i])
64
+ i+=1
65
+ end
66
+ end
67
+
68
+ def render_graph_area!
69
+ if layout.graph_area.renderable?
70
+ prawn.bounding_box layout.graph_area.point, width: layout.graph_area.width, height: layout.graph_area.height do
71
+ plot_series!
72
+ end
73
+ end
74
+ end
75
+
76
+ def render_title_area!
77
+ if layout.title_area.renderable?
78
+ prawn.text_box "<color rgb=\"#{@theme.title}\">#{@options[:title]}</color>", at: layout.title_area.point, inline_format: true,
79
+ valign: :center, align: :center, size: @theme.font_sizes.main_title, width: layout.title_area.width, height: layout.title_area.height
80
+ end
81
+ end
82
+
83
+ def render_series_keys!
84
+ if layout.series_key_area.renderable?
85
+ prawn.bounding_box layout.series_key_area.point, width: layout.series_key_area.width, height: layout.series_key_area.height do
86
+ series.each_with_index do |series, i|
87
+ series_offset = i + 1
88
+
89
+ prawn.save_graphics_state do
90
+ prawn.stroke_color = theme.axes
91
+ prawn.line_width = 0.5
92
+ prawn.fill_color = theme.series[i]
93
+
94
+ series_offset = series_offset * theme.font_sizes.series_key
95
+
96
+ title = series.title || "Series #{series_offset}"
97
+ top_position = (prawn.bounds.top - (series_offset * 3))
98
+
99
+
100
+ prawn.fill_and_stroke_rectangle([ theme.font_sizes.series_key, top_position ], theme.font_sizes.series_key, theme.font_sizes.series_key)
101
+
102
+ prawn.fill_color = theme.axes
103
+ prawn.text_box title, at: [ (theme.font_sizes.series_key * 3), top_position ], size: theme.font_sizes.series_key, height: (series_offset * 2)
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
109
+
110
+ # Verifies that we provide an array-like object of Prawn::Graph::Series instances to
111
+ # the Canvas, for later rendering.
112
+ #
113
+ def verify_series_are_ok!
114
+ if @series.respond_to?(:each) && @series.respond_to?(:collect)
115
+ classes = @series.collect{ |c| c.is_a?(Prawn::Graph::Series) }.uniq
116
+ if classes.size > 1 || classes[0] != true
117
+ raise RuntimeError.new("All of the items provided must be instances of Prawn::Graph::Series")
118
+ end
119
+ else
120
+ raise RuntimeError.new("Series provided must be an Array (or Array-like) object.")
121
+ end
122
+ end
123
+
124
+ # From prawn-svg - creates a cipped retangle, which we will then use to draw the graphs
125
+ # upon.
126
+ #
127
+ def clip_graph_to_bounds(x, y, width, height)
128
+ prawn.move_to x, y
129
+ prawn.line_to x + width, y
130
+ prawn.line_to x + width, y + height
131
+ prawn.line_to x, y + height
132
+ prawn.close_path
133
+ prawn.add_content "W n"
134
+ end
135
+ end
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,173 @@
1
+ module Prawn
2
+ module Graph
3
+ module ChartComponents
4
+
5
+ # The Prawn::Graph::ChartComponents::SeriesRenderer is used to plot indivdual Prawn::Graph::Series on
6
+ # a Prawn::Graph::ChartComponents::Canvas and its associated Prawn::Document.
7
+ #
8
+ module SeriesRenderer
9
+ class << self
10
+ # @param series [Prawn::Graph::Series]
11
+ # @param canvas [Prawn::Graph::ChartComponents::Canvas]
12
+ #
13
+ def render(series, canvas, color = '000000')
14
+ raise ArgumentError.new("series must be a Prawn::Graph::Series") unless series.is_a?(Prawn::Graph::Series)
15
+ raise ArgumentError.new("canvas must be a Prawn::Graph::ChartComponents::Canvas") unless canvas.is_a?(Prawn::Graph::ChartComponents::Canvas)
16
+
17
+ @series = series
18
+ @canvas = canvas
19
+ @prawn = canvas.prawn
20
+ @color = color
21
+
22
+ @graph_area = @canvas.layout.graph_area
23
+
24
+ @plot_area_width = @graph_area.width - 25
25
+ @plot_area_height = @graph_area.height - 20
26
+
27
+ render_line_chart
28
+ end
29
+
30
+ private
31
+
32
+ def render_line_chart
33
+ prawn.bounding_box [@graph_area.point[0] + 5, @graph_area.point[1] - 20], width: @plot_area_width, height: @plot_area_height do
34
+ j = 2
35
+ prawn.save_graphics_state do
36
+ @series.values.each_with_index do |v, i|
37
+ next if i == 0
38
+
39
+ width_per_point = (@plot_area_width / @series.size).round(2).to_f
40
+ spacing = width_per_point
41
+
42
+ prawn.line_width = 2
43
+ prawn.fill_color = @color
44
+ prawn.stroke_color = @color
45
+
46
+
47
+ previous_value = @series.values[i - 1]
48
+ this_value = v
49
+
50
+ previous_y = (point_height_percentage(previous_value) * @plot_area_height) - 5
51
+ this_y = (point_height_percentage(this_value) * @plot_area_height) - 5
52
+
53
+ previous_x_offset = ((spacing * (j - 1)) - spacing) + (spacing / 2.0)
54
+ this_x_offset = ((spacing * j) - spacing) + (spacing / 2.0)
55
+
56
+
57
+ prawn.stroke_line([previous_x_offset, previous_y], [ this_x_offset, this_y ])
58
+
59
+ prawn.fill_color = @canvas.theme.markers
60
+ prawn.fill_ellipse([ ( previous_x_offset), previous_y ], 1)
61
+ prawn.fill_ellipse([ ( this_x_offset), this_y ], 1)
62
+ j += 1
63
+ end
64
+
65
+ end
66
+ render_axes
67
+ end
68
+ end
69
+
70
+ def render_axes
71
+ prawn.stroke_color = @canvas.theme.axes
72
+ prawn.fill_color = @canvas.theme.axes
73
+ prawn.stroke_horizontal_line(0, @plot_area_width, at: 0)
74
+ prawn.stroke_vertical_line(0, @plot_area_height, at: 0)
75
+ prawn.fill_and_stroke_ellipse [ 0,0], 1
76
+ end
77
+
78
+ # Calculates the relative height of a given point based on the maximum value present in
79
+ # the series.
80
+ #
81
+ def point_height_percentage(value)
82
+ ((BigDecimal(value, 10)/BigDecimal(@series.max, 10)) * BigDecimal(1)).round(2)
83
+ end
84
+
85
+ def prawn
86
+ @prawn
87
+ end
88
+ end
89
+ end
90
+
91
+
92
+
93
+ # The Prawn::Graph::ChartComponents::BarChartRenderer is used to plot one or more bar charts
94
+ # in a sensible way on a a Prawn::Graph::ChartComponents::Canvas and its associated
95
+ # Prawn::Document.
96
+ #
97
+ module BarChartRenderer
98
+ class << self
99
+ # @param series [Prawn::Graph::Series]
100
+ # @param canvas [Prawn::Graph::ChartComponents::Canvas]
101
+ #
102
+ def render(series, canvas, colors)
103
+ @series = series
104
+ @canvas = canvas
105
+ @prawn = canvas.prawn
106
+ @colors = colors
107
+
108
+
109
+ @graph_area = @canvas.layout.graph_area
110
+
111
+ @plot_area_width = @graph_area.width - 25
112
+ @plot_area_height = @graph_area.height - 20
113
+
114
+ render_bar_charts
115
+ end
116
+
117
+ private
118
+
119
+ def render_bar_charts
120
+ prawn.bounding_box [@graph_area.point[0] + 5, @graph_area.point[1] - 20], width: @plot_area_width, height: @plot_area_height do
121
+
122
+ prawn.save_graphics_state do
123
+ num_points = @series[0].size
124
+ width_per_point = (@plot_area_width / num_points).round(2).to_f
125
+ width = ((width_per_point * BigDecimal('0.9')) / @series.size).round(2).to_f
126
+ spacing = (width_per_point - (width * @series.size).to_f / 2.0)
127
+
128
+ num_points.times do |point|
129
+
130
+ @series.size.times do |series_index|
131
+ series_offset = series_index + 1
132
+ prawn.fill_color = @colors[series_index]
133
+ prawn.stroke_color = @colors[series_index]
134
+ prawn.line_width = width
135
+
136
+ starting = (prawn.bounds.left + (point * width_per_point))
137
+
138
+ x_position = ( (starting + (series_offset * width) ).to_f - (width / 2.0))
139
+ y_position = ((point_height_percentage(@series[series_index].values[point]) * @plot_area_height) - 5).to_f
140
+
141
+ prawn.fill_and_stroke_line([ x_position ,0], [x_position ,y_position])
142
+ end
143
+
144
+ end
145
+
146
+ end
147
+ render_axes
148
+ end
149
+ end
150
+
151
+ def render_axes
152
+ prawn.stroke_color = @canvas.theme.axes
153
+ prawn.fill_color = @canvas.theme.axes
154
+ prawn.stroke_horizontal_line(0, @plot_area_width, at: 0)
155
+ prawn.stroke_vertical_line(0, @plot_area_height, at: 0)
156
+ prawn.fill_and_stroke_ellipse [ 0,0], 1
157
+ end
158
+
159
+ # Calculates the relative height of a given point based on the maximum value present in
160
+ # the series.
161
+ #
162
+ def point_height_percentage(value)
163
+ ((BigDecimal(value, 10)/BigDecimal(@series[0].max, 10)) * BigDecimal(1)).round(2)
164
+ end
165
+
166
+ def prawn
167
+ @prawn
168
+ end
169
+ end
170
+ end
171
+ end
172
+ end
173
+ end
@@ -0,0 +1,4 @@
1
+ require_relative "charts/base"
2
+ require_relative "charts/bar"
3
+ require_relative "charts/line"
4
+ require_relative "charts/legacy"
@@ -0,0 +1,18 @@
1
+ module Prawn
2
+ module Graph
3
+ module Charts
4
+ class Bar < Base
5
+
6
+ private
7
+
8
+ def chart_object
9
+ Prawn::Graph::Charts::Legacy::Bar.new(@series.collect(&:to_a), @prawn, @options)
10
+ end
11
+
12
+ def series_type
13
+ :bar
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,69 @@
1
+ module Prawn
2
+ module Graph
3
+ module Charts
4
+
5
+ class Base
6
+ VALID_OPTIONS = [:at, :width, :height]
7
+ attr_reader :series, :prawn, :options, :lowest_value, :highest_value
8
+
9
+ def initialize(data, prawn, options = {}, &block)
10
+ Prawn.verify_options VALID_OPTIONS, options
11
+
12
+ if data.is_a?(Array) || (!data.is_a?(Array) && data.is_a?(Prawn::Graph::Series))
13
+ data = [ data ] unless data.is_a?(Array)
14
+ else
15
+ raise ArgumentError.new("data must be a multidimensional Array, array of Prawn::Graph::Series or a single Prawn::Graph::Series")
16
+ end
17
+
18
+ @series = []
19
+ data.each do |s|
20
+ if s.is_a?(Prawn::Graph::Series)
21
+ @series << s
22
+ else
23
+ title = s.shift
24
+ @series << Prawn::Graph::Series.new(s, title: title, type: series_type)
25
+ end
26
+ end
27
+
28
+ @prawn = prawn
29
+ @options = options
30
+ calculate_extreme_values!
31
+ end
32
+
33
+ def titles
34
+ @series.collect(&:title)
35
+ end
36
+
37
+ def position
38
+ end
39
+
40
+ def draw
41
+ chart_object.draw
42
+ end
43
+
44
+ private
45
+
46
+ def chart_object
47
+ raise RuntimeError.new("I need to be subclassed.")
48
+ end
49
+
50
+ def series_type
51
+ :bar
52
+ end
53
+
54
+ def calculate_extreme_values!
55
+ @lowest_value = @series.collect(&:min).min
56
+ @highest_value = @series.collect(&:max).max
57
+ end
58
+
59
+ def process_the(data_array)
60
+ col = []
61
+ val = []
62
+ data_array.each { |i| val << i[1]; col << i[0] }
63
+ [ col, val, val.min, val.max ]
64
+ end
65
+
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,4 @@
1
+ require_relative 'legacy/grid'
2
+ require_relative 'legacy/base'
3
+ require_relative 'legacy/bar'
4
+ require_relative 'legacy/line'
@@ -0,0 +1,28 @@
1
+ module Prawn
2
+ module Graph
3
+ module Charts
4
+ module Legacy
5
+ class Bar < Prawn::Graph::Charts::Legacy::Base
6
+
7
+ private
8
+
9
+ def plot_values
10
+ base_x = @grid.start_x + 1
11
+ base_y = @grid.start_y + 1
12
+ bar_width = calculate_bar_width
13
+ @document.line_width bar_width
14
+ last_position = base_x
15
+ point_spacing = calculate_plot_spacing
16
+ @values.each do |value|
17
+ @document.move_to [base_x + last_position, base_y]
18
+ bar_height = calculate_point_height_from value
19
+ @document.stroke_color @theme.series.sample
20
+ @document.stroke_line_to [base_x + last_position, base_y + bar_height]
21
+ last_position += point_spacing
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end