prawn-graph 1.0.0.pre1 → 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +10 -0
- data/README.md +72 -36
- data/Rakefile +6 -0
- data/lib/prawn-graph.rb +6 -1
- data/lib/prawn/graph/calculations.rb +1 -0
- data/lib/prawn/graph/calculations/layout_calculator.rb +109 -0
- data/lib/prawn/graph/chart_components.rb +4 -0
- data/lib/prawn/graph/chart_components/bar_chart_renderer.rb +94 -0
- data/lib/prawn/graph/chart_components/canvas.rb +128 -0
- data/lib/prawn/graph/chart_components/line_chart_renderer.rb +92 -0
- data/lib/prawn/graph/chart_components/series_renderer.rb +120 -0
- data/lib/prawn/graph/extension.rb +16 -23
- data/lib/prawn/graph/series.rb +57 -8
- data/lib/prawn/graph/theme.rb +23 -6
- data/lib/prawn/graph/version.rb +1 -1
- data/prawn-graph.gemspec +8 -2
- metadata +44 -15
- data/lib/prawn/graph/charts.rb +0 -4
- data/lib/prawn/graph/charts/bar.rb +0 -18
- data/lib/prawn/graph/charts/base.rb +0 -69
- data/lib/prawn/graph/charts/legacy.rb +0 -4
- data/lib/prawn/graph/charts/legacy/bar.rb +0 -28
- data/lib/prawn/graph/charts/legacy/base.rb +0 -195
- data/lib/prawn/graph/charts/legacy/grid.rb +0 -51
- data/lib/prawn/graph/charts/legacy/line.rb +0 -39
- data/lib/prawn/graph/charts/line.rb +0 -18
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6390e1f2b56cd0e602ed9bad9f9e79d8bf8d24fd
|
4
|
+
data.tar.gz: 5a17ad2247533d9a61a26043521e26c7dd21f1f8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 18a28570e31af7e7d7dce328cfd1277d50e13c255d47afa015ef3a8fc0e06d76f64e43c726b0c5a67afb885ed2ede934d5f73e7f16d52d18ae8136fabcfbdf5e
|
7
|
+
data.tar.gz: 8dd7705229fa6450dd46f8a679560299a9feba69a7356f9c7f60e4326e9ba65eade94c5f77cb1d94679477de849cc48da21dbd2ff4355848ad9cb324c14dc8e9
|
data/.travis.yml
CHANGED
@@ -1,7 +1,17 @@
|
|
1
1
|
language: ruby
|
2
2
|
rvm:
|
3
3
|
- 2.0.0
|
4
|
+
- 2.1.0-p0
|
4
5
|
- 2.1.8
|
6
|
+
- 2.2.0-p0
|
5
7
|
- 2.2.4
|
6
8
|
- 2.3.0
|
9
|
+
- jruby-9.0.5.0
|
10
|
+
- rbx-3.19
|
11
|
+
matrix:
|
12
|
+
allow_failures:
|
13
|
+
- rvm: rbx-3.19
|
7
14
|
before_install: gem install bundler -v 1.11.2
|
15
|
+
addons:
|
16
|
+
code_climate:
|
17
|
+
repo_token: 9646aae52d1d1670d545c46bd94f5d86b92d516c5e3cb9890fcef62acc3a34b6
|
data/README.md
CHANGED
@@ -2,6 +2,16 @@
|
|
2
2
|
|
3
3
|
[![Gem Version](https://badge.fury.io/rb/prawn-graph.svg)](https://badge.fury.io/rb/prawn-graph)
|
4
4
|
[![License](http://img.shields.io/:license-mit-blue.svg)](http://sujrd.mit-license.org)
|
5
|
+
[![Code Climate](https://codeclimate.com/github/HHRy/prawn-graph/badges/gpa.svg)](https://codeclimate.com/github/HHRy/prawn-graph)
|
6
|
+
[![Test Coverage](https://codeclimate.com/github/HHRy/prawn-graph/badges/coverage.svg)](https://codeclimate.com/github/HHRy/prawn-graph/coverage)
|
7
|
+
[![Build Status](https://travis-ci.org/HHRy/prawn-graph.svg?branch=master)](https://travis-ci.org/HHRy/prawn-graph)
|
8
|
+
[![security](https://hakiri.io/github/HHRy/prawn-graph/master.svg)](https://hakiri.io/github/HHRy/prawn-graph/master)
|
9
|
+
![Maintained: yes](https://img.shields.io/badge/maintained-yes-brightgreen.png)
|
10
|
+
|
11
|
+
**This readme reflects the state of _master_ which is not the released version of prawn-graph.**
|
12
|
+
|
13
|
+
**The code being used to cut gems for release is the `stable` Branch, please make pull requests for**
|
14
|
+
**bug fixes from that branch.**
|
5
15
|
|
6
16
|
An extension for the [prawn pdf library][5] which adds the ability to draw graphs (or charts if
|
7
17
|
you perfer) in PDF documents.
|
@@ -13,42 +23,43 @@ are dramatic.
|
|
13
23
|
|
14
24
|
By default, graphs are drawn in monochrome, as that's likely how they will be printed.
|
15
25
|
|
26
|
+
This is free and open source software released under ther terms of the [MIT Licence](http://opensource.org/licenses/MIT).
|
27
|
+
|
28
|
+
Its copyright is held by Ryan Stenhouse and the [other contributors][8] and it was first released in
|
29
|
+
2010.
|
30
|
+
|
16
31
|
## Compatibility
|
17
32
|
|
18
|
-
|
33
|
+
This gem is built assuming a Ruby version of 2.0 or higher. Older Ruby versions may work but are not
|
19
34
|
officially supported. We aim for compatibilty with 1.x and 2.x series of prawn. Any incomaptibilities
|
20
|
-
should be treated as bugs and added to the [issue tracker][2].
|
21
|
-
|
22
|
-
Unlike previous version of prawn-graph, this version does not at this time include a theme api or the
|
23
|
-
ability to change the colors used to render the graph.
|
35
|
+
with prawn versions should be treated as bugs and added to the [issue tracker][2].
|
24
36
|
|
37
|
+
We build automatically using Travis CI. Our [.travis.yml][9] file targets the same Ruby versions as
|
38
|
+
[prawn itself][5] does.
|
25
39
|
|
26
|
-
## IMPORTANT - READ THIS BEFORE USING
|
27
40
|
|
28
|
-
|
29
|
-
test-driven bits of code to draw the various cool graphs and charts that people would like to use in
|
30
|
-
their PDFs.
|
41
|
+
### Removed features:
|
31
42
|
|
32
|
-
|
33
|
-
|
34
|
-
therefore are useful again.
|
43
|
+
Unlike previous versions of `prawn-graph`, this version does not at this time include a theme api or the
|
44
|
+
ability to change the colors used to render the graph.
|
35
45
|
|
36
|
-
|
37
|
-
[a good citizen][2] and [report them][2].
|
46
|
+
## Installation
|
38
47
|
|
39
|
-
To use prawn-graph
|
48
|
+
To use prawn-graph, you can add the following to your `Gemfile`:
|
40
49
|
|
41
50
|
```Gemfile
|
42
|
-
gem 'prawn-graph', '1.0
|
51
|
+
gem 'prawn-graph', ' ~> 1.0'
|
43
52
|
```
|
44
53
|
|
45
|
-
Alternatively, you can use Rubygems directly: `gem install prawn-graph
|
54
|
+
Alternatively, you can use Rubygems directly: `gem install prawn-graph`.
|
46
55
|
|
47
56
|
## Acknowledgements
|
48
57
|
|
49
58
|
With thanks to [株式会社アルム][3] ([Allm Inc][4]) for allowing Ryan Stenhouse the time to rebuild this version of
|
50
59
|
prawn-graph. This updated version of prawn-graph was inspired and guided by [prawn-svg][1] by [Roger Nesbitt][6].
|
51
|
-
|
60
|
+
|
61
|
+
Prawn Graph was originally sponsored by and built for use at [Purchasing Card Consultancy Ltd][7] while
|
62
|
+
Ryan Stenhouse was employed there.
|
52
63
|
|
53
64
|
## Supported graph / chart types
|
54
65
|
|
@@ -62,39 +73,64 @@ adventurous - please add it!
|
|
62
73
|
|
63
74
|
## Using prawn-graph
|
64
75
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
76
|
+
Graphs can be created by calling the `graph` or its alias, `chart` method with an array of
|
77
|
+
`Prawn::Graph::Series` objects representing the data you would like to plot and how it should
|
78
|
+
be displayed. It will also take a hash of options, and block which will have the graph yeilded
|
79
|
+
to it.
|
69
80
|
|
70
|
-
|
71
|
-
|
72
|
-
bar_graph data
|
73
|
-
end
|
81
|
+
```ruby
|
82
|
+
graph data, options = {}, &block.
|
74
83
|
```
|
75
84
|
|
76
85
|
When called with just a set of data, prawn-graph will do its best to make the graph fit in the
|
77
86
|
available space. For a little more control over how the graphs are rendered in the document
|
78
|
-
you can pass the following options
|
87
|
+
you can pass the following options to `graph` or `chart`:
|
79
88
|
|
80
89
|
Option | Data type | Description
|
81
90
|
----------- | --------- | -----------
|
82
91
|
:at | [integer, integer] | Specify the location on the page you want the graph to appear.
|
83
92
|
:width | integer | Desired width of the graph. Defaults to horizontal space available.
|
84
93
|
:height | integer | Desired height of the graph. Defaults to vertical space available.
|
94
|
+
:title | string | The overall title for your chart
|
95
|
+
:series_key | boolean | Should we render the series key for multi series graphs? Defaults to true.
|
96
|
+
|
97
|
+
The `data` passed to `graph` or `chart` should be an `Array` of `Prawn::Graph::Series` objects, which
|
98
|
+
themselves are made up of an array of data points to plot, and a series of options.
|
99
|
+
|
100
|
+
```ruby
|
101
|
+
Prawn::Graph::Series.new [1,2,3,4], options = {}
|
102
|
+
```
|
85
103
|
|
86
|
-
|
104
|
+
Valid `options` are:
|
105
|
+
|
106
|
+
Option | Data type | Description
|
107
|
+
------------- | --------- | -----------
|
108
|
+
:mark_average | boolean | Should we mark a line showing the average value of the series? Defaults to false.
|
109
|
+
:mark_minimum | boolean | Should we mark the minimum value of the series? Defaults to false.
|
110
|
+
:mark_maximum | boolean | Should we mark the maximum value of the series? Defaults to false.
|
111
|
+
:title | string | The title of this series, which will be shown in the series key.
|
112
|
+
:type | symbol | How this series should be rendered. Defaults to `:bar`, valid options are `:bar`, `:line`.
|
113
|
+
|
114
|
+
### Show me some code!
|
87
115
|
|
88
116
|
```ruby
|
89
117
|
require 'prawn-graph'
|
90
118
|
|
91
|
-
|
119
|
+
series = []
|
120
|
+
series << Prawn::Graph::Series.new([4,9,3,2,1,6,2,8,2,3,4,9,2], title: "A label for a series", type: :bar)
|
121
|
+
series << Prawn::Graph::Series.new([5,4,3,2,7,9,2,8,7,5,4,9,2], title: "Another label", type: :line, mark_average: true, mark_minimum: true)
|
122
|
+
series << Prawn::Graph::Series.new([1,2,3,4,5,9,6,4,5,6,3,2,11], title: "Yet another label", type: :bar)
|
123
|
+
series << Prawn::Graph::Series.new([1,2,3,4,5,12,6,4,5,6,3,2,9].shuffle, title: "One final label", type: :line, mark_average: true, mark_maximum: true)
|
124
|
+
|
125
|
+
xaxis_labels = ['0900', '1000', '1100', '1200', '1300', '1400', '1500', '1600', '1700', '1800', '1900', '2000', '2100']
|
92
126
|
|
93
127
|
Prawn::Document.generate('test.pdf') do
|
94
|
-
|
95
|
-
bar_graph data, at: [10,20], width: 200
|
128
|
+
graph series, width: 500, height: 200, title: "A Title for the chart", at: [10,700], xaxis_labels: xaxis_labels
|
96
129
|
end
|
97
|
-
```
|
130
|
+
```
|
131
|
+
|
132
|
+
### Output
|
133
|
+
<img src="http://prawn-graph.ryanstenhouse.jp/img/prawn-graph-output.png" alt="Prawn Graph Example Output" width="933" height="420">
|
98
134
|
|
99
135
|
## Development
|
100
136
|
|
@@ -111,9 +147,6 @@ Bug reports and pull requests are welcome [on GitHub][2]. This project is intend
|
|
111
147
|
safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org)
|
112
148
|
code of conduct.
|
113
149
|
|
114
|
-
## License
|
115
|
-
|
116
|
-
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
117
150
|
|
118
151
|
|
119
152
|
[1]: https://github.com/mogest/prawn-svg/
|
@@ -121,4 +154,7 @@ The gem is available as open source under the terms of the [MIT License](http://
|
|
121
154
|
[3]: http://www.allm.net/
|
122
155
|
[4]: http://www.allm.net/en/
|
123
156
|
[5]: http://github.com/prawnpdf/prawn/
|
124
|
-
[6]: https://github.com/mogest/
|
157
|
+
[6]: https://github.com/mogest/
|
158
|
+
[7]: http://www.pccl.co.uk/
|
159
|
+
[8]: https://github.com/HHRy/prawn-graph/blob/master/CONTRIBUTORS.md
|
160
|
+
[9]: https://github.com/HHRy/prawn-graph/blob/master/.travis.yml
|
data/Rakefile
CHANGED
data/lib/prawn-graph.rb
CHANGED
@@ -1,8 +1,13 @@
|
|
1
|
+
require "ostruct"
|
2
|
+
require "bigdecimal"
|
1
3
|
require "prawn"
|
2
4
|
require "prawn/graph/version"
|
5
|
+
require "prawn/graph/calculations"
|
6
|
+
|
7
|
+
require "prawn/graph/chart_components"
|
8
|
+
|
3
9
|
require "prawn/graph/theme"
|
4
10
|
require "prawn/graph/series"
|
5
|
-
require "prawn/graph/charts"
|
6
11
|
|
7
12
|
require "prawn/graph/extension"
|
8
13
|
|
@@ -0,0 +1 @@
|
|
1
|
+
require_relative "calculations/layout_calculator"
|
@@ -0,0 +1,109 @@
|
|
1
|
+
module Prawn
|
2
|
+
module Graph
|
3
|
+
module Calculations
|
4
|
+
|
5
|
+
class LayoutCalculator
|
6
|
+
attr_reader :bounds
|
7
|
+
attr_reader :series_key_area, :title_area, :graph_area, :canvas_width, :canvas_height
|
8
|
+
|
9
|
+
class Dimensions < OpenStruct
|
10
|
+
def renderable?
|
11
|
+
width > 0 && height > 0
|
12
|
+
end
|
13
|
+
|
14
|
+
def point
|
15
|
+
[x, y]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize(bounds, attributes = nil, theme = Prawn::Graph::Theme::Default)
|
20
|
+
@bounds = bounds
|
21
|
+
@graph_area = Dimensions.new({ width: 0, height: 0, x: 0, y: 0 })
|
22
|
+
@title_area = Dimensions.new({ width: 0, height: 0, x: 0, y: 0 })
|
23
|
+
@series_key_area = Dimensions.new({ width: 0, height: 0, x: 0, y: 0 })
|
24
|
+
@theme = theme
|
25
|
+
set_from_attributes(attributes) if attributes
|
26
|
+
end
|
27
|
+
|
28
|
+
def calculate
|
29
|
+
calculate_width_and_height_of_canvas
|
30
|
+
calculate_key_area
|
31
|
+
calculate_title_area
|
32
|
+
calculate_graph_area
|
33
|
+
|
34
|
+
self
|
35
|
+
end
|
36
|
+
|
37
|
+
def hpadding
|
38
|
+
((BigDecimal(canvas_width) / 100) * 2).round
|
39
|
+
end
|
40
|
+
|
41
|
+
def vpadding
|
42
|
+
((BigDecimal(canvas_height) / 100) * 2).round
|
43
|
+
end
|
44
|
+
|
45
|
+
def invalid?
|
46
|
+
canvas_width > bounds[0] || canvas_height > bounds[1]
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def set_from_attributes(attributes)
|
52
|
+
@canvas_width = BigDecimal(attributes[:width], 10) rescue 0
|
53
|
+
@canvas_height = BigDecimal(attributes[:height], 10) rescue 0
|
54
|
+
@num_series = attributes[:series_count] || 1
|
55
|
+
@title = attributes[:title]
|
56
|
+
@show_series_key = !attributes[:show_series_key].nil? ? attributes[:show_series_key] : true
|
57
|
+
end
|
58
|
+
|
59
|
+
def calculate_width_and_height_of_canvas
|
60
|
+
if @canvas_width.zero? && @canvas_height.zero?
|
61
|
+
@canvas_width = BigDecimal(bounds[0], 10)
|
62
|
+
@canvas_height = BigDecimal(bounds[1], 10)
|
63
|
+
elsif !@canvas_width.zero? && @canvas_height.zero?
|
64
|
+
@canvas_height = (@canvas_width / bounds_aspect_ratio).round
|
65
|
+
elsif !@canvas_height.zero? && @canvas_width.zero?
|
66
|
+
@canvas_width = (@canvas_height * bounds_aspect_ratio).round
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def bounds_aspect_ratio
|
71
|
+
BigDecimal(bounds[0], 10) / BigDecimal(bounds[1], 10)
|
72
|
+
end
|
73
|
+
|
74
|
+
def calculate_title_area
|
75
|
+
unless @title.nil?
|
76
|
+
@title_area[:width] = (canvas_width - @series_key_area[:width] - (2*hpadding))
|
77
|
+
@title_area[:x] = hpadding
|
78
|
+
@title_area[:height] = @theme.font_sizes.main_title + vpadding
|
79
|
+
@title_area[:y] = canvas_height + vpadding
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def calculate_key_area
|
84
|
+
if @num_series > 1 && @show_series_key
|
85
|
+
@series_key_area[:width] = ( (canvas_width / 100) * 25 ).round
|
86
|
+
@series_key_area[:x] = (canvas_width - @series_key_area[:width] - hpadding)
|
87
|
+
@series_key_area[:y] = canvas_height + vpadding
|
88
|
+
@series_key_area[:height] = (canvas_height - vpadding)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def calculate_graph_area
|
93
|
+
@graph_area[:width] = (canvas_width - @series_key_area[:width] - (2*hpadding))
|
94
|
+
@graph_area[:x] = hpadding
|
95
|
+
|
96
|
+
if !@title_area.renderable?
|
97
|
+
@graph_area[:y] = canvas_height + vpadding
|
98
|
+
@graph_area[:height] = (canvas_height - vpadding)
|
99
|
+
else
|
100
|
+
@graph_area[:y] = (@title_area[:y] - @title_area[:height])
|
101
|
+
@graph_area[:height] = (canvas_height - vpadding - @title_area[:height])
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
module Prawn
|
2
|
+
module Graph
|
3
|
+
module ChartComponents
|
4
|
+
# The Prawn::Graph::ChartComponents::BarChartRenderer is used to plot one or more bar charts
|
5
|
+
# in a sensible way on a a Prawn::Graph::ChartComponents::Canvas and its associated
|
6
|
+
# Prawn::Document.
|
7
|
+
#
|
8
|
+
class BarChartRenderer < SeriesRenderer
|
9
|
+
def render
|
10
|
+
render_the_chart
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def mark_average_line(series_index)
|
16
|
+
if @series[series_index].mark_average?
|
17
|
+
average_y_coordinate = (point_height_percentage(@series[series_index].avg) * @plot_area_height) - 5
|
18
|
+
prawn.line_width = 1
|
19
|
+
prawn.stroke_color = @color[series_index]
|
20
|
+
prawn.dash(2)
|
21
|
+
prawn.stroke_line([0, average_y_coordinate], [ @plot_area_width, average_y_coordinate ])
|
22
|
+
prawn.undash
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def mark_maximum_point(series_index, point, max_marked, x_position, y_position)
|
27
|
+
if @series[series_index].mark_maximum? && max_marked == false && @series[series_index].values[point] == @series[series_index].max
|
28
|
+
max_marked = draw_marker_point(@canvas.theme.max, x_position, y_position)
|
29
|
+
end
|
30
|
+
|
31
|
+
max_marked
|
32
|
+
end
|
33
|
+
|
34
|
+
def mark_minimum_point(series_index, point, min_marked, x_position, y_position)
|
35
|
+
if @series[series_index].mark_minimum? && min_marked == false && !@series[series_index].values[point].zero? && @series[series_index].values[point] == @series[series_index].min
|
36
|
+
min_marked = draw_marker_point(@canvas.theme.min, x_position, y_position)
|
37
|
+
end
|
38
|
+
|
39
|
+
min_marked
|
40
|
+
end
|
41
|
+
|
42
|
+
def render_the_chart
|
43
|
+
prawn.bounding_box [@graph_area.point[0] + 5, @graph_area.point[1] - 20], width: @plot_area_width, height: @plot_area_height do
|
44
|
+
|
45
|
+
prawn.save_graphics_state do
|
46
|
+
num_points = @series[0].size
|
47
|
+
width_per_point = (@plot_area_width / num_points)
|
48
|
+
width = (((width_per_point * 0.9) / @series.size).round(2)).to_f
|
49
|
+
min_marked = false
|
50
|
+
max_marked = false
|
51
|
+
|
52
|
+
num_points.times do |point|
|
53
|
+
|
54
|
+
@series.size.times do |series_index|
|
55
|
+
series_offset = series_index + 1
|
56
|
+
prawn.fill_color = @color[series_index]
|
57
|
+
prawn.stroke_color = @color[series_index]
|
58
|
+
prawn.line_width = width
|
59
|
+
|
60
|
+
starting = (prawn.bounds.left + (point * width_per_point))
|
61
|
+
|
62
|
+
x_position = ( (starting + (series_offset * width) ).to_f - (width / 2.0))
|
63
|
+
y_position = ((point_height_percentage(@series[series_index].values[point]) * @plot_area_height) - 5).to_f
|
64
|
+
|
65
|
+
prawn.fill_and_stroke_line([ x_position ,0], [x_position ,y_position]) unless @series[series_index].values[point].zero?
|
66
|
+
|
67
|
+
mark_average_line(series_index)
|
68
|
+
max_marked = mark_maximum_point(series_index, point, max_marked, x_position, y_position)
|
69
|
+
min_marked = mark_minimum_point(series_index, point, min_marked, x_position, y_position)
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
render_axes
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def max
|
80
|
+
@series.collect(&:max).max || 0
|
81
|
+
end
|
82
|
+
|
83
|
+
def min
|
84
|
+
@series.collect(&:min).min || 0
|
85
|
+
end
|
86
|
+
|
87
|
+
def avg
|
88
|
+
@series.collect(&:avg).inject(:+) / @series.size rescue 0
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|