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.
- checksums.yaml +7 -0
- data/.codeclimate.yml +16 -0
- data/.gitignore +9 -0
- data/.rspec +2 -0
- data/.rubocop.yml +1168 -0
- data/.travis.yml +17 -0
- data/CODE_OF_CONDUCT.md +49 -0
- data/CONTRIBUTORS.md +6 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +142 -0
- data/Rakefile +7 -43
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/prawn-graph.rb +16 -0
- data/lib/prawn/graph/calculations.rb +1 -0
- data/lib/prawn/graph/calculations/layout_calculator.rb +108 -0
- data/lib/prawn/graph/chart_components.rb +2 -0
- data/lib/prawn/graph/chart_components/canvas.rb +138 -0
- data/lib/prawn/graph/chart_components/series_renderer.rb +173 -0
- data/lib/prawn/graph/charts.rb +4 -0
- data/lib/prawn/graph/charts/bar.rb +18 -0
- data/lib/prawn/graph/charts/base.rb +69 -0
- data/lib/prawn/graph/charts/legacy.rb +4 -0
- data/lib/prawn/graph/charts/legacy/bar.rb +28 -0
- data/lib/prawn/graph/charts/legacy/base.rb +193 -0
- data/lib/prawn/graph/charts/legacy/grid.rb +51 -0
- data/lib/prawn/graph/charts/legacy/line.rb +39 -0
- data/lib/prawn/graph/charts/line.rb +18 -0
- data/lib/prawn/graph/extension.rb +59 -0
- data/lib/prawn/graph/series.rb +79 -0
- data/lib/prawn/graph/theme.rb +41 -0
- data/lib/prawn/graph/version.rb +5 -0
- data/prawn-graph.gemspec +42 -0
- metadata +156 -80
- data/README.markdown +0 -64
- data/examples/example_helper.rb +0 -10
- data/examples/graph/advanced_bar_chart.rb +0 -22
- data/examples/graph/bar_chart.pdf +0 -185
- data/examples/graph/bar_chart.rb +0 -18
- data/examples/graph/line_chart.pdf +0 -219
- data/examples/graph/line_chart.rb +0 -18
- data/examples/graph/themed_bar_chart.rb +0 -18
- data/examples/graph/themed_line_chart.rb +0 -18
- data/lib/prawn/graph.rb +0 -94
- data/lib/prawn/graph/bar.rb +0 -64
- data/lib/prawn/graph/base.rb +0 -231
- data/lib/prawn/graph/chart.rb +0 -4
- data/lib/prawn/graph/errors.rb +0 -7
- data/lib/prawn/graph/grid.rb +0 -50
- data/lib/prawn/graph/line.rb +0 -74
- data/lib/prawn/graph/themes.rb +0 -116
- data/lib/prawn/graph/themes/37signals.yml +0 -14
- data/lib/prawn/graph/themes/keynote.yml +0 -14
- data/lib/prawn/graph/themes/monochome.yml +0 -8
- data/lib/prawn/graph/themes/odeo.yml +0 -14
@@ -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,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,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
|