charts 0.0.9
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.
- checksums.yaml +7 -0
- data/lib/charts/bar_chart/bar/bar.rb +34 -0
- data/lib/charts/bar_chart/bar/horizontal_bar.rb +33 -0
- data/lib/charts/bar_chart/bar/vertical_bar.rb +33 -0
- data/lib/charts/bar_chart/bar_chart.rb +130 -0
- data/lib/charts/bar_chart/grid/grid.rb +48 -0
- data/lib/charts/bar_chart/grid/grid_line.rb +45 -0
- data/lib/charts/bar_chart/grid/horizontal_grid_line.rb +29 -0
- data/lib/charts/bar_chart/grid/vertical_grid_line.rb +31 -0
- data/lib/charts/bin/dispatcher.rb +66 -0
- data/lib/charts/bin/opt_parser.rb +183 -0
- data/lib/charts/chart.rb +105 -0
- data/lib/charts/count_chart/circle_count_chart.rb +9 -0
- data/lib/charts/count_chart/count_chart.rb +83 -0
- data/lib/charts/count_chart/cross_count_chart.rb +13 -0
- data/lib/charts/count_chart/manikin_count_chart.rb +47 -0
- data/lib/charts/count_chart/symbol_count_chart.rb +10 -0
- data/lib/charts/legend.rb +31 -0
- data/lib/charts/pie_chart/pie_chart.rb +78 -0
- data/lib/charts/renderer/renderer.rb +40 -0
- data/lib/charts/renderer/rvg_renderer.rb +46 -0
- data/lib/charts/renderer/svg_renderer.rb +38 -0
- data/lib/charts.rb +29 -0
- metadata +95 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 4da2755eb0417c5b0c4745ba3c48731b7885b976
|
4
|
+
data.tar.gz: aa2f198109cf7b2778a0e96a373d26f28747c613
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: aeb9f5516f4c5f4636158833639d369a1ab67c9b2b1a7a5beda73d13512a6fb78c981372c608ac13abf11171c51f22b0892a3caf6db58bece66418bce3111ae8
|
7
|
+
data.tar.gz: c7500f8d6819e229ef05890ace09bc27b66d597afd71bae33bd83adab7ff4b2a692e54dda9fdf18850ca4bbd5b9fa9b6f4a10117308d2c5e9c2c301962ee61db
|
@@ -0,0 +1,34 @@
|
|
1
|
+
class Charts::BarChart::Bar
|
2
|
+
attr_reader :graph, :data_value, :set_nr, :bar_nr_in_set
|
3
|
+
|
4
|
+
def initialize(graph, data_value, set_nr, bar_nr_in_set)
|
5
|
+
@graph = graph
|
6
|
+
@data_value = data_value
|
7
|
+
@set_nr = set_nr
|
8
|
+
@bar_nr_in_set = bar_nr_in_set
|
9
|
+
end
|
10
|
+
|
11
|
+
def draw
|
12
|
+
graph.renderer.rect x, y, width, height, fill: graph.colors[set_nr], class: 'bar'
|
13
|
+
end
|
14
|
+
|
15
|
+
def x
|
16
|
+
raise NotImplementedError
|
17
|
+
end
|
18
|
+
|
19
|
+
def y
|
20
|
+
raise NotImplementedError
|
21
|
+
end
|
22
|
+
|
23
|
+
def width
|
24
|
+
raise NotImplementedError
|
25
|
+
end
|
26
|
+
|
27
|
+
def height
|
28
|
+
raise NotImplementedError
|
29
|
+
end
|
30
|
+
|
31
|
+
def bar_number_in_graph
|
32
|
+
set_nr + bar_nr_in_set * graph.set_count
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
class Charts::BarChart::HorizontalBar < Charts::BarChart::Bar
|
2
|
+
def x
|
3
|
+
x_margin + x_offset
|
4
|
+
end
|
5
|
+
|
6
|
+
def x_margin
|
7
|
+
graph.outer_margin
|
8
|
+
end
|
9
|
+
|
10
|
+
def x_offset
|
11
|
+
graph.inner_width * [data_value, graph.base_line].min
|
12
|
+
end
|
13
|
+
|
14
|
+
def y
|
15
|
+
(y_margin + y_offset).floor.to_i
|
16
|
+
end
|
17
|
+
|
18
|
+
def y_margin
|
19
|
+
graph.outer_margin + graph.bar_margin + graph.group_margin * bar_nr_in_set
|
20
|
+
end
|
21
|
+
|
22
|
+
def y_offset
|
23
|
+
graph.bar_outer_width * bar_number_in_graph
|
24
|
+
end
|
25
|
+
|
26
|
+
def width
|
27
|
+
graph.inner_width * (data_value - graph.base_line).abs
|
28
|
+
end
|
29
|
+
|
30
|
+
def height
|
31
|
+
graph.bar_inner_width.floor.to_i
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
class Charts::BarChart::VerticalBar < Charts::BarChart::Bar
|
2
|
+
def x
|
3
|
+
(x_margin + x_offset).floor.to_i
|
4
|
+
end
|
5
|
+
|
6
|
+
def x_margin
|
7
|
+
graph.outer_margin + graph.bar_margin + graph.group_margin * bar_nr_in_set
|
8
|
+
end
|
9
|
+
|
10
|
+
def x_offset
|
11
|
+
graph.bar_outer_width * bar_number_in_graph
|
12
|
+
end
|
13
|
+
|
14
|
+
def y
|
15
|
+
y_margin + y_offset
|
16
|
+
end
|
17
|
+
|
18
|
+
def y_margin
|
19
|
+
graph.outer_margin
|
20
|
+
end
|
21
|
+
|
22
|
+
def y_offset
|
23
|
+
graph.inner_height * [(1 - data_value), (1 - graph.base_line)].min
|
24
|
+
end
|
25
|
+
|
26
|
+
def width
|
27
|
+
graph.bar_inner_width.floor.to_i
|
28
|
+
end
|
29
|
+
|
30
|
+
def height
|
31
|
+
graph.inner_height * (data_value - graph.base_line).abs
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
class Charts::BarChart < Charts::Chart
|
2
|
+
include Charts::Grid
|
3
|
+
include Charts::Legend
|
4
|
+
|
5
|
+
attr_reader :max_value,
|
6
|
+
:min_value,
|
7
|
+
:set_count,
|
8
|
+
:group_count,
|
9
|
+
:total_bar_count,
|
10
|
+
:base_line
|
11
|
+
|
12
|
+
def initialize_instance_variables
|
13
|
+
@set_count = data.count
|
14
|
+
@group_count = data.map(&:count).max
|
15
|
+
@total_bar_count = set_count * group_count
|
16
|
+
@max_value = calc_max
|
17
|
+
@min_value = calc_min
|
18
|
+
@base_line = calc_base_line
|
19
|
+
end
|
20
|
+
|
21
|
+
def default_options
|
22
|
+
super.merge(
|
23
|
+
width: 600,
|
24
|
+
height: 400,
|
25
|
+
include_zero: true,
|
26
|
+
group_margin: 20,
|
27
|
+
bar_margin: 3,
|
28
|
+
label_height: 20,
|
29
|
+
label_margin: 10,
|
30
|
+
direction: :vertical
|
31
|
+
)
|
32
|
+
end
|
33
|
+
|
34
|
+
def validate_arguments(data, options)
|
35
|
+
super(data, options)
|
36
|
+
raise ArgumentError unless data.is_a? Array
|
37
|
+
end
|
38
|
+
|
39
|
+
def prepare_data
|
40
|
+
data.map do |set|
|
41
|
+
set.map do |value|
|
42
|
+
value.nil? ? nil : normalize(value)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def pre_draw
|
48
|
+
super
|
49
|
+
draw_grid
|
50
|
+
draw_group_labels
|
51
|
+
draw_labels
|
52
|
+
end
|
53
|
+
|
54
|
+
def draw_group_labels
|
55
|
+
return if options[:group_labels].nil? || group_labels.empty?
|
56
|
+
raise ArgumentError if group_labels.count != group_count
|
57
|
+
group_label_style = {
|
58
|
+
text_anchor: 'middle',
|
59
|
+
writing_mode: (vertical? ? 'lr' : 'tb'),
|
60
|
+
class: 'group_label'
|
61
|
+
}
|
62
|
+
group_labels.each_with_index do |group_label, i|
|
63
|
+
if vertical?
|
64
|
+
x = outer_margin + (i + 0.5) * all_bars_width / group_count + i * group_margin
|
65
|
+
y = outer_margin + inner_height + renderer.font_size
|
66
|
+
else
|
67
|
+
x = outer_margin - renderer.font_size
|
68
|
+
y = outer_margin + (i + 0.5) * all_bars_width / group_count + i * group_margin
|
69
|
+
end
|
70
|
+
renderer.text group_label, x, y, group_label_style
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def draw
|
75
|
+
prepared_data.each_with_index do |set, set_nr|
|
76
|
+
set.each_with_index do |data_value, bar_nr_in_set|
|
77
|
+
bar_class = vertical? ? VerticalBar : HorizontalBar
|
78
|
+
bar_class.new(self, data_value, set_nr, bar_nr_in_set).draw unless data_value.nil?
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def inner_width
|
84
|
+
width - 2 * outer_margin
|
85
|
+
end
|
86
|
+
|
87
|
+
def bar_inner_width
|
88
|
+
bar_outer_width - 2 * bar_margin
|
89
|
+
end
|
90
|
+
|
91
|
+
def bar_outer_width
|
92
|
+
all_bars_width.to_f / total_bar_count
|
93
|
+
end
|
94
|
+
|
95
|
+
def all_bars_width
|
96
|
+
(vertical? ? inner_width : inner_height) - sum_of_group_margins
|
97
|
+
end
|
98
|
+
|
99
|
+
def sum_of_group_margins
|
100
|
+
(group_count - 1) * group_margin
|
101
|
+
end
|
102
|
+
|
103
|
+
def inner_height
|
104
|
+
height - 2 * outer_margin - label_total_height
|
105
|
+
end
|
106
|
+
|
107
|
+
def calc_max
|
108
|
+
max = data.map{ |d| d.reject(&:nil?).max }.max
|
109
|
+
max = 0 if max < 0 && include_zero
|
110
|
+
options[:max] || max
|
111
|
+
end
|
112
|
+
|
113
|
+
def calc_min
|
114
|
+
min = data.map{ |d| d.reject(&:nil?).min }.min
|
115
|
+
min = 0 if min > 0 && include_zero
|
116
|
+
options[:min] || min
|
117
|
+
end
|
118
|
+
|
119
|
+
def calc_base_line
|
120
|
+
[[normalize(0), 0].max, 1].min # zero value normalized and clamped between 0 and 1
|
121
|
+
end
|
122
|
+
|
123
|
+
def normalize(value)
|
124
|
+
(value.to_f - min_value) / (max_value - min_value)
|
125
|
+
end
|
126
|
+
|
127
|
+
def vertical?
|
128
|
+
direction == :vertical
|
129
|
+
end
|
130
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Charts::Grid
|
2
|
+
def draw_grid
|
3
|
+
lines.each { |l| l.draw }
|
4
|
+
end
|
5
|
+
|
6
|
+
def lines
|
7
|
+
grid_line_class = vertical? ? HorizontalGridLine : VerticalGridLine
|
8
|
+
grid_line_values.map { |v| grid_line_class.new(self, v) }
|
9
|
+
end
|
10
|
+
|
11
|
+
def grid_line_values
|
12
|
+
(0..number_of_grid_lines).map do |i|
|
13
|
+
value = i.to_f * rounded_spread / number_of_grid_lines + min_value
|
14
|
+
if spread_log10 < 1
|
15
|
+
value.round(-spread_order_of_magnitude + 1)
|
16
|
+
else
|
17
|
+
value.round
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def number_of_grid_lines
|
23
|
+
(3..7).find { |line_count| spread_factor % line_count == 0 } || 4
|
24
|
+
end
|
25
|
+
|
26
|
+
def spread
|
27
|
+
max_value - min_value
|
28
|
+
end
|
29
|
+
|
30
|
+
def spread_order_of_magnitude
|
31
|
+
spread_log10.floor
|
32
|
+
end
|
33
|
+
|
34
|
+
def spread_log10
|
35
|
+
Math.log10(spread)
|
36
|
+
end
|
37
|
+
|
38
|
+
def rounded_spread
|
39
|
+
rs = spread_factor * 10 ** spread_order_of_magnitude
|
40
|
+
spread_log10 % 1 == 0 ? rs / 10 : rs
|
41
|
+
end
|
42
|
+
|
43
|
+
def spread_factor
|
44
|
+
f = (spread.to_f / 10 ** spread_order_of_magnitude).floor
|
45
|
+
spread_log10 % 1 == 0 ? 10 * f : f
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
class Charts::Grid::GridLine
|
2
|
+
attr_accessor :graph, :value
|
3
|
+
|
4
|
+
def initialize(graph, value)
|
5
|
+
@graph = graph
|
6
|
+
@value = value
|
7
|
+
end
|
8
|
+
|
9
|
+
def draw
|
10
|
+
graph.renderer.line x1, y1, x2, y2, graph.renderer.grid_line_style
|
11
|
+
graph.renderer.text label_text, label_x, label_y, label_style
|
12
|
+
end
|
13
|
+
|
14
|
+
def x1
|
15
|
+
raise NotImplementedError
|
16
|
+
end
|
17
|
+
|
18
|
+
def x2
|
19
|
+
raise NotImplementedError
|
20
|
+
end
|
21
|
+
|
22
|
+
def y1
|
23
|
+
raise NotImplementedError
|
24
|
+
end
|
25
|
+
|
26
|
+
def y2
|
27
|
+
raise NotImplementedError
|
28
|
+
end
|
29
|
+
|
30
|
+
def label_x
|
31
|
+
raise NotImplementedError
|
32
|
+
end
|
33
|
+
|
34
|
+
def label_y
|
35
|
+
raise NotImplementedError
|
36
|
+
end
|
37
|
+
|
38
|
+
def label_text
|
39
|
+
if graph.spread_order_of_magnitude <= 0
|
40
|
+
value.to_f
|
41
|
+
else
|
42
|
+
value
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
class Charts::Grid::HorizontalGridLine < Charts::Grid::GridLine
|
2
|
+
def x1
|
3
|
+
graph.outer_margin
|
4
|
+
end
|
5
|
+
|
6
|
+
def x2
|
7
|
+
graph.width - graph.outer_margin
|
8
|
+
end
|
9
|
+
|
10
|
+
def y1
|
11
|
+
graph.outer_margin + graph.inner_height * (1 - graph.normalize(value))
|
12
|
+
end
|
13
|
+
|
14
|
+
def y2
|
15
|
+
y1
|
16
|
+
end
|
17
|
+
|
18
|
+
def label_x
|
19
|
+
x1 - 5
|
20
|
+
end
|
21
|
+
|
22
|
+
def label_y
|
23
|
+
y1 + graph.renderer.font_size / 3
|
24
|
+
end
|
25
|
+
|
26
|
+
def label_style
|
27
|
+
{ text_anchor: 'end' }
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
class Charts::Grid::VerticalGridLine < Charts::Grid::GridLine
|
2
|
+
def y1
|
3
|
+
graph.outer_margin
|
4
|
+
end
|
5
|
+
|
6
|
+
def y2
|
7
|
+
graph.outer_margin + graph.inner_height
|
8
|
+
end
|
9
|
+
|
10
|
+
def x1
|
11
|
+
graph.outer_margin + graph.inner_width * graph.normalize(value)
|
12
|
+
end
|
13
|
+
|
14
|
+
def x2
|
15
|
+
x1
|
16
|
+
end
|
17
|
+
|
18
|
+
def label_y
|
19
|
+
y2 + graph.renderer.font_size + 5
|
20
|
+
end
|
21
|
+
|
22
|
+
def label_x
|
23
|
+
x1
|
24
|
+
end
|
25
|
+
|
26
|
+
def label_style
|
27
|
+
{
|
28
|
+
text_anchor: 'middle'
|
29
|
+
}
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module Charts
|
2
|
+
class Dispatcher
|
3
|
+
attr_reader :options
|
4
|
+
|
5
|
+
def initialize(options)
|
6
|
+
@options = options
|
7
|
+
end
|
8
|
+
|
9
|
+
def render
|
10
|
+
if options[:filename]
|
11
|
+
graph.render
|
12
|
+
else
|
13
|
+
puts graph.render
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def graph
|
18
|
+
if type == :txt
|
19
|
+
SymbolCountChart.new(data, graph_options)
|
20
|
+
elsif [:svg, :png, :jpg, :gif].include? type
|
21
|
+
if style == :circle
|
22
|
+
CircleCountChart.new(data, graph_options)
|
23
|
+
elsif style == :cross
|
24
|
+
CrossCountChart.new(data, graph_options)
|
25
|
+
elsif style == :manikin
|
26
|
+
ManikinCountChart.new(data, graph_options)
|
27
|
+
elsif style == :bar
|
28
|
+
BarChart.new(data, graph_options)
|
29
|
+
elsif style == :pie
|
30
|
+
PieChart.new(data, graph_options)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def data
|
36
|
+
options[:data]
|
37
|
+
end
|
38
|
+
|
39
|
+
def graph_options
|
40
|
+
options.select do |key, _value|
|
41
|
+
[
|
42
|
+
:background_color,
|
43
|
+
:colors,
|
44
|
+
:columns,
|
45
|
+
:filename,
|
46
|
+
:group_labels,
|
47
|
+
:height,
|
48
|
+
:item_height,
|
49
|
+
:item_width,
|
50
|
+
:labels,
|
51
|
+
:title,
|
52
|
+
:type,
|
53
|
+
:width
|
54
|
+
].include? key
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def type
|
59
|
+
options[:type].to_sym
|
60
|
+
end
|
61
|
+
|
62
|
+
def style
|
63
|
+
options[:style].to_sym
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,183 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
|
3
|
+
class Charts::OptParser
|
4
|
+
attr_reader :args, :options, :parser
|
5
|
+
|
6
|
+
FORMATS = [:txt, :svg, :png, :jpg, :gif].freeze
|
7
|
+
STYLES = [:circle, :cross, :manikin, :bar, :pie].freeze
|
8
|
+
DATA_EXAMPLE_ARGS = '-d 8,7'.freeze
|
9
|
+
COLOR_EXAMPLE_ARGS = '--colors red,gold'.freeze
|
10
|
+
FLOAT_INTEGER_REGEX = /^(?=.)([+-]?([0-9]*)(\.([0-9]+))?)$/.freeze
|
11
|
+
|
12
|
+
def initialize(args)
|
13
|
+
@args = args.empty? ? ['--help'] : args
|
14
|
+
# The options specified on the command line will be collected in *options*.
|
15
|
+
# We set default values here.
|
16
|
+
@options = {
|
17
|
+
type: :svg,
|
18
|
+
style: :circle
|
19
|
+
}
|
20
|
+
@parser = opt_parser
|
21
|
+
end
|
22
|
+
|
23
|
+
# Return a hash describing the options.
|
24
|
+
def parse
|
25
|
+
parser.parse!(args) # this sets the options
|
26
|
+
post_process_options
|
27
|
+
infer_type_from_filename
|
28
|
+
validate_options
|
29
|
+
|
30
|
+
options
|
31
|
+
end
|
32
|
+
|
33
|
+
def post_process_options
|
34
|
+
# force 2-dimensional array for bar graph if only one data set is provided
|
35
|
+
if options[:style] == :bar and !options[:data].first.is_a? Array
|
36
|
+
options[:data] = [options[:data]]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def infer_type_from_filename
|
41
|
+
return if options[:help]
|
42
|
+
return unless options[:filename]
|
43
|
+
type = options[:filename].match(/.*\.(#{FORMATS.join("|")})/)
|
44
|
+
if type
|
45
|
+
options[:type] = type[1].to_sym
|
46
|
+
else
|
47
|
+
options[:type] = false
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def validate_options
|
52
|
+
return if options[:help]
|
53
|
+
unless options[:type]
|
54
|
+
raise 'No type provided. Set a type with --type flag or by setting a valid --filename'
|
55
|
+
end
|
56
|
+
unless options[:data]
|
57
|
+
raise "No data provided. Please pass in data using the --data flag: 'bin/charts #{DATA_EXAMPLE_ARGS}'"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def opt_parser
|
62
|
+
OptionParser.new do |opts|
|
63
|
+
opts.banner = 'Usage: bin/charts [options]'
|
64
|
+
opts.on(
|
65
|
+
'-d DATA',
|
66
|
+
'--data DATA',
|
67
|
+
Array,
|
68
|
+
"Provide multiple data points, ie: 'bin/charts #{DATA_EXAMPLE_ARGS}'"
|
69
|
+
) do |data|
|
70
|
+
data = data.map { |d| d.match(FLOAT_INTEGER_REGEX) ? Float(d) : nil }
|
71
|
+
# if multiple --data arguments are provided,
|
72
|
+
# the data option becomes a two dimensional array
|
73
|
+
if !options[:data]
|
74
|
+
options[:data] = data
|
75
|
+
else
|
76
|
+
options[:data] = [options[:data]] unless options[:data].first.is_a? Array
|
77
|
+
options[:data].push(data)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
opts.on(
|
81
|
+
'-f FILENAME',
|
82
|
+
'--filename FILENAME',
|
83
|
+
"Set the filename the result is stored in. Supported formats are: : #{FORMATS.join(', ')}"
|
84
|
+
) do |filename|
|
85
|
+
options[:filename] = filename
|
86
|
+
end
|
87
|
+
opts.on(
|
88
|
+
'-s STYLE',
|
89
|
+
'--style STYLE',
|
90
|
+
STYLES,
|
91
|
+
"Choose the graph style: #{STYLES.join(', ')} (circle, cross and manikin are count graphs)"
|
92
|
+
) do |style|
|
93
|
+
options[:style] = style
|
94
|
+
end
|
95
|
+
opts.on(
|
96
|
+
'-t TITLE',
|
97
|
+
'--title TITLE',
|
98
|
+
"Set the title"
|
99
|
+
) do |title|
|
100
|
+
options[:title] = title
|
101
|
+
end
|
102
|
+
opts.on(
|
103
|
+
'--labels LABELS',
|
104
|
+
Array,
|
105
|
+
"Set the labels to be used, ie: 'bin/charts #{DATA_EXAMPLE_ARGS} --labels Failures,Successes' "
|
106
|
+
) do |labels|
|
107
|
+
options[:labels] = labels
|
108
|
+
end
|
109
|
+
opts.on(
|
110
|
+
'--group-labels GROUP_LABELS',
|
111
|
+
Array,
|
112
|
+
"Set the group-labels to be used, ie: 'bin/charts #{DATA_EXAMPLE_ARGS} --style bar --group-labels Summer,Winter' "
|
113
|
+
) do |group_labels|
|
114
|
+
options[:group_labels] = group_labels
|
115
|
+
end
|
116
|
+
opts.on(
|
117
|
+
'--colors COLORS',
|
118
|
+
Array,
|
119
|
+
"Set the colors to be used, ie: 'bin/charts #{DATA_EXAMPLE_ARGS} #{COLOR_EXAMPLE_ARGS}' "
|
120
|
+
) do |colors|
|
121
|
+
options[:colors] = colors
|
122
|
+
end
|
123
|
+
opts.on(
|
124
|
+
'--columns COLUMNS',
|
125
|
+
Integer,
|
126
|
+
'Set number of columns'
|
127
|
+
) do |columns|
|
128
|
+
options[:columns] = columns
|
129
|
+
end
|
130
|
+
opts.on(
|
131
|
+
'-w WIDTH',
|
132
|
+
'--width WIDTH (not for count graphs)',
|
133
|
+
Integer,
|
134
|
+
'Sets the image width'
|
135
|
+
) do |width|
|
136
|
+
options[:width] = width
|
137
|
+
end
|
138
|
+
opts.on(
|
139
|
+
'-h HEIGHT',
|
140
|
+
'--height HEIGHT (not for count graphs)',
|
141
|
+
Integer,
|
142
|
+
'Sets the image height'
|
143
|
+
) do |height|
|
144
|
+
options[:height] = height
|
145
|
+
end
|
146
|
+
opts.on(
|
147
|
+
'--item-width WIDTH',
|
148
|
+
Integer,
|
149
|
+
'Sets the width of the individual item (count graphs only)'
|
150
|
+
) do |item_width|
|
151
|
+
options[:item_width] = item_width
|
152
|
+
end
|
153
|
+
opts.on(
|
154
|
+
'--item-height HEIGHT',
|
155
|
+
Integer,
|
156
|
+
'Sets the height of the individual item (count graphs only)'
|
157
|
+
) do |item_height|
|
158
|
+
options[:item_height] = item_height
|
159
|
+
end
|
160
|
+
opts.on(
|
161
|
+
'--type TYPE',
|
162
|
+
FORMATS,
|
163
|
+
"If no filename is provided, output is sent to STDOUT, choose the format: #{FORMATS.join(', ')}"
|
164
|
+
) do |type|
|
165
|
+
options[:type] = type
|
166
|
+
end
|
167
|
+
opts.on(
|
168
|
+
'--help',
|
169
|
+
'Prints this help'
|
170
|
+
) do
|
171
|
+
puts opts
|
172
|
+
options[:help] = true
|
173
|
+
end
|
174
|
+
opts.on(
|
175
|
+
'--background_color BACKGROUNDCOLOR',
|
176
|
+
Array,
|
177
|
+
"Set the backgroundcolor to be used, ie: 'bin/charts #{DATA_EXAMPLE_ARGS} --background_color Silver"
|
178
|
+
) do |background_color|
|
179
|
+
options[:background_color] = background_color
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
data/lib/charts/chart.rb
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
class Charts::Chart
|
2
|
+
attr_reader :data,
|
3
|
+
:options,
|
4
|
+
:prepared_data,
|
5
|
+
:renderer
|
6
|
+
|
7
|
+
def initialize(data, opts = {})
|
8
|
+
validate_arguments(data, opts)
|
9
|
+
@data = data
|
10
|
+
@options = default_options.merge opts
|
11
|
+
create_options_methods
|
12
|
+
initialize_instance_variables
|
13
|
+
@prepared_data = prepare_data
|
14
|
+
end
|
15
|
+
|
16
|
+
def validate_arguments(data, options)
|
17
|
+
raise ArgumentError.new('Data missing') if data.empty?
|
18
|
+
raise ArgumentError.new('Data not an array') unless data.is_a? Array
|
19
|
+
raise ArgumentError.new('Options missing') unless options.is_a? Hash
|
20
|
+
if options[:outer_margin] and !options[:outer_margin].is_a?(Numeric)
|
21
|
+
raise ArgumentError.new('outer_margin not a number')
|
22
|
+
end
|
23
|
+
if options[:colors]
|
24
|
+
unless options[:colors].is_a? Array
|
25
|
+
raise ArgumentError.new('colors not an array')
|
26
|
+
end
|
27
|
+
if options[:colors].any? and data.count > options[:colors].count
|
28
|
+
raise ArgumentError.new('not enough colors')
|
29
|
+
end
|
30
|
+
end
|
31
|
+
if options[:labels]
|
32
|
+
unless options[:labels].is_a? Array
|
33
|
+
raise ArgumentError.new('labels not an array')
|
34
|
+
end
|
35
|
+
if options[:labels].any? and data.count > options[:labels].count
|
36
|
+
raise ArgumentError.new('not enough labels')
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def default_options
|
42
|
+
{
|
43
|
+
title: nil,
|
44
|
+
type: :svg,
|
45
|
+
outer_margin: 30,
|
46
|
+
background_color: 'white',
|
47
|
+
labels: [],
|
48
|
+
colors: [
|
49
|
+
'#e41a1d',
|
50
|
+
'#377eb9',
|
51
|
+
'#4daf4b',
|
52
|
+
'#984ea4',
|
53
|
+
'#ff7f01',
|
54
|
+
'#ffff34',
|
55
|
+
'#a65629',
|
56
|
+
'#f781c0',
|
57
|
+
'#888888'
|
58
|
+
]
|
59
|
+
}
|
60
|
+
end
|
61
|
+
|
62
|
+
def prepare_data
|
63
|
+
data
|
64
|
+
end
|
65
|
+
|
66
|
+
def render
|
67
|
+
pre_draw
|
68
|
+
draw
|
69
|
+
post_draw
|
70
|
+
end
|
71
|
+
|
72
|
+
def pre_draw
|
73
|
+
@renderer = Charts::Renderer.new(self)
|
74
|
+
draw_background
|
75
|
+
draw_title
|
76
|
+
end
|
77
|
+
|
78
|
+
def draw
|
79
|
+
raise NotImplementedError
|
80
|
+
end
|
81
|
+
|
82
|
+
def post_draw
|
83
|
+
renderer.post_draw
|
84
|
+
end
|
85
|
+
|
86
|
+
def draw_background
|
87
|
+
renderer.rect 0, 0, width, height, fill: background_color, class: 'background_color'
|
88
|
+
end
|
89
|
+
|
90
|
+
def draw_title
|
91
|
+
return unless options[:title]
|
92
|
+
x = width / 2
|
93
|
+
y = outer_margin / 2 + 2 * renderer.font_size / 5
|
94
|
+
renderer.text options[:title], x, y, text_anchor: 'middle', class: 'title'
|
95
|
+
end
|
96
|
+
|
97
|
+
def initialize_instance_variables
|
98
|
+
end
|
99
|
+
|
100
|
+
def create_options_methods
|
101
|
+
options.each do |key, value|
|
102
|
+
define_singleton_method key, proc { value }
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
class Charts::CountChart < Charts::Chart
|
2
|
+
def default_options
|
3
|
+
super.merge(
|
4
|
+
columns: 10,
|
5
|
+
inner_margin: 2,
|
6
|
+
item_width: 20,
|
7
|
+
item_height: 20
|
8
|
+
)
|
9
|
+
end
|
10
|
+
|
11
|
+
def validate_arguments(data, options)
|
12
|
+
super(data, options)
|
13
|
+
raise ArgumentError if options[:inner_margin] and !options[:inner_margin].is_a?(Numeric)
|
14
|
+
raise ArgumentError unless data.all? { |x| Integer(x) }
|
15
|
+
end
|
16
|
+
|
17
|
+
def prepare_data
|
18
|
+
prepared_data = []
|
19
|
+
data.each_with_index do |value, index|
|
20
|
+
value.to_i.times { prepared_data << colors[index].to_s }
|
21
|
+
end
|
22
|
+
prepared_data.each_slice(columns).to_a
|
23
|
+
end
|
24
|
+
|
25
|
+
def draw
|
26
|
+
prepared_data.each_with_index do |row, row_count|
|
27
|
+
row.each_with_index do |color, column_count|
|
28
|
+
x = offset_x(column_count) + inner_margin + outer_margin
|
29
|
+
y = offset_y(row_count) + inner_margin + outer_margin
|
30
|
+
draw_item(x, y, color)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
draw_labels
|
34
|
+
end
|
35
|
+
|
36
|
+
def draw_labels
|
37
|
+
return if labels.empty?
|
38
|
+
data.each_with_index do |data, index|
|
39
|
+
x = inner_margin + outer_margin
|
40
|
+
y = offset_y(prepared_data.count + (index + 1)) + inner_margin + outer_margin
|
41
|
+
draw_item(x, y, colors[index])
|
42
|
+
draw_label_text(x, y, labels[index]) # expand total image size according to labels
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def draw_label_text(x, y, label)
|
47
|
+
x = x + item_width + inner_margin
|
48
|
+
y = y + item_height / 2 + 2 * renderer.font_size / 5
|
49
|
+
renderer.text(label, x, y, class: 'label_text')
|
50
|
+
end
|
51
|
+
|
52
|
+
def offset_x(column_count)
|
53
|
+
column_count * outer_item_width
|
54
|
+
end
|
55
|
+
|
56
|
+
def offset_y(row_count)
|
57
|
+
row_count * outer_item_height
|
58
|
+
end
|
59
|
+
|
60
|
+
def outer_item_width
|
61
|
+
item_width + 2 * inner_margin
|
62
|
+
end
|
63
|
+
|
64
|
+
def outer_item_height
|
65
|
+
item_height + 2 * inner_margin
|
66
|
+
end
|
67
|
+
|
68
|
+
def draw_item(_x, _y, _color)
|
69
|
+
raise NotImplementedError
|
70
|
+
end
|
71
|
+
|
72
|
+
def width
|
73
|
+
prepared_data.first.count * outer_item_width + (2 * outer_margin) # + label_count?
|
74
|
+
end
|
75
|
+
|
76
|
+
def height
|
77
|
+
(prepared_data.count + label_count) * outer_item_height + (2 * outer_margin)
|
78
|
+
end
|
79
|
+
|
80
|
+
def label_count
|
81
|
+
labels.any? ? (labels.count + 1) : 0
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class Charts::CrossCountChart < Charts::CountChart
|
2
|
+
def draw_item(x, y, color)
|
3
|
+
left = x + 4
|
4
|
+
right = x + item_width - 4
|
5
|
+
top = y + 4
|
6
|
+
bottom = y + item_height - 4
|
7
|
+
|
8
|
+
style = { stroke: color, stroke_width: 6, stroke_linecap: 'round' }
|
9
|
+
|
10
|
+
renderer.line left, top, right, bottom, style
|
11
|
+
renderer.line left, bottom, right, top, style
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
class Charts::ManikinCountChart < Charts::CountChart
|
2
|
+
def draw_item(x, y, color)
|
3
|
+
head x + width_percent(50), y, style(color)
|
4
|
+
body x + width_percent(50), y, style(color)
|
5
|
+
arms x + width_percent(50), y, style(color)
|
6
|
+
end
|
7
|
+
|
8
|
+
def head(x, y, style)
|
9
|
+
cy = y + height_percent(20)
|
10
|
+
radius = height_percent(10)
|
11
|
+
|
12
|
+
renderer.circle x, cy, radius, style.merge(class: 'head')
|
13
|
+
end
|
14
|
+
|
15
|
+
def body(x, y, style)
|
16
|
+
top = y + height_percent(40)
|
17
|
+
bottom = y + height_percent(95)
|
18
|
+
|
19
|
+
renderer.line x, top, x, bottom, style.merge(stroke_width: width_percent(30), class: 'body')
|
20
|
+
end
|
21
|
+
|
22
|
+
def arms(x, y, style)
|
23
|
+
top = y + height_percent(40)
|
24
|
+
bottom = y + height_percent(70)
|
25
|
+
left_x = x - width_percent(25)
|
26
|
+
right_x = x + width_percent(25)
|
27
|
+
|
28
|
+
renderer.line left_x, top, left_x, bottom, style.merge(class: 'left-arm')
|
29
|
+
renderer.line right_x, top, right_x, bottom, style.merge(class: 'right-arm')
|
30
|
+
end
|
31
|
+
|
32
|
+
def style(color)
|
33
|
+
{
|
34
|
+
fill: color,
|
35
|
+
stroke: color,
|
36
|
+
stroke_width: width_percent(10)
|
37
|
+
}
|
38
|
+
end
|
39
|
+
|
40
|
+
def width_percent(multiplicator)
|
41
|
+
multiplicator * item_width / 100
|
42
|
+
end
|
43
|
+
|
44
|
+
def height_percent(multiplicator)
|
45
|
+
multiplicator * item_height / 100
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Charts::Legend
|
2
|
+
def draw_labels
|
3
|
+
return if options[:labels].nil? || labels.empty?
|
4
|
+
label_row_length_sum = 0
|
5
|
+
label_row = 1
|
6
|
+
labels.each_with_index do |label, index|
|
7
|
+
x = outer_margin + label_row_length_sum
|
8
|
+
y = height - outer_margin - label_total_height + label_row * (label_height + label_margin)
|
9
|
+
label_row_length_sum += label.length * 10 + label_height + 2 * label_margin
|
10
|
+
if label_row_length_sum > inner_width
|
11
|
+
label_row_length_sum = 0
|
12
|
+
label_row += 1
|
13
|
+
end
|
14
|
+
renderer.rect x, y, label_height, label_height, fill: colors[index], stroke: colors[index]
|
15
|
+
label_x = x + label_height + label_margin
|
16
|
+
label_y = y + label_height - renderer.font_size / 3
|
17
|
+
renderer.text label, label_x, label_y, text_anchor: 'start', class: 'label'
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def label_total_height
|
22
|
+
return 0 if options[:labels].nil? || labels.empty?
|
23
|
+
label_rows * (label_height + label_margin)
|
24
|
+
end
|
25
|
+
|
26
|
+
def label_rows
|
27
|
+
return 0 if options[:labels].nil? || labels.empty?
|
28
|
+
avg_character_width = 10
|
29
|
+
((labels.join.length * avg_character_width + 2 * labels.count * label_margin) / inner_width.to_f).ceil
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
class Charts::PieChart < Charts::Chart
|
2
|
+
include Charts::Legend
|
3
|
+
|
4
|
+
attr_reader :sum,
|
5
|
+
:sub_sums
|
6
|
+
|
7
|
+
def validate_arguments(data, options)
|
8
|
+
super(data, options)
|
9
|
+
raise ArgumentError unless data.is_a? Array
|
10
|
+
raise ArgumentError if options[:labels] && !options[:labels].empty? && options[:labels].count != data.count
|
11
|
+
raise ArgumentError if options[:explode] && !options[:explode].empty? && options[:explode].count != data.count
|
12
|
+
end
|
13
|
+
|
14
|
+
def default_options
|
15
|
+
super.merge(
|
16
|
+
width: 600,
|
17
|
+
height: 400,
|
18
|
+
label_height: 20,
|
19
|
+
label_margin: 10,
|
20
|
+
explode: nil,
|
21
|
+
border_width: 2
|
22
|
+
)
|
23
|
+
end
|
24
|
+
|
25
|
+
def prepare_data
|
26
|
+
@sum = data.reduce 0, :+
|
27
|
+
normalized_data = data.map { |value| value.to_f / sum }
|
28
|
+
@sub_sums = (normalized_data.length + 1).times.map { |i| normalized_data.first(i).reduce 0.0, :+ }
|
29
|
+
normalized_data
|
30
|
+
end
|
31
|
+
|
32
|
+
def pre_draw
|
33
|
+
super
|
34
|
+
draw_labels
|
35
|
+
end
|
36
|
+
|
37
|
+
def draw
|
38
|
+
prepared_data.length.times do |i|
|
39
|
+
start_deg = Math::PI * 2 * sub_sums[i]
|
40
|
+
end_deg = Math::PI * 2 * sub_sums[i + 1]
|
41
|
+
middle_deg = (start_deg + end_deg) / 2
|
42
|
+
expl = explode ? explode[i] : 0
|
43
|
+
middle_x = center_x + expl * Math.sin(middle_deg)
|
44
|
+
middle_y = center_y - expl * Math.cos(middle_deg)
|
45
|
+
start_x = middle_x + radius * Math.sin(start_deg)
|
46
|
+
start_y = middle_y - radius * Math.cos(start_deg)
|
47
|
+
end_x = middle_x + radius * Math.sin(end_deg)
|
48
|
+
end_y = middle_y - radius * Math.cos(end_deg)
|
49
|
+
more_than_half = prepared_data[i] > 0.5 ? 1 : 0
|
50
|
+
path = "M#{middle_x} #{middle_y}
|
51
|
+
L#{start_x} #{start_y}
|
52
|
+
A#{radius} #{radius} 0 #{more_than_half} 1 #{end_x} #{end_y}
|
53
|
+
L#{middle_x} #{middle_y}
|
54
|
+
"
|
55
|
+
renderer.path(path, fill: colors[i], stroke: background_color, stroke_width: border_width)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def radius
|
60
|
+
[inner_width, inner_height].min / 2
|
61
|
+
end
|
62
|
+
|
63
|
+
def center_x
|
64
|
+
outer_margin + inner_width / 2
|
65
|
+
end
|
66
|
+
|
67
|
+
def center_y
|
68
|
+
outer_margin + inner_height / 2
|
69
|
+
end
|
70
|
+
|
71
|
+
def inner_width
|
72
|
+
width - 2 * outer_margin
|
73
|
+
end
|
74
|
+
|
75
|
+
def inner_height
|
76
|
+
height - 2 * outer_margin - label_total_height
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
class Charts::Renderer
|
2
|
+
attr_reader :graph
|
3
|
+
|
4
|
+
def initialize(graph)
|
5
|
+
@graph = graph
|
6
|
+
if graph.type == :svg
|
7
|
+
extend Charts::Renderer::SvgRenderer
|
8
|
+
else
|
9
|
+
extend Charts::Renderer::RvgRenderer
|
10
|
+
end
|
11
|
+
pre_draw
|
12
|
+
end
|
13
|
+
|
14
|
+
def post_draw
|
15
|
+
filename = graph.options[:filename]
|
16
|
+
if filename
|
17
|
+
save filename
|
18
|
+
else
|
19
|
+
print
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def grid_line_style
|
24
|
+
{
|
25
|
+
stroke: '#BBBBBB',
|
26
|
+
stroke_width: 1
|
27
|
+
}
|
28
|
+
end
|
29
|
+
|
30
|
+
def font_style
|
31
|
+
{
|
32
|
+
font_family: 'arial',
|
33
|
+
font_size: font_size
|
34
|
+
}
|
35
|
+
end
|
36
|
+
|
37
|
+
def font_size
|
38
|
+
16
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'rvg/rvg'
|
2
|
+
|
3
|
+
module Charts::Renderer::RvgRenderer
|
4
|
+
attr_reader :rvg
|
5
|
+
|
6
|
+
def pre_draw
|
7
|
+
@rvg = Magick::RVG.new(graph.width, graph.height) do |canvas|
|
8
|
+
canvas.background_fill = 'white'
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def print
|
13
|
+
rvg.draw.to_blob { |attrs| attrs.format = 'PNG' }
|
14
|
+
end
|
15
|
+
|
16
|
+
def save(filename)
|
17
|
+
rvg.draw.write filename
|
18
|
+
end
|
19
|
+
|
20
|
+
def line(x1, y1, x2, y2, style)
|
21
|
+
canvas(style) { |c| c.line x1, y1, x2, y2 }
|
22
|
+
end
|
23
|
+
|
24
|
+
def circle(cx, cy, radius, style)
|
25
|
+
canvas(style) { |c| c.circle radius, cx, cy }
|
26
|
+
end
|
27
|
+
|
28
|
+
def rect(x, y, width, height, style)
|
29
|
+
canvas(style) { |c| c.rect width, height, x, y }
|
30
|
+
end
|
31
|
+
|
32
|
+
def path(d, style)
|
33
|
+
canvas(style) { |c| c.path d }
|
34
|
+
end
|
35
|
+
|
36
|
+
def text(text, x, y, style = {})
|
37
|
+
canvas(font_style.merge(style)) { |c| c.text x, y, text }
|
38
|
+
end
|
39
|
+
|
40
|
+
def canvas(style)
|
41
|
+
style.delete(:class)
|
42
|
+
rvg.rvg(graph.width, graph.height) do |canvas|
|
43
|
+
yield(canvas).styles(style)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'victor'
|
2
|
+
|
3
|
+
module Charts::Renderer::SvgRenderer
|
4
|
+
attr_reader :svg
|
5
|
+
|
6
|
+
def pre_draw
|
7
|
+
@svg = SVG.new width: graph.width, height: graph.height
|
8
|
+
end
|
9
|
+
|
10
|
+
def print
|
11
|
+
svg.render
|
12
|
+
end
|
13
|
+
|
14
|
+
def save(filename)
|
15
|
+
svg.save filename
|
16
|
+
end
|
17
|
+
|
18
|
+
def line(x1, y1, x2, y2, style = {})
|
19
|
+
svg.line style.merge(x1: x1, y1: y1, x2: x2, y2: y2)
|
20
|
+
end
|
21
|
+
|
22
|
+
def circle(cx, cy, radius, style = {})
|
23
|
+
svg.circle style.merge(cx: cx, cy: cy, r: radius)
|
24
|
+
end
|
25
|
+
|
26
|
+
def rect(x, y, width, height, style = {})
|
27
|
+
svg.rect style.merge(x: x, y: y, width: width, height: height)
|
28
|
+
end
|
29
|
+
|
30
|
+
def path(d, style = {})
|
31
|
+
svg.path style.merge(d: d)
|
32
|
+
end
|
33
|
+
|
34
|
+
def text(text, x, y, style = {})
|
35
|
+
svg.text text, font_style.merge(style).merge(x: x, y: y)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
data/lib/charts.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
module Charts
|
2
|
+
end
|
3
|
+
|
4
|
+
# Includes:
|
5
|
+
require_relative 'charts/renderer/renderer'
|
6
|
+
require_relative 'charts/renderer/rvg_renderer'
|
7
|
+
require_relative 'charts/renderer/svg_renderer'
|
8
|
+
|
9
|
+
# Classes:
|
10
|
+
require_relative 'charts/chart'
|
11
|
+
require_relative 'charts/legend'
|
12
|
+
require_relative 'charts/bar_chart/grid/grid'
|
13
|
+
require_relative 'charts/bar_chart/grid/grid_line'
|
14
|
+
require_relative 'charts/bar_chart/grid/vertical_grid_line'
|
15
|
+
require_relative 'charts/bar_chart/grid/horizontal_grid_line'
|
16
|
+
require_relative 'charts/bar_chart/bar_chart'
|
17
|
+
require_relative 'charts/bar_chart/bar/bar'
|
18
|
+
require_relative 'charts/bar_chart/bar/vertical_bar'
|
19
|
+
require_relative 'charts/bar_chart/bar/horizontal_bar'
|
20
|
+
require_relative 'charts/pie_chart/pie_chart'
|
21
|
+
require_relative 'charts/count_chart/count_chart'
|
22
|
+
require_relative 'charts/count_chart/circle_count_chart'
|
23
|
+
require_relative 'charts/count_chart/cross_count_chart'
|
24
|
+
require_relative 'charts/count_chart/manikin_count_chart'
|
25
|
+
require_relative 'charts/count_chart/symbol_count_chart'
|
26
|
+
|
27
|
+
# Helper for command line charts
|
28
|
+
require_relative 'charts/bin/opt_parser'
|
29
|
+
require_relative 'charts/bin/dispatcher'
|
metadata
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: charts
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.9
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Eike Send
|
8
|
+
- Maximilian Maintz
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2017-01-04 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: victor
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - "~>"
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: 0.1.3
|
21
|
+
type: :runtime
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - "~>"
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: 0.1.3
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: rmagick
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - "~>"
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '2.0'
|
35
|
+
type: :runtime
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - "~>"
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '2.0'
|
42
|
+
description: Create charts, graphs and info graphics with ruby as SVG, JPG or PNG
|
43
|
+
images
|
44
|
+
email: charts@eike.se
|
45
|
+
executables: []
|
46
|
+
extensions: []
|
47
|
+
extra_rdoc_files: []
|
48
|
+
files:
|
49
|
+
- lib/charts.rb
|
50
|
+
- lib/charts/bar_chart/bar/bar.rb
|
51
|
+
- lib/charts/bar_chart/bar/horizontal_bar.rb
|
52
|
+
- lib/charts/bar_chart/bar/vertical_bar.rb
|
53
|
+
- lib/charts/bar_chart/bar_chart.rb
|
54
|
+
- lib/charts/bar_chart/grid/grid.rb
|
55
|
+
- lib/charts/bar_chart/grid/grid_line.rb
|
56
|
+
- lib/charts/bar_chart/grid/horizontal_grid_line.rb
|
57
|
+
- lib/charts/bar_chart/grid/vertical_grid_line.rb
|
58
|
+
- lib/charts/bin/dispatcher.rb
|
59
|
+
- lib/charts/bin/opt_parser.rb
|
60
|
+
- lib/charts/chart.rb
|
61
|
+
- lib/charts/count_chart/circle_count_chart.rb
|
62
|
+
- lib/charts/count_chart/count_chart.rb
|
63
|
+
- lib/charts/count_chart/cross_count_chart.rb
|
64
|
+
- lib/charts/count_chart/manikin_count_chart.rb
|
65
|
+
- lib/charts/count_chart/symbol_count_chart.rb
|
66
|
+
- lib/charts/legend.rb
|
67
|
+
- lib/charts/pie_chart/pie_chart.rb
|
68
|
+
- lib/charts/renderer/renderer.rb
|
69
|
+
- lib/charts/renderer/rvg_renderer.rb
|
70
|
+
- lib/charts/renderer/svg_renderer.rb
|
71
|
+
homepage: http://github.com/eikes/charts
|
72
|
+
licenses:
|
73
|
+
- MIT
|
74
|
+
metadata: {}
|
75
|
+
post_install_message:
|
76
|
+
rdoc_options: []
|
77
|
+
require_paths:
|
78
|
+
- lib
|
79
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - ">="
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '0'
|
84
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - ">="
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '0'
|
89
|
+
requirements: []
|
90
|
+
rubyforge_project:
|
91
|
+
rubygems_version: 2.5.1
|
92
|
+
signing_key:
|
93
|
+
specification_version: 4
|
94
|
+
summary: Renders beautiful charts
|
95
|
+
test_files: []
|