prawn-charts 0.0.1.alpha

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d229bb1567aa946fa8f682c7fba2c2464cdf44ca
4
+ data.tar.gz: e1e42e6ddd1e162ba0bba5e57aca729dc0af7e5d
5
+ SHA512:
6
+ metadata.gz: 14f82bd9bfd2013ca1ed09fd8522b2a38e05b0ca8647cf62702d13b902581fca1aa17dbcb96232fb8067b7432fd4751a6574e1ad585fd7365fec5e733b991742
7
+ data.tar.gz: 74033d486dd821171d746e55b0fa806e8f674aefe60b6b8fb34f71cd344c63d09ceff516a71d6da09102f8d0f64a09373abf56262842db7e146f3576ea3a416c
data/.gitignore ADDED
@@ -0,0 +1,51 @@
1
+ .gems
2
+ *.pdf
3
+ *.gem
4
+ *.rbc
5
+ .bundle
6
+ .config
7
+ .yardoc
8
+ Gemfile.lock
9
+ InstalledFiles
10
+ _yardoc
11
+ coverage
12
+ doc/
13
+ lib/bundler/man
14
+ pkg
15
+ rdoc
16
+ spec/reports
17
+ test/tmp
18
+ test/version_tmp
19
+ tmp
20
+ /.config
21
+ /coverage/
22
+ /InstalledFiles
23
+ /pkg/
24
+ /spec/reports/
25
+ /test/tmp/
26
+ /test/version_tmp/
27
+ /tmp/
28
+
29
+ ## Specific to RubyMotion:
30
+ .dat*
31
+ .repl_history
32
+ build/
33
+
34
+ ## Documentation cache and generated files:
35
+ /.yardoc/
36
+ /_yardoc/
37
+ /doc/
38
+ /rdoc/
39
+
40
+ ## Environment normalisation:
41
+ /.bundle/
42
+ /lib/bundler/man/
43
+
44
+ # for a library or gem, you might want to ignore these files since the code is
45
+ # intended to run in multiple environments; otherwise, check them in:
46
+ # Gemfile.lock
47
+ # .ruby-version
48
+ # .ruby-gemset
49
+
50
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
51
+ .rvmrc
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in prawn-charts.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2014 Zac Kleinpeter
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Zac
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,30 @@
1
+ # Prawn::Charts
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'prawn-charts'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install prawn-charts
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create new Pull Request
30
+ =======
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require 'rake/testtask'
4
+
5
+ Rake::TestTask.new(:test) do |test|
6
+ test.test_files = FileList['spec/*_spec.rb']
7
+ end
8
+
9
+ desc "Run Specs"
10
+ task default: :test
11
+
@@ -0,0 +1,14 @@
1
+ en:
2
+ prawn:
3
+ charts:
4
+ errors:
5
+ messages:
6
+ no_series:
7
+ message: "No series data for %{chart_type}."
8
+ summary:
9
+ "When creating a chart %{klass} looks for a key named 'series'
10
+ to be in the options passed in. This series data will need to be
11
+ in a specific format for the %{chart_type} to understand."
12
+ resolution: "Please use the following series example to create the
13
+ %{chart_type}.
14
+ Example: %{example_options}"
@@ -0,0 +1,79 @@
1
+ module Prawn
2
+ module Charts
3
+ class Bar < Base
4
+ attr_accessor :ratio
5
+
6
+ def initialize pdf, opts = {}
7
+ super pdf, opts
8
+ @ratio = opts[:ratio] || 0.75
9
+ end
10
+
11
+ def plot_values
12
+ return if series.nil?
13
+ series.each_with_index do |bar,index|
14
+ point_x = first_x_point index
15
+
16
+ bar[:values].each do |h|
17
+ fill_color bar[:color]
18
+ fill do
19
+ height = value_height(h[:value])
20
+ rectangle [point_x,height], bar_width, height
21
+ #draw_text height.to_i, at: [point_x,height]
22
+ #draw_text h[:value], at: [point_x,height]
23
+ end
24
+ point_x += additional_points
25
+ end
26
+
27
+ end
28
+ end
29
+
30
+ def first_x_point index
31
+ (bar_width * index) + (bar_space * (index + 1))
32
+ end
33
+
34
+ def additional_points
35
+ (bar_space + bar_width) * series.count
36
+ end
37
+
38
+ def x_points
39
+ points = nil
40
+ series.each_with_index do |bar,index|
41
+ tmp = []
42
+
43
+ tmp << first_x_point(index) + (bar_width / 2)
44
+
45
+ bar[:values].each do |h|
46
+ tmp << tmp.last + additional_points
47
+ end
48
+
49
+ points ||= [0] * tmp.length
50
+
51
+ tmp.each_with_index do |point, i|
52
+ points[i] += point
53
+ end
54
+ end
55
+
56
+ points.map do |point|
57
+ (point / series.count).to_i
58
+ end
59
+ end
60
+
61
+ def bar_width
62
+ @bar_width ||= (bounds.width * ratio) / series_length.to_f
63
+ end
64
+
65
+ def bar_space
66
+ @bar_space ||= (bounds.width * (1.0 - ratio)) / (series_length + 1).to_f
67
+ end
68
+
69
+ def series_length
70
+ series.map { |v| v[:values].length }.max * series.count
71
+ end
72
+
73
+ def value_height val
74
+ bounds.height * ((val - min_value) / series_height.to_f)
75
+ end
76
+
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,262 @@
1
+ module Prawn
2
+ module Charts
3
+
4
+ # Prawn::Charts::Base will handle most of the common activities that any
5
+ # chart will require. It also will call the drawing functions for each of
6
+ # the types of charts.
7
+ class Base
8
+ attr_reader :pdf, :config
9
+
10
+ extend Forwardable
11
+
12
+ def_delegators :@pdf, :bounding_box, :bounds
13
+ def_delegators :@pdf, :draw_text, :pad, :text
14
+ def_delegators :@pdf, :stroke_axis, :rotate, :stroke_bounds
15
+ def_delegators :@pdf, :height_of, :width_of
16
+ def_delegators :@pdf, :fill, :fill_color
17
+ def_delegators :@pdf, :rectangle, :stroke_color, :line, :stroke
18
+ def_delegators :@pdf, :fill_ellipse, :curve
19
+
20
+ #
21
+ # @param pdf [Prawn::Document] and instance of the prawn document
22
+ # @param opts [Hash]
23
+ # @option opts :title [String]
24
+ # @option opts :at [Array<x,y>]
25
+ # @option opts :width [Fixnum] (500)
26
+ # @option opts :height [Fixnum] (200)
27
+ # @option opts :x [Hash] ({title: 'X Axis', display: false })
28
+ # @option opts :y [Hash] ({title: 'Y Axis', display: false})
29
+ # @option opts :y1 [Hash] ({title: 'Y1 Axis', display: false})
30
+ # @option opts :key_formatter [Proc] lambda{|key| key.to_s },
31
+ # @option opts :value_formatter [Proc] lambda{|value| value.to_s},
32
+ # @option opts :series [Array<Hash>] [
33
+ # {
34
+ # name: 'Red',
35
+ # color: 'FF00',
36
+ # values: [{ key: , value: }]
37
+ # },
38
+ # {
39
+ # name: 'Blue',
40
+ # color: '1F1F',
41
+ # values: [{ key: , value: }]
42
+ # }]
43
+ # }
44
+ def initialize pdf, opts = {}
45
+
46
+ @pdf = pdf
47
+ opts = defaults.merge(opts)
48
+ @config = OpenStruct.new defaults.merge(opts)
49
+ opts.keys.each do |key|
50
+ define_singleton_method key.to_s, &@config.method(key)
51
+ end
52
+
53
+ raise Errors::NoChartData.new if @config.series.nil?
54
+ end
55
+
56
+ def defaults
57
+ {
58
+ padding: {
59
+ bottom: 50,
60
+ left: 50,
61
+ right: 50,
62
+ top: 50,
63
+ },
64
+ x: { display: false },
65
+ y: { display: false },
66
+ y1: { display: false },
67
+ at: [0,0],
68
+ width: 500,
69
+ height: 200,
70
+ key_formatter: lambda{ |key| key.to_s },
71
+ value_formatter: lambda{ |value| value.to_s }
72
+ }
73
+ end
74
+
75
+ def draw
76
+ bounding_box at, width: width, height: height do
77
+ stroke_bounds
78
+ fill_color '0000'
79
+
80
+ draw_title
81
+ draw_x_axis_label if x[:display]
82
+ draw_y_axis_label if y[:display]
83
+ draw_y1_axis_label if y1[:display]
84
+
85
+ bounding_box(chart_at, width: chart_width, height: chart_height) do
86
+ draw_x_axis if x[:display]
87
+ draw_y_axis if y[:display]
88
+ draw_y1_axis if y1[:display]
89
+ #stroke_axis( color: 'FF00', step_length: 50 )
90
+ plot_values
91
+ end
92
+
93
+ end
94
+ end
95
+
96
+ def padding_top_bottom
97
+ padding[:top] + padding[:bottom]
98
+ end
99
+
100
+ def padding_left_right
101
+ padding[:left] + padding[:right]
102
+ end
103
+
104
+ def chart_at
105
+ [ bounds.left + (padding_left_right / 2), bounds.top - (padding_top_bottom / 2) ]
106
+ end
107
+
108
+ def chart_width
109
+ bounds.width - padding_left_right
110
+ end
111
+
112
+ def chart_height
113
+ bounds.height - padding_top_bottom
114
+ end
115
+
116
+ def draw_title
117
+ opts ={ width: bounds.width, height: height_of(title.to_s) }
118
+ bounding_box( [bounds.left, bounds.top - height_of(title.to_s) / 2], opts ) do
119
+ text title, align: :center
120
+ end
121
+ end
122
+
123
+ def draw_y_axis_label
124
+ w = width_of(y[:title]) / 2
125
+ rotate 90, origin: [bounds.left + w , (bounds.height / 2 )] do
126
+ mid = bounds.height / 2
127
+ draw_text y[:title], at: [0, mid]
128
+ end
129
+ end
130
+
131
+ def draw_y1_axis_label
132
+ w = width_of(y1[:title]) / 2
133
+ rotate 270, origin: [bounds.right - w, (bounds.height / 2 )] do
134
+ mid = bounds.height / 2
135
+ draw_text y1[:title], at: [bounds.right - w, mid]
136
+ end
137
+ end
138
+
139
+ def draw_x_axis_label
140
+ opts ={ width: bounds.width, height: height_of(x[:title]) }
141
+ bounding_box( [bounds.left, bounds.bottom + height_of(x[:title])], opts ) do
142
+ text x[:title], align: :center
143
+ end
144
+ end
145
+
146
+
147
+ def draw_x_axis
148
+ txt = series.map do |s|
149
+ s[:values].map{ |v| height_of(key_formatter.call(v[:key]))}.max
150
+ end.max
151
+
152
+ opts = {
153
+ series: series,
154
+ at: [0,0],
155
+ width: bounds.width,
156
+ height: txt,
157
+ points: x_points,
158
+ formatter: key_formatter
159
+ }
160
+
161
+ Prawn::Charts::XAxis.new(pdf, opts).draw
162
+ end
163
+
164
+ def draw_y_axis
165
+ txt = series.map do |s|
166
+ s[:values].map{ |v| width_of(value_formatter.call(v[:value]))}.max
167
+ end.max
168
+
169
+ opts = {
170
+ at: [-txt, bounds.height],
171
+ width: txt,
172
+ height: bounds.height,
173
+ points: [min_value, max_value],
174
+ formatter: value_formatter,
175
+ percentage: percentage
176
+ }
177
+
178
+ Prawn::Charts::YAxis.new(pdf, opts).draw
179
+
180
+ end
181
+
182
+ def draw_y1_axis
183
+ txt = series.map do |s|
184
+ s[:values].map{ |v| width_of(value_formatter.call(v[:value]))}.max
185
+ end.max
186
+
187
+ opts = {
188
+ at: [bounds.right, bounds.height],
189
+ width: txt,
190
+ height: bounds.height,
191
+ points: [min_value, max_value],
192
+ formatter: value_formatter
193
+ }
194
+
195
+ Prawn::Charts::YAxis.new(pdf, opts).draw
196
+
197
+ end
198
+
199
+
200
+ def plot_values
201
+ end
202
+
203
+ def x_points
204
+ end
205
+
206
+ def values
207
+ @values ||= series.map{ |bar| bar[:values].map{|single| single[:value] }}.flatten
208
+ end
209
+
210
+ def keys
211
+ @keys ||= series.map{ |v| v[:values].map{|k| k[:key] }}.flatten.uniq
212
+ end
213
+
214
+ def max_value
215
+ n = values.max
216
+ exp = 10 ** (Math.log10(n).floor - 1)
217
+ n + ( exp - n % exp) + exp
218
+ end
219
+
220
+ def min_value
221
+ n = (values.min - delta_value * 0.1).to_i
222
+ exp = 10 ** (Math.log10(n).floor - 1)
223
+ n - (n % exp)
224
+ end
225
+
226
+ def delta_value
227
+ values.max - values.min
228
+ end
229
+
230
+ def series_height
231
+ max_value - min_value
232
+ end
233
+
234
+ def percentage
235
+ false
236
+ end
237
+
238
+ def stacked_bar_values
239
+ keys.map do |key|
240
+ items = for_key(key)
241
+ {
242
+ key: key,
243
+ values: items,
244
+ total: items.inject(0){ |s,v| s + v[:value] }
245
+ }
246
+ end
247
+ end
248
+
249
+
250
+ def for_key key
251
+ series.map do |v|
252
+ {
253
+ name: v[:name],
254
+ color: v[:color],
255
+ value: v[:values].detect{|k| k[:key] == key }[:value]
256
+ }
257
+ end
258
+ end
259
+
260
+ end
261
+ end
262
+ end
@@ -0,0 +1,19 @@
1
+ module Prawn
2
+ module Charts
3
+
4
+ class Combo
5
+
6
+ def initialize pdf, opts = {}
7
+ @pdf = pdf
8
+ @line_chart = opts[:line_chart]
9
+ @bar_chart = opts[:bar_chart]
10
+ end
11
+
12
+ def draw
13
+ Prawn::Charts::Bar.new( @pdf, @bar_chart ).draw
14
+ Prawn::Charts::Line.new(@pdf, @line_chart ).draw
15
+ end
16
+
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,52 @@
1
+ module Prawn
2
+ module Charts
3
+ class NoSeries < Error
4
+
5
+ def initialize chart_type, klass, opts
6
+ super(
7
+ compose_message(
8
+ "no_series",
9
+ {
10
+ chart_type: chart_type,
11
+ klass: klass,
12
+ example_options: "\n\n" + opts.merge(series).inspect
13
+ }
14
+ )
15
+ )
16
+ end
17
+
18
+
19
+ private
20
+
21
+ def series
22
+ {
23
+ series: [
24
+ {
25
+ name: 'Red',
26
+ color: 'FF00',
27
+ value_formatter: "lambda{|value| value.to_s}",
28
+ values: [
29
+ { key: 1, value: 100 },
30
+ { key: 2, value: 220 },
31
+ { key: 3, value: 330 },
32
+ { key: 4, value: 403 }
33
+ ]
34
+ },
35
+ {
36
+ name: 'Green',
37
+ color: '0000',
38
+ value_formatter: "lambda{|value| value.to_s}",
39
+ values: [
40
+ { key: 1, value: 140 },
41
+ { key: 2, value: 120 },
42
+ { key: 3, value: 330 },
43
+ { key: 4, value: 300 }
44
+ ]
45
+ }
46
+ ]
47
+ }
48
+ end
49
+
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,87 @@
1
+ module Prawn
2
+ module Charts
3
+ # Modelled after Mongoid Errors
4
+ # SOURCE: Mongoid creator of awesome error messages
5
+ # https://github.com/mongoid/mongoid/blob/master/lib/mongoid/errors/mongoid_error.rb
6
+ class Error < StandardError
7
+
8
+ BASE_KEY = 'prawn.charts.errors.messages'
9
+ #
10
+ # Compose the message.
11
+ #
12
+ # @example Create the message
13
+ # error.compose_message
14
+ #
15
+ # @return [ String ] The composed message.
16
+ def compose_message(key, attributes)
17
+ @problem = problem(key, attributes)
18
+ @summary = summary(key, attributes)
19
+ @resolution = resolution(key, attributes)
20
+
21
+ "\n\nProblem:\n\n #{@problem}"+
22
+ "\n\nSummary:\n\n #{@summary}"+
23
+ "\n\nResolution:\n\n #{@resolution} \n\n"
24
+ end
25
+
26
+ private
27
+
28
+ # Given the key of the specific error and the options hash, translate the
29
+ # message.
30
+ #
31
+ # @example Translate the message.
32
+ # error.translate("errors", :key => value)
33
+ #
34
+ # @param [ String ] key The key of the error in the locales.
35
+ # @param [ Hash ] options The objects to pass to create the message.
36
+ #
37
+ # @return [ String ] A localized error message string.
38
+ def translate(key, options)
39
+ ::I18n.translate("#{BASE_KEY}.#{key}", options)
40
+ end
41
+
42
+ # Create the problem.
43
+ #
44
+ # @example Create the problem.
45
+ # error.problem("error", {})
46
+ #
47
+ # @param [ String, Symbol ] key The error key.
48
+ # @param [ Hash ] attributes The attributes to interpolate.
49
+ #
50
+ # @return [ String ] The problem.
51
+ def problem(key, attributes)
52
+ translate("#{key}.message", attributes)
53
+ end
54
+
55
+ # Create the summary.
56
+ #
57
+ # @example Create the summary.
58
+ # error.summary("error", {})
59
+ #
60
+ # @param [ String, Symbol ] key The error key.
61
+ # @param [ Hash ] attributes The attributes to interpolate.
62
+ #
63
+ # @return [ String ] The summary.
64
+ def summary(key, attributes)
65
+ translate("#{key}.summary", attributes)
66
+ end
67
+
68
+ # Create the resolution.
69
+ #
70
+ # @example Create the resolution.
71
+ # error.resolution("error", {})
72
+ #
73
+ # @param [ String, Symbol ] key The error key.
74
+ # @param [ Hash ] attributes The attributes to interpolate.
75
+ #
76
+ # @return [ String ] The resolution.
77
+ def resolution(key, attributes)
78
+ translate("#{key}.resolution", attributes)
79
+ end
80
+
81
+ end
82
+ #NoChartData = Class.new StandardError
83
+ #MalformedSeries = Class.new StandardError
84
+ #MalformedAxis = Class.new StandardError
85
+ #NoPlotValuesMethod = Class.new StandardError
86
+ end
87
+ end