prawn_charts 0.0.4 → 0.0.5
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.
- data/README.md +87 -17
- data/lib/prawn_charts.rb +1 -2
- data/lib/prawn_charts/core_extensions.rb +7 -0
- data/lib/prawn_charts/data_collector.rb +68 -0
- data/lib/prawn_charts/data_points.rb +16 -0
- data/lib/prawn_charts/examples/linear_example.rb +55 -0
- data/lib/prawn_charts/examples/log_example.rb +56 -133
- data/lib/prawn_charts/helpers.rb +26 -0
- data/lib/prawn_charts/linear_vertical_data_collector.rb +23 -0
- data/lib/prawn_charts/log_vertical_data_collector.rb +27 -0
- data/lib/prawn_charts/prawn_chart_renderer.rb +139 -0
- data/lib/prawn_charts/renderer_assistant.rb +146 -0
- data/lib/prawn_charts/version.rb +1 -1
- data/spec/data_collector_spec.rb +64 -0
- data/spec/linear_vertical_data_collector_spec.rb +17 -0
- data/spec/log_vertical_data_collector.rb +12 -0
- data/todo +5 -0
- metadata +18 -31
- data/lib/prawn_charts/data_collectors/container/container_data_collector.rb +0 -59
- data/lib/prawn_charts/data_collectors/container/graph_title_data_collector.rb +0 -15
- data/lib/prawn_charts/data_collectors/graph/horizontal_lines_data_collector.rb +0 -22
- data/lib/prawn_charts/data_collectors/graph/linear_y_pdf_data_collector.rb +0 -23
- data/lib/prawn_charts/data_collectors/graph/log_y_pdf_data_collector.rb +0 -21
- data/lib/prawn_charts/data_collectors/graph/pdf_data_collector.rb +0 -26
- data/lib/prawn_charts/data_collectors/graph/x_labels_data_collector.rb +0 -29
- data/lib/prawn_charts/data_collectors/graph/x_pdf_data_collector.rb +0 -16
- data/lib/prawn_charts/data_collectors/graph/y_labels_data_collector.rb +0 -35
- data/lib/prawn_charts/data_collectors/graph/y_pdf_data_collector.rb +0 -21
- data/lib/prawn_charts/examples/simple_linear_example.rb +0 -59
- data/lib/prawn_charts/examples/simple_log_example.rb +0 -59
- data/lib/prawn_charts/renderers/prawn_chart_renderer.rb +0 -31
- data/spec/data_collectors/container/container_data_collector_spec.rb +0 -59
- data/spec/data_collectors/graph/horizontal_lines_data_collector_spec.rb +0 -19
- data/spec/data_collectors/graph/linear_y_pdf_data_collector_spec.rb +0 -19
- data/spec/data_collectors/graph/log_y_pdf_data_collector_spec.rb +0 -26
- data/spec/data_collectors/graph/pdf_data_collector_spec.rb +0 -49
- data/spec/data_collectors/graph/x_labels_data_collector_spec.rb +0 -20
- data/spec/data_collectors/graph/x_pdf_data_collector_spec.rb +0 -23
- data/spec/data_collectors/graph/y_labels_data_collector_spec.rb +0 -21
@@ -0,0 +1,26 @@
|
|
1
|
+
module PrawnCharts
|
2
|
+
module Helpers
|
3
|
+
def stroke_axis(options={})
|
4
|
+
options = { :height => (cursor - 20).to_i,
|
5
|
+
:width => bounds.width.to_i
|
6
|
+
}.merge(options)
|
7
|
+
|
8
|
+
dash(1, :space => 4)
|
9
|
+
stroke_horizontal_line(-21, options[:width], :at => 0)
|
10
|
+
stroke_vertical_line(-21, options[:height], :at => 0)
|
11
|
+
undash
|
12
|
+
|
13
|
+
fill_circle [0, 0], 1
|
14
|
+
|
15
|
+
(100..options[:width]).step(100) do |point|
|
16
|
+
fill_circle [point, 0], 1
|
17
|
+
draw_text point, :at => [point-5, -10], :size => 7
|
18
|
+
end
|
19
|
+
|
20
|
+
(100..options[:height]).step(100) do |point|
|
21
|
+
fill_circle [0, point], 1
|
22
|
+
draw_text point, :at => [-17, point-2], :size => 7
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module PrawnCharts
|
2
|
+
class LinearVerticalDataCollector
|
3
|
+
attr_reader :height, :y_labels_units
|
4
|
+
def initialize(height, y_labels_units)
|
5
|
+
@height = height
|
6
|
+
@y_labels_units = y_labels_units
|
7
|
+
end
|
8
|
+
|
9
|
+
def convert_to_pdf(n)
|
10
|
+
pdf_points_per_y_unit * n
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def pdf_points_per_y_unit
|
16
|
+
height.to_f / total_y_units
|
17
|
+
end
|
18
|
+
|
19
|
+
def total_y_units
|
20
|
+
y_labels_units.max - y_labels_units.min
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module PrawnCharts
|
2
|
+
class LogVerticalDataCollector
|
3
|
+
attr_reader :height, :y_labels_units
|
4
|
+
def initialize(height, y_labels_units)
|
5
|
+
@height = height
|
6
|
+
@y_labels_units = y_labels_units
|
7
|
+
end
|
8
|
+
|
9
|
+
def convert_to_pdf(n)
|
10
|
+
Math.log10(n) * pdf_points_per_increment
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def number_of_labels
|
16
|
+
y_labels_units.length
|
17
|
+
end
|
18
|
+
|
19
|
+
def number_of_increments
|
20
|
+
number_of_labels - 1
|
21
|
+
end
|
22
|
+
|
23
|
+
def pdf_points_per_increment
|
24
|
+
height.to_f / number_of_increments
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,139 @@
|
|
1
|
+
module PrawnCharts
|
2
|
+
module PrawnChartRenderer
|
3
|
+
|
4
|
+
def draw_graph(input, colors = {})
|
5
|
+
@assistant = RendererAssistant.new(input)
|
6
|
+
@collector = @assistant.collector
|
7
|
+
@colors = colors
|
8
|
+
#stroke_axis
|
9
|
+
translate(*input[:graph][:starting_point]) do
|
10
|
+
draw_rectangle_with_color
|
11
|
+
fill_rectangle_with_color
|
12
|
+
draw_line_with_color
|
13
|
+
draw_dots_with_color
|
14
|
+
draw_x_labels_with_color
|
15
|
+
draw_y_labels_with_color
|
16
|
+
draw_horizontal_lines_with_color
|
17
|
+
draw_title_with_color
|
18
|
+
draw_x_title_with_color
|
19
|
+
draw_y_title_with_color
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def draw_line(graph_points)
|
24
|
+
graph_points.each_index do |i|
|
25
|
+
stroke_line(graph_points[i].coordinate, graph_points[i + 1].coordinate) unless graph_points[i + 1].nil?
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def draw_dots(graph_points, dot_radius)
|
30
|
+
graph_points.each do |point|
|
31
|
+
fill_circle(point.coordinate, dot_radius)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def draw_x_labels(data_points, offset, options)
|
36
|
+
label_width = options[:width]
|
37
|
+
centering_adjustment = label_width / 2
|
38
|
+
translate(0, offset) do
|
39
|
+
data_points.each do |data_point|
|
40
|
+
text_box(data_point.x_label, {at: [(data_point.x_pdf - centering_adjustment), 0]}.merge(options))
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def draw_y_labels(label_data, offset, options)
|
46
|
+
label_height = options[:height]
|
47
|
+
centering_adjustment = label_height / 2
|
48
|
+
translate(offset, 0) do
|
49
|
+
label_data.each do |data|
|
50
|
+
text_box(data[:label], {at: ([0, data[:y] + centering_adjustment]) }.merge(options))
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def draw_horizontal_lines(data)
|
56
|
+
data.each do |start_point, end_point|
|
57
|
+
stroke_line(start_point, end_point)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def draw_title(translation, title, options)
|
62
|
+
translate(*translation) do
|
63
|
+
text_box(title, options)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def set_color_and_execute(method, component, &block)
|
68
|
+
self.send(method, @colors[component]) if @colors[component]
|
69
|
+
yield if @assistant.components_to_draw.include?(component)
|
70
|
+
set_color_black(method)
|
71
|
+
end
|
72
|
+
|
73
|
+
def set_color_black(method)
|
74
|
+
black = "000000"
|
75
|
+
self.send(method, black)
|
76
|
+
end
|
77
|
+
|
78
|
+
def draw_rectangle_with_color
|
79
|
+
set_color_and_execute(:stroke_color, :rectangle_border) do
|
80
|
+
stroke_rectangle([0, @collector.height], @collector.width, @collector.height)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def fill_rectangle_with_color
|
85
|
+
set_color_and_execute(:fill_color, :rectangle_fill) do
|
86
|
+
fill_rectangle([0, @collector.height], @collector.width, @collector.height)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def draw_line_with_color
|
91
|
+
set_color_and_execute(:stroke_color, :line) do
|
92
|
+
draw_line(@collector.graph_points)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def draw_dots_with_color
|
97
|
+
set_color_and_execute(:fill_color, :dots) do
|
98
|
+
draw_dots(@collector.graph_points, @assistant.dot_radius)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def draw_x_labels_with_color
|
103
|
+
set_color_and_execute(:fill_color, :x_labels) do
|
104
|
+
draw_x_labels(@collector.data_points, @assistant.x_labels_offset, @assistant.x_labels_options)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def draw_y_labels_with_color
|
109
|
+
set_color_and_execute(:fill_color, :y_labels) do
|
110
|
+
draw_y_labels(@collector.y_labels, @assistant.y_labels_offset, @assistant.y_labels_options)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def draw_horizontal_lines_with_color
|
115
|
+
set_color_and_execute(:stroke_color, :horizontal_lines) do
|
116
|
+
draw_horizontal_lines(@collector.horizontal_lines)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def draw_title_with_color
|
121
|
+
set_color_and_execute(:fill_color, :graph_title) do
|
122
|
+
draw_title(@assistant.graph_title_translate, @assistant.graph_title, @assistant.graph_title_options)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def draw_x_title_with_color
|
127
|
+
set_color_and_execute(:fill_color, :x_title) do
|
128
|
+
draw_title(@assistant.x_title_translate, @assistant.x_title, @assistant.x_title_options)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def draw_y_title_with_color
|
133
|
+
set_color_and_execute(:fill_color, :y_title) do
|
134
|
+
draw_title(@assistant.y_title_translate, @assistant.y_title, @assistant.y_title_options)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
end
|
139
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
module PrawnCharts
|
2
|
+
class RendererAssistant
|
3
|
+
# Parses input data and responds with smart defaults or user specified data
|
4
|
+
attr_reader :input
|
5
|
+
def initialize(input)
|
6
|
+
@input = input
|
7
|
+
end
|
8
|
+
|
9
|
+
def collector
|
10
|
+
@collector ||= DataCollector.new(input.fetch(:graph))
|
11
|
+
end
|
12
|
+
|
13
|
+
def components_to_draw
|
14
|
+
available_components = [:rectangle_border, :rectangle_fill, :line, :dots, :x_labels, :y_labels, :horizontal_lines, :graph_title, :y_title, :x_title]
|
15
|
+
specified_components = [:rectangle_border, :rectangle_fill, :line] + @input.keys
|
16
|
+
@components_to_draw = available_components & specified_components
|
17
|
+
end
|
18
|
+
|
19
|
+
# dots
|
20
|
+
def dot_radius
|
21
|
+
return default_dot_radius unless input.dig(:dots, :radius)
|
22
|
+
input[:dots][:radius]
|
23
|
+
end
|
24
|
+
|
25
|
+
# graph_title
|
26
|
+
def graph_title_translate
|
27
|
+
[0, collector.height + offset(:graph_title, default_graph_title_offset)]
|
28
|
+
end
|
29
|
+
|
30
|
+
def graph_title
|
31
|
+
raise "Graph must have title" unless input.dig(:graph_title, :title)
|
32
|
+
input[:graph_title][:title]
|
33
|
+
end
|
34
|
+
|
35
|
+
def graph_title_options
|
36
|
+
return default_graph_title_options unless input.dig(:graph_title, :text_box_options)
|
37
|
+
input[:graph_title][:text_box_options]
|
38
|
+
end
|
39
|
+
|
40
|
+
# x_title
|
41
|
+
def x_title_translate
|
42
|
+
[0, offset(:x_title, default_x_title_offset)]
|
43
|
+
end
|
44
|
+
|
45
|
+
def x_title
|
46
|
+
raise "Graph must have title" unless input.dig(:x_title, :title)
|
47
|
+
input[:x_title][:title]
|
48
|
+
end
|
49
|
+
|
50
|
+
def x_title_options
|
51
|
+
return default_x_title_options unless input.dig(:x_title, :text_box_options)
|
52
|
+
input[:x_title][:text_box_options]
|
53
|
+
end
|
54
|
+
|
55
|
+
# y_title
|
56
|
+
def y_title_translate
|
57
|
+
[offset(:y_title, default_y_title_offset), 0]
|
58
|
+
end
|
59
|
+
|
60
|
+
def y_title
|
61
|
+
raise "Graph must have title" unless input.dig(:y_title, :title)
|
62
|
+
input[:y_title][:title]
|
63
|
+
end
|
64
|
+
|
65
|
+
def y_title_options
|
66
|
+
return default_y_title_options unless input.dig(:y_title, :text_box_options)
|
67
|
+
input[:y_title][:text_box_options]
|
68
|
+
end
|
69
|
+
|
70
|
+
# x_labels
|
71
|
+
def x_labels_offset
|
72
|
+
offset(:x_labels, default_x_labels_offset)
|
73
|
+
end
|
74
|
+
|
75
|
+
def x_labels_options
|
76
|
+
return default_x_labels_options unless input.dig(:x_labels, :text_box_options)
|
77
|
+
input[:x_labels][:text_box_options]
|
78
|
+
end
|
79
|
+
|
80
|
+
# y_labels
|
81
|
+
def y_labels_offset
|
82
|
+
offset(:y_labels, default_y_labels_offset)
|
83
|
+
end
|
84
|
+
|
85
|
+
def y_labels_options
|
86
|
+
return default_y_labels_options unless input.dig(:y_labels, :text_box_options)
|
87
|
+
default_y_labels_options.merge(input[:y_labels][:text_box_options])
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
|
92
|
+
def default_dot_radius
|
93
|
+
4
|
94
|
+
end
|
95
|
+
|
96
|
+
def offset(title_type, default_offset)
|
97
|
+
return default_offset unless input.dig(title_type, :offset)
|
98
|
+
input[title_type][:offset]
|
99
|
+
end
|
100
|
+
|
101
|
+
# graph title
|
102
|
+
def default_graph_title_options
|
103
|
+
{at: [0, -20], width: collector.width, height: 20, align: :center}
|
104
|
+
end
|
105
|
+
|
106
|
+
def default_graph_title_offset
|
107
|
+
50
|
108
|
+
end
|
109
|
+
|
110
|
+
# x title
|
111
|
+
def default_x_title_options
|
112
|
+
{at: [0, 20], width: collector.width, height: 20, align: :center}
|
113
|
+
end
|
114
|
+
|
115
|
+
def default_x_title_offset
|
116
|
+
-60
|
117
|
+
end
|
118
|
+
|
119
|
+
# y title
|
120
|
+
def default_y_title_options
|
121
|
+
{at: [0, 0], width: collector.height, height: 20, align: :center, valign: :center, rotate: 90}
|
122
|
+
end
|
123
|
+
|
124
|
+
def default_y_title_offset
|
125
|
+
-50
|
126
|
+
end
|
127
|
+
|
128
|
+
# x_labels
|
129
|
+
def default_x_labels_offset
|
130
|
+
-50
|
131
|
+
end
|
132
|
+
|
133
|
+
def default_x_labels_options
|
134
|
+
{ :width => 100, :height => 20, align: :center, rotate: 45 }
|
135
|
+
end
|
136
|
+
|
137
|
+
# y_labels
|
138
|
+
def default_y_labels_offset
|
139
|
+
-40
|
140
|
+
end
|
141
|
+
|
142
|
+
def default_y_labels_options
|
143
|
+
{ width: 40, :height => 40, valign: :center }
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
data/lib/prawn_charts/version.rb
CHANGED
@@ -0,0 +1,64 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
module PrawnCharts
|
4
|
+
describe DataCollector do
|
5
|
+
|
6
|
+
data = [
|
7
|
+
{x_label: "Apr-11", y: 10},
|
8
|
+
{x_label: "May-11", y: nil},
|
9
|
+
{x_label: "Jun-11", y: 30}
|
10
|
+
]
|
11
|
+
input = { :width => 300, :height => 200, :data => data, :y_labels => [0, 10, 20] }
|
12
|
+
let(:collector) { DataCollector.new(input) }
|
13
|
+
|
14
|
+
context "#y_labels" do
|
15
|
+
it "returns y_labels and associated y_pdf position" do
|
16
|
+
collector.stub(:y_convert_to_pdf).with(0) { "zero" }
|
17
|
+
collector.stub(:y_convert_to_pdf).with(10) { "ten" }
|
18
|
+
collector.stub(:y_convert_to_pdf).with(20) { "twenty" }
|
19
|
+
expected = [{:label=>"0", :y=>"zero"}, {:label=>"10", :y=>"ten"}, {:label=>"20", :y=>"twenty"}]
|
20
|
+
expect(collector.y_labels).to eq expected
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
context "#horizontal_lines" do
|
25
|
+
it "returns starting and ending points for graph lines at y_labels" do
|
26
|
+
collector.stub(:y_convert_to_pdf).with(0) { 10 }
|
27
|
+
collector.stub(:y_convert_to_pdf).with(10) { 20 }
|
28
|
+
collector.stub(:y_convert_to_pdf).with(20) { 30 }
|
29
|
+
expected = [[[0, 10], [300, 10]], [[0, 20], [300, 20]], [[0, 30], [300, 30]]]
|
30
|
+
expect(collector.horizontal_lines).to eq expected
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
context "#data_points" do
|
35
|
+
it "makes a DataPoint corresponding to every piece of data it is initialized with" do
|
36
|
+
expect(collector.data_points).to have(3).things
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
context "#graph_points" do
|
41
|
+
it "returns all DataPoints with a y_pdf value that is truthy" do
|
42
|
+
data_point_inputs = { x_units: 1, y_units: 2, x_pdf: 12, y_pdf: 16, x_label: "jan 12" }
|
43
|
+
dp_1 = DataPoint.new(data_point_inputs)
|
44
|
+
dp_2 = DataPoint.new(data_point_inputs)
|
45
|
+
dp_3 = DataPoint.new(data_point_inputs.merge({ y_pdf: nil }))
|
46
|
+
data_points = [dp_1, dp_2, dp_3]
|
47
|
+
collector.stub(:data_points) { data_points }
|
48
|
+
expect(collector.graph_points).to eq [dp_1, dp_2]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
context "#y_convert_to_pdf" do
|
53
|
+
it "converts linear y_unit to y_pdf" do
|
54
|
+
expect(collector.y_convert_to_pdf(8)).to eq 80
|
55
|
+
end
|
56
|
+
|
57
|
+
it "converts log y_unit to y_pdf" do
|
58
|
+
input = { :width => 300, :height => 200, :data => [], :y_labels => [0, 10, 100], :scale => :log }
|
59
|
+
collector = DataCollector.new(input)
|
60
|
+
expect(collector.y_convert_to_pdf(12)).to be_within(0.1).of 107.9
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
module PrawnCharts
|
4
|
+
describe LinearVerticalDataCollector do
|
5
|
+
context "#convert_to_pdf" do
|
6
|
+
it "converts a y_unit point to a y_pdf point" do
|
7
|
+
collector = LinearVerticalDataCollector.new(200, [0, 10, 20, 30, 40, 50])
|
8
|
+
expect(collector.convert_to_pdf(15)).to eq 60
|
9
|
+
end
|
10
|
+
|
11
|
+
it "accounts for linear scales that don't start at 0" do
|
12
|
+
collector = LinearVerticalDataCollector.new(200, [30, 40, 50])
|
13
|
+
expect(collector.convert_to_pdf(15)).to eq 150
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|