prawn-graph 0.0.4 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
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