prawn-charts 0.0.1.alpha

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