prawn-charts 0.0.1.alpha

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.
@@ -0,0 +1,61 @@
1
+ module Prawn
2
+ module Charts
3
+
4
+ class Line < Bar
5
+
6
+ def plot_values
7
+ return if series.nil?
8
+ series.each_with_index do |bar,index|
9
+ point_x = first_x
10
+
11
+ points = bar[:values].map do |h|
12
+ height = value_height(h[:value])
13
+ point = [point_x,height]
14
+
15
+ fill_color bar[:color]
16
+ fill_ellipse point, 2
17
+ point_x += addition_x
18
+
19
+ point
20
+ end
21
+
22
+ stroke_color bar[:color]
23
+ stroke do
24
+ last_point = points.first
25
+ points.each do |point|
26
+ line last_point, point
27
+ last_point = point
28
+ end
29
+ end
30
+
31
+ end
32
+ end
33
+
34
+ def x_points
35
+ points = []
36
+ stacked_bar_values.each_with_index do |stack,index|
37
+ points << first_x_point(index) + (bar_width / 2)
38
+ end
39
+ points
40
+ end
41
+
42
+ def percentage
43
+ false
44
+ end
45
+
46
+
47
+ def first_x
48
+ (bar_width / 2) + bar_space
49
+ end
50
+
51
+ def addition_x
52
+ bar_width + bar_space
53
+ end
54
+
55
+
56
+ def series_length
57
+ series.map { |v| v[:values].length }.max
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,49 @@
1
+ module Prawn
2
+ module Charts
3
+ class StackedBar < Bar
4
+
5
+ def plot_values
6
+ stacked_bar_values.each_with_index do |stack,index|
7
+ point_x = first_x_point(index)
8
+
9
+ point_y = bounds.bottom
10
+ stack[:values].each do |h|
11
+ fill_color h[:color]
12
+ fill do
13
+ percentage = h[:value].to_f / stack[:total].to_f
14
+ height = bounds.height * percentage
15
+ point_y += height
16
+ rectangle [point_x,point_y], bar_width, height
17
+ end
18
+ end
19
+ end
20
+ end
21
+
22
+ def percentage
23
+ true
24
+ end
25
+
26
+ def x_points
27
+ points = []
28
+ stacked_bar_values.each_with_index do |stack,index|
29
+ points << first_x_point(index) + (bar_width / 2)
30
+ end
31
+ points
32
+ end
33
+
34
+ def series_length
35
+ series.map { |v| v[:values].length }.max
36
+ end
37
+
38
+ def max_value
39
+ 100
40
+ end
41
+
42
+ def min_value
43
+ 0
44
+ end
45
+
46
+
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,5 @@
1
+ module Prawn
2
+ module Charts
3
+ VERSION = "0.0.1.alpha"
4
+ end
5
+ end
@@ -0,0 +1,63 @@
1
+ module Prawn
2
+ module Charts
3
+ class XAxis
4
+ attr_reader :pdf
5
+ attr_accessor :series, :at, :width, :height, :formatter
6
+
7
+ extend Forwardable
8
+
9
+ def_delegators :@pdf, :bounding_box, :stroke_bounds, :text
10
+ def_delegators :@pdf, :height_of, :width_of, :fill_color
11
+ def_delegators :@pdf, :draw_text, :bounds, :rotate
12
+
13
+ def initialize pdf, opts
14
+ @pdf = pdf
15
+ @series = opts[:series]
16
+ @at = opts[:at]
17
+ @width = opts[:width]
18
+ @height = opts[:height]
19
+ @points = opts[:points]
20
+ @formatter = opts[:formatter]
21
+ end
22
+
23
+ def draw
24
+ fill_color '0000'
25
+ bounding_box at, width: width, height: height do
26
+ index = 0
27
+ slice = if label_count_width < labels.count
28
+ (labels.count.to_f / label_count_width.to_f).ceil
29
+ else
30
+ 1
31
+ end
32
+
33
+ labels.each_slice(slice) do |items|
34
+ offset = width_of(items.first) / 2
35
+ origin = [(@points[index] - offset).to_i,0]
36
+ point = [origin.first,-20]
37
+ rotate 20, origin: origin do
38
+ draw_text items.first, at: point
39
+ end
40
+ index += slice
41
+ end
42
+ end
43
+ end
44
+
45
+ def labels
46
+ @labels ||= series.map do |s|
47
+ s[:values].map do |v|
48
+ formatter.call(v[:key])
49
+ end.uniq
50
+ end.flatten.uniq
51
+ end
52
+
53
+ def max_label_width
54
+ @max_label_width ||= labels.map { |label| width_of(label) }.max
55
+ end
56
+
57
+
58
+ def label_count_width
59
+ (bounds.width / max_label_width).to_i
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,76 @@
1
+ module Prawn
2
+ module Charts
3
+ class YAxis
4
+ attr_reader :pdf
5
+ attr_accessor :points, :at, :width, :height, :formatter
6
+
7
+ extend Forwardable
8
+
9
+ def_delegators :@pdf, :bounding_box, :stroke_bounds, :text
10
+ def_delegators :@pdf, :height_of, :width_of, :fill_color
11
+ def_delegators :@pdf, :draw_text, :bounds
12
+
13
+ def initialize pdf, opts
14
+ @pdf = pdf
15
+ @at = opts[:at]
16
+ @width = opts[:width]
17
+ @height = opts[:height]
18
+ @points = opts[:points]
19
+ @formatter = opts[:formatter]
20
+ @percentage = opts[:percentage]
21
+ end
22
+
23
+ def draw
24
+ fill_color '0000'
25
+ bounding_box at, width: width, height: height do
26
+ last_point = nil
27
+ list.each do |item|
28
+ percent = ((item - points.min).to_f / axis_height.to_f)
29
+ y_point = (percent * bounds.height) - (text_height / 3).to_i
30
+ if y_point > (last_point || y_point - 1)
31
+ draw_text formatter.call(item), at: [0, y_point]
32
+ last_point = y_point + text_height
33
+ end
34
+ end
35
+ end
36
+ end
37
+
38
+
39
+ def text_height
40
+ @text_height ||= height_of(formatter.call( points.first))
41
+ end
42
+
43
+ def axis_height
44
+ points.max - points.min
45
+ end
46
+
47
+ def single_height
48
+ (bounds.height / text_height).to_i
49
+ end
50
+
51
+ def list
52
+ return percentage_list if percentage?
53
+ return @range if @range
54
+ @range =[]
55
+ exp = Math.log10(points.min).floor - 1
56
+ (points.min.to_i..points.max.to_i).each_slice( 10 ** exp) do |n|
57
+ @range.push n.first
58
+ end
59
+ @range
60
+ end
61
+
62
+ def percentage?
63
+ @percentage
64
+ end
65
+
66
+ def percentage_list
67
+ percentage_list = []
68
+ (0 .. 100).each_slice(10) do |n|
69
+ percentage_list.push n.first
70
+ end
71
+ percentage_list
72
+ end
73
+
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,133 @@
1
+ require "prawn/charts/version"
2
+ require "ostruct"
3
+ require 'i18n'
4
+ require File.dirname(__FILE__) + '/charts/error'
5
+ require File.dirname(__FILE__) + '/charts/error/no_series'
6
+ require File.dirname(__FILE__) + '/charts/base'
7
+ require File.dirname(__FILE__) + '/charts/bar'
8
+ require File.dirname(__FILE__) + '/charts/stacked_bar'
9
+ require File.dirname(__FILE__) + '/charts/line'
10
+ require File.dirname(__FILE__) + '/charts/combo'
11
+ require File.dirname(__FILE__) + '/charts/x_axis'
12
+ require File.dirname(__FILE__) + '/charts/y_axis'
13
+
14
+ I18n.load_path << File.dirname(__FILE__) + '/../config/locales/en.yml'
15
+ I18n.enforce_available_locales = false
16
+ module Prawn
17
+ class Document
18
+
19
+ # Draws a bar chart.
20
+ #
21
+ #
22
+ # @example
23
+ # 1 + 1 = 2
24
+ #
25
+ # @param [Hash] opts ({}) the data and all options
26
+ # @option opts [String] :title The Title on the Chart
27
+ # @option opts [Boolean] :legend show legend
28
+ # @option opts [Hash] :padding { top: bottom: left: right: }
29
+ # @option opts [String] :x name of the x axis
30
+ # @option opts [String] :y name of the y axis
31
+ # @option opts [String] :y1 name of the y1 axis
32
+ # @option opts [Array [x,y]] :at Top Left corner of bounding box
33
+ # @option opts [Fixnum] :width width of chart
34
+ # @option opts [Fixnum] :height height of chart
35
+ # @option opts [Hash] :series all the data
36
+ # @option opts [Proc] :key_formatter formatter for the X values
37
+ # @option opts [Proc] :value_formatter formatter for the Y values
38
+ #
39
+ # @yieldparam [Prawn::Charts::Bar#config]
40
+ #
41
+ # @return [Prawn::Charts::Bar]
42
+ def bar_chart opts={}, &block
43
+ chart = Prawn::Charts::Bar.new(self, opts)
44
+ yield chart.config if block_given?
45
+ chart.draw
46
+ end
47
+
48
+ # Draws a stacked bar chart
49
+ #
50
+ # @example
51
+ # 1 + 1 = 2
52
+ #
53
+ # @param [Hash] opts ({}) the data and all options
54
+ # @option opts [String] :title The Title on the Chart
55
+ # @option opts [Boolean] :legend show legend
56
+ # @option opts [Hash] :padding { top: bottom: left: right: }
57
+ # @option opts [String] :x name of the x axis
58
+ # @option opts [String] :y name of the y axis
59
+ # @option opts [String] :y1 name of the y1 axis
60
+ # @option opts [Array [x,y]] :at Top Left corner of bounding box
61
+ # @option opts [Fixnum] :width width of chart
62
+ # @option opts [Fixnum] :height height of chart
63
+ # @option opts [Hash] :series all the data
64
+ # @option opts [Proc] :key_formatter formatter for the X values
65
+ # @option opts [Proc] :value_formatter formatter for the Y values
66
+ #
67
+ # @yieldparam [Prawn::Charts::StackedBar#config]
68
+ #
69
+ # @return [Prawn::Charts::StackedBar]
70
+ def stacked_bar_chart opts={}, &block
71
+ chart = Prawn::Charts::StackedBar.new(self, opts)
72
+ yield chart.config if block_given?
73
+ chart.draw
74
+ end
75
+
76
+ # Draws a line chart
77
+ #
78
+ # @example
79
+ # 1 + 1 = 2
80
+ #
81
+ # @param [Hash] opts ({}) the data and all options
82
+ # @option opts [String] :title The Title on the Chart
83
+ # @option opts [Boolean] :legend show legend
84
+ # @option opts [Hash] :padding { top: bottom: left: right: }
85
+ # @option opts [String] :x name of the x axis
86
+ # @option opts [String] :y name of the y axis
87
+ # @option opts [String] :y1 name of the y1 axis
88
+ # @option opts [Array [x,y]] :at Top Left corner of bounding box
89
+ # @option opts [Fixnum] :width width of chart
90
+ # @option opts [Fixnum] :height height of chart
91
+ # @option opts [Hash] :series all the data
92
+ # @option opts [Proc] :key_formatter formatter for the X values
93
+ # @option opts [Proc] :value_formatter formatter for the Y values
94
+ #
95
+ # @yieldparam [Prawn::Charts::Line#config]
96
+ #
97
+ # @return [Prawn::Charts::Line]
98
+ def line_chart opts={}, &block
99
+ chart = Prawn::Charts::Line.new(self, opts)
100
+ yield chart.config if block_given?
101
+ chart.draw
102
+ end
103
+
104
+ # Draws a combo of other charts. Currently it will draw
105
+ # a combo of a bar chart and a line chart
106
+ #
107
+ # @example
108
+ # 1 + 1 = 2
109
+ #
110
+ # @param [Hash] opts ({}) the data and all options
111
+ # @option opts [String] :title The Title on the Chart
112
+ # @option opts [Boolean] :legend show legend
113
+ # @option opts [Hash] :padding { top: bottom: left: right: }
114
+ # @option opts [String] :x name of the x axis
115
+ # @option opts [String] :y name of the y axis
116
+ # @option opts [String] :y1 name of the y1 axis
117
+ # @option opts [Array [x,y]] :at Top Left corner of bounding box
118
+ # @option opts [Fixnum] :width width of chart
119
+ # @option opts [Fixnum] :height height of chart
120
+ # @option opts [Hash] :series all the data
121
+ # @option opts [Proc] :key_formatter formatter for the X values
122
+ # @option opts [Proc] :value_formatter formatter for the Y values
123
+ #
124
+ # @yieldparam [Prawn::Charts::Combo]
125
+ #
126
+ # @return [Prawn::Charts::Combo]
127
+ def combo_chart opts={}, &block
128
+ chart = Prawn::Charts::Combo.new(self, opts)
129
+ #yield chart if block_given?
130
+ chart.draw
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,29 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'prawn/charts/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "prawn-charts"
8
+ spec.version = Prawn::Charts::VERSION
9
+ spec.authors = ["Cajun"]
10
+ spec.email = ["zac@kleinpeter.org"]
11
+ spec.description = %q{WARNING: Alpha Software. Charting Lib for Prawn.
12
+ Supports bar, line, and combo charts. }
13
+ spec.summary = %q{Prawn Charting Lib}
14
+ spec.homepage = ""
15
+ spec.license = "MIT"
16
+
17
+ spec.files = `git ls-files`.split($/)
18
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
19
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
20
+ spec.require_paths = ["lib"]
21
+ spec.required_ruby_version = '>= 1.9.3'
22
+ spec.required_rubygems_version = ">= 1.3.6"
23
+
24
+ spec.add_dependency "prawn"
25
+ spec.add_dependency "i18n"
26
+ spec.add_development_dependency "bundler", "~> 1.3"
27
+ spec.add_development_dependency "rake"
28
+ spec.add_development_dependency "yard"
29
+ end
data/samples/simple.rb ADDED
@@ -0,0 +1,94 @@
1
+ require 'prawn'
2
+ require 'date'
3
+ require_relative '../lib/prawn/charts'
4
+
5
+ Prawn::Document.generate('chart.pdf') do
6
+ red = []
7
+ green = []
8
+ blue = []
9
+
10
+ 5.times do |i|
11
+ red.push( { key: i, value: rand(780) + 750 })
12
+ green.push( { key: i, value: rand(780) + 750 })
13
+ blue.push( { key: i, value: rand(780) + 750 })
14
+ end
15
+
16
+ opts = {
17
+ title: 'Bar',
18
+ at: [bounds.left + 20, bounds.top],
19
+ width: 500,
20
+ height: 200,
21
+ x: {title: 'X Axis', display: true},
22
+ y: {title: 'Y Axis', display: true},
23
+ key_formatter: lambda{|key| (Date.today >> key).strftime('%b %Y')},
24
+ value_formatter: lambda{|value| value.to_s},
25
+ series: [
26
+ {
27
+ name: 'Red',
28
+ color: 'FF00',
29
+ value_formatter: lambda{|value| value.to_s},
30
+ values: red
31
+ },
32
+ {
33
+ name: 'Green',
34
+ color: '0000',
35
+ value_formatter: lambda{|value| value.to_s},
36
+ values: green
37
+ },
38
+ {
39
+ name: 'Blue',
40
+ color: '1F1F',
41
+ value_formatter: lambda{|value| value.to_s},
42
+ values: blue
43
+ }
44
+ ]
45
+ }
46
+
47
+ bar_chart(opts) do |config|
48
+ config.title = 'Bar'
49
+ end
50
+
51
+ start_new_page
52
+ stacked_bar_chart(opts) do |config|
53
+ config.title = 'Stacked Bar'
54
+ end
55
+
56
+ start_new_page
57
+ line_chart(opts) do |config|
58
+ config.title = 'Line Chart'
59
+ end
60
+
61
+ start_new_page
62
+
63
+ line_opts = opts.merge({
64
+ title: nil,
65
+ x: {title: 'X Axis', display: false},
66
+ y: {title: 'Y Axis', display: false},
67
+ y1: {title: 'Y1 Axis', display: true},
68
+ series: [
69
+ {
70
+ name: 'Red',
71
+ color: 'FF00',
72
+ key_formatter: lambda{|key| 'Red ' * key},
73
+ value_formatter: lambda{|value| value.to_s},
74
+ values: red
75
+ },
76
+ ]
77
+ })
78
+
79
+ bar_opts = opts.merge({
80
+ title: 'Combo Chart',
81
+ x: {title: 'X Axis', display: true},
82
+ y: {title: 'Y Axis', display: true},
83
+ series: [
84
+ {
85
+ name: 'BLUE',
86
+ color: '12AA',
87
+ key_formatter: lambda{|key| key.to_s},
88
+ value_formatter: lambda{|value| value.to_s},
89
+ values: blue
90
+ },
91
+ ]
92
+ })
93
+ combo_chart(line_chart: line_opts, bar_chart: bar_opts)
94
+ end
data/spec/base_spec.rb ADDED
@@ -0,0 +1,56 @@
1
+ require_relative 'spec_helper'
2
+
3
+ describe Prawn::Charts::Base do
4
+ subject { Prawn::Charts::Base.new }
5
+
6
+ def defaults
7
+ {
8
+ padding: 10,
9
+ at: [0,0],
10
+ width: 500,
11
+ height: 200,
12
+ spacing: 10,
13
+ }
14
+ end
15
+
16
+ def override
17
+ {
18
+ padding: 2,
19
+ at: [10,10],
20
+ width: 50,
21
+ height: 3,
22
+ spacing: 2,
23
+ }
24
+ end
25
+
26
+ describe 'attributes' do
27
+ specify { subject.must_respond_to :title }
28
+ specify { subject.must_respond_to :legend }
29
+ specify { subject.must_respond_to :padding }
30
+ specify { subject.must_respond_to :x }
31
+ specify { subject.must_respond_to :y }
32
+ specify { subject.must_respond_to :y1 }
33
+ specify { subject.must_respond_to :at }
34
+ specify { subject.must_respond_to :width }
35
+ specify { subject.must_respond_to :height }
36
+ specify { subject.must_respond_to :spacing }
37
+ end
38
+
39
+ describe 'defaults' do
40
+ specify { subject.defaults.must_equal defaults }
41
+ specify { subject.padding.must_equal defaults[:padding] }
42
+ specify { subject.at.must_equal defaults[:at] }
43
+ specify { subject.width.must_equal defaults[:width] }
44
+ specify { subject.height.must_equal defaults[:height] }
45
+ specify { subject.spacing.must_equal defaults[:spacing] }
46
+
47
+ describe 'overriding defaults' do
48
+ subject { Prawn::Charts::Base.new(override) }
49
+ specify { subject.padding.must_equal 2 }
50
+ specify { subject.at.must_equal [10,10]}
51
+ specify { subject.width.must_equal 50}
52
+ specify { subject.height.must_equal 3}
53
+ specify { subject.spacing.must_equal 2}
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,7 @@
1
+ require_relative 'spec_helper'
2
+
3
+ describe Prawn::Document do
4
+ subject { Prawn::Document.new }
5
+
6
+ specify { subject.must_respond_to :bar_chart }
7
+ end
@@ -0,0 +1,4 @@
1
+ require 'minitest/autorun'
2
+ require 'minitest/pride'
3
+ require File.expand_path('../../lib/prawn/charts.rb', __FILE__)
4
+