charty 0.1.1.dev
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +62 -0
- data/Gemfile +6 -0
- data/LICENSE +21 -0
- data/LICENSE.txt +21 -0
- data/README.md +388 -0
- data/Rakefile +2 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/charty.gemspec +31 -0
- data/examples/Gemfile +29 -0
- data/examples/active_record.ipynb +281 -0
- data/examples/daru.ipynb +308 -0
- data/examples/nmatrix.ipynb +242 -0
- data/examples/numo-narray.ipynb +245 -0
- data/examples/sample_gruff.ipynb +465 -0
- data/examples/sample_images/bar_gruff.png +0 -0
- data/examples/sample_images/bar_matplot.png +0 -0
- data/examples/sample_images/bar_rubyplot.png +0 -0
- data/examples/sample_images/boxplot_matplot.png +0 -0
- data/examples/sample_images/bubble_matplot.png +0 -0
- data/examples/sample_images/bubble_rubyplot.png +0 -0
- data/examples/sample_images/curve_gruff.png +0 -0
- data/examples/sample_images/curve_matplot.png +0 -0
- data/examples/sample_images/curve_rubyplot.png +0 -0
- data/examples/sample_images/curve_with_function_matplot.png +0 -0
- data/examples/sample_images/curve_with_function_rubyplot.png +0 -0
- data/examples/sample_images/errorbar_matplot.png +0 -0
- data/examples/sample_images/hist_matplot.png +0 -0
- data/examples/sample_images/scatter_gruff.png +0 -0
- data/examples/sample_images/scatter_matplot.png +0 -0
- data/examples/sample_images/scatter_rubyplot.png +0 -0
- data/examples/sample_images/subplot2_matplot.png +0 -0
- data/examples/sample_images/subplot_matplot.png +0 -0
- data/examples/sample_matplotlib.ipynb +372 -0
- data/examples/sample_rubyplot.ipynb +432 -0
- data/images/design_concept.png +0 -0
- data/lib/charty.rb +12 -0
- data/lib/charty/gruff.rb +75 -0
- data/lib/charty/layout.rb +75 -0
- data/lib/charty/linspace.rb +21 -0
- data/lib/charty/matplot.rb +91 -0
- data/lib/charty/plotter.rb +241 -0
- data/lib/charty/rubyplot.rb +90 -0
- data/lib/charty/table.rb +41 -0
- data/lib/charty/version.rb +9 -0
- metadata +120 -0
Binary file
|
data/lib/charty.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require_relative "charty/version"
|
2
|
+
|
3
|
+
require_relative "charty/plotter"
|
4
|
+
require_relative "charty/layout"
|
5
|
+
require_relative "charty/linspace"
|
6
|
+
require_relative "charty/table"
|
7
|
+
|
8
|
+
module Charty
|
9
|
+
def self.new(*args)
|
10
|
+
Charty::Plotter.new(*args)
|
11
|
+
end
|
12
|
+
end
|
data/lib/charty/gruff.rb
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'gruff'
|
2
|
+
|
3
|
+
module Charty
|
4
|
+
class Gruff
|
5
|
+
def initialize
|
6
|
+
@plot = ::Gruff
|
7
|
+
end
|
8
|
+
|
9
|
+
def label(x, y)
|
10
|
+
end
|
11
|
+
|
12
|
+
def series=(series)
|
13
|
+
@series = series
|
14
|
+
end
|
15
|
+
|
16
|
+
def render_layout(layout)
|
17
|
+
raise NotImplementedError
|
18
|
+
end
|
19
|
+
|
20
|
+
def render(context, filename)
|
21
|
+
plot(@plot, context).write(filename)
|
22
|
+
end
|
23
|
+
|
24
|
+
def plot(plot, context)
|
25
|
+
# case
|
26
|
+
# when plot.respond_to?(:xlim)
|
27
|
+
# plot.xlim(context.range_x.begin, context.range_x.end)
|
28
|
+
# plot.ylim(context.range_y.begin, context.range_y.end)
|
29
|
+
# when plot.respond_to?(:set_xlim)
|
30
|
+
# plot.set_xlim(context.range_x.begin, context.range_x.end)
|
31
|
+
# plot.set_ylim(context.range_y.begin, context.range_y.end)
|
32
|
+
# end
|
33
|
+
|
34
|
+
case context.method
|
35
|
+
when :bar
|
36
|
+
p = plot::Bar.new
|
37
|
+
p.title = context.title if context.title
|
38
|
+
p.x_axis_label = context.xlabel if context.xlabel
|
39
|
+
p.y_axis_label = context.ylabel if context.ylabel
|
40
|
+
context.series.each do |data|
|
41
|
+
p.data(data.label, data.xs.to_a)
|
42
|
+
end
|
43
|
+
p
|
44
|
+
when :boxplot
|
45
|
+
# refs. https://github.com/topfunky/gruff/issues/155
|
46
|
+
raise NotImplementedError
|
47
|
+
when :bubble
|
48
|
+
raise NotImplementedError
|
49
|
+
when :curve
|
50
|
+
p = plot::Line.new
|
51
|
+
p.title = context.title if context.title
|
52
|
+
p.x_axis_label = context.xlabel if context.xlabel
|
53
|
+
p.y_axis_label = context.ylabel if context.ylabel
|
54
|
+
context.series.each do |data|
|
55
|
+
p.data(data.label, data.xs.to_a)
|
56
|
+
end
|
57
|
+
p
|
58
|
+
when :scatter
|
59
|
+
p = plot::Scatter.new
|
60
|
+
p.title = context.title if context.title
|
61
|
+
p.x_axis_label = context.xlabel if context.xlabel
|
62
|
+
p.y_axis_label = context.ylabel if context.ylabel
|
63
|
+
context.series.each do |data|
|
64
|
+
p.data(data.label, data.xs.to_a, data.ys.to_a)
|
65
|
+
end
|
66
|
+
p
|
67
|
+
when :errorbar
|
68
|
+
# refs. https://github.com/topfunky/gruff/issues/163
|
69
|
+
raise NotImplementedError
|
70
|
+
when :hist
|
71
|
+
raise NotImplementedError
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module Charty
|
2
|
+
class Layout
|
3
|
+
def initialize(frontend, definition = :horizontal)
|
4
|
+
@frontend = frontend
|
5
|
+
@layout = parse_definition(definition)
|
6
|
+
end
|
7
|
+
|
8
|
+
def parse_definition(definition)
|
9
|
+
case definition
|
10
|
+
when :horizontal
|
11
|
+
ArrayLayout.new
|
12
|
+
when :vertical
|
13
|
+
ArrayLayout.new(:vertical)
|
14
|
+
else
|
15
|
+
if match = definition.to_s.match(/\Agrid(\d+)x(\d+)\z/)
|
16
|
+
num_cols = match[1].to_i
|
17
|
+
num_rows = match[2].to_i
|
18
|
+
GridLayout.new(num_cols, num_rows)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def <<(content)
|
24
|
+
if content.respond_to?(:each)
|
25
|
+
content.each {|c| self << c }
|
26
|
+
else
|
27
|
+
@layout << content
|
28
|
+
end
|
29
|
+
nil
|
30
|
+
end
|
31
|
+
|
32
|
+
def render(filename="")
|
33
|
+
@frontend.render_layout(@layout)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class ArrayLayout
|
38
|
+
def initialize(direction=:horizontal)
|
39
|
+
@array = []
|
40
|
+
@direction = direction
|
41
|
+
end
|
42
|
+
|
43
|
+
def <<(content)
|
44
|
+
@array << content
|
45
|
+
end
|
46
|
+
|
47
|
+
def num_rows
|
48
|
+
@direction == :horizontal ? 1 : @array.count
|
49
|
+
end
|
50
|
+
|
51
|
+
def num_cols
|
52
|
+
@direction == :vertical ? 1 : @array.count
|
53
|
+
end
|
54
|
+
|
55
|
+
def rows
|
56
|
+
[@array]
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
class GridLayout
|
61
|
+
attr_reader :num_rows, :num_cols, :rows
|
62
|
+
|
63
|
+
def initialize(num_cols, num_rows)
|
64
|
+
@rows = Array.new(num_rows) { Array.new(num_cols) }
|
65
|
+
@num_cols = num_cols
|
66
|
+
@num_rows = num_rows
|
67
|
+
@cursor = 0
|
68
|
+
end
|
69
|
+
|
70
|
+
def <<(content)
|
71
|
+
@rows[@cursor / @num_rows][@cursor % @num_cols] = content
|
72
|
+
@cursor += 1
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Charty
|
2
|
+
class Linspace
|
3
|
+
include Enumerable
|
4
|
+
|
5
|
+
def initialize(range, num_step)
|
6
|
+
@range = range
|
7
|
+
@num_step = num_step
|
8
|
+
end
|
9
|
+
|
10
|
+
def each(&block)
|
11
|
+
step = (@range.end - @range.begin).to_f / @num_step
|
12
|
+
(@num_step - 1).times do |i|
|
13
|
+
block.call(@range.begin + i * step)
|
14
|
+
end
|
15
|
+
|
16
|
+
unless @range.exclude_end?
|
17
|
+
block.call(@range.end)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'matplotlib/pyplot'
|
2
|
+
|
3
|
+
module Charty
|
4
|
+
class Matplot
|
5
|
+
def initialize
|
6
|
+
@plot = Matplotlib::Pyplot
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.activate_iruby_integration
|
10
|
+
require 'matplotlib/iruby'
|
11
|
+
Matplotlib::IRuby.activate
|
12
|
+
end
|
13
|
+
|
14
|
+
def label(x, y)
|
15
|
+
end
|
16
|
+
|
17
|
+
def series=(series)
|
18
|
+
@series = series
|
19
|
+
end
|
20
|
+
|
21
|
+
def render_layout(layout)
|
22
|
+
(fig, axes) = *@plot.subplots(nrows: layout.num_rows, ncols: layout.num_cols)
|
23
|
+
layout.rows.each_with_index do |row, y|
|
24
|
+
row.each_with_index do |cel, x|
|
25
|
+
plot = layout.num_rows > 1 ? axes[y][x] : axes[x]
|
26
|
+
plot(plot, cel, subplot: true)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
@plot.show
|
30
|
+
end
|
31
|
+
|
32
|
+
def render(context, filename)
|
33
|
+
plot(@plot, context)
|
34
|
+
@plot.savefig(filename) if filename
|
35
|
+
@plot.show
|
36
|
+
end
|
37
|
+
|
38
|
+
def plot(plot, context, subplot: false)
|
39
|
+
# TODO: Since it is not required, research and change conditions.
|
40
|
+
# case
|
41
|
+
# when plot.respond_to?(:xlim)
|
42
|
+
# plot.xlim(context.range_x.begin, context.range_x.end)
|
43
|
+
# plot.ylim(context.range_y.begin, context.range_y.end)
|
44
|
+
# when plot.respond_to?(:set_xlim)
|
45
|
+
# plot.set_xlim(context.range_x.begin, context.range_x.end)
|
46
|
+
# plot.set_ylim(context.range_y.begin, context.range_y.end)
|
47
|
+
# end
|
48
|
+
|
49
|
+
plot.title(context.title) if context.title
|
50
|
+
if !subplot
|
51
|
+
plot.xlabel(context.xlabel) if context.xlabel
|
52
|
+
plot.ylabel(context.ylabel) if context.ylabel
|
53
|
+
end
|
54
|
+
|
55
|
+
case context.method
|
56
|
+
when :bar
|
57
|
+
context.series.each do |data|
|
58
|
+
plot.bar(data.xs.to_a, data.ys.to_a)
|
59
|
+
end
|
60
|
+
when :boxplot
|
61
|
+
plot.boxplot(context.data.to_a)
|
62
|
+
when :bubble
|
63
|
+
context.series.each do |data|
|
64
|
+
plot.scatter(data.xs.to_a, data.ys.to_a, s: data.zs.to_a, alpha: 0.5)
|
65
|
+
end
|
66
|
+
when :curve
|
67
|
+
context.series.each do |data|
|
68
|
+
plot.plot(data.xs.to_a, data.ys.to_a)
|
69
|
+
end
|
70
|
+
when :scatter
|
71
|
+
context.series.each do |data|
|
72
|
+
plot.scatter(data.xs.to_a, data.ys.to_a, label: data.label)
|
73
|
+
end
|
74
|
+
plot.legend()
|
75
|
+
when :errorbar
|
76
|
+
context.series.each do |data|
|
77
|
+
plot.errorbar(
|
78
|
+
data.xs.to_a,
|
79
|
+
data.ys.to_a,
|
80
|
+
data.xerr,
|
81
|
+
data.yerr,
|
82
|
+
label: data.label,
|
83
|
+
)
|
84
|
+
end
|
85
|
+
plot.legend()
|
86
|
+
when :hist
|
87
|
+
plot.hist(context.data.to_a)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,241 @@
|
|
1
|
+
module Charty
|
2
|
+
class Plotter
|
3
|
+
def initialize(frontend)
|
4
|
+
@frontend = case frontend
|
5
|
+
when :matplot
|
6
|
+
require_relative "matplot"
|
7
|
+
Charty::Matplot.new
|
8
|
+
when :gruff
|
9
|
+
require_relative "gruff"
|
10
|
+
Charty::Gruff.new
|
11
|
+
when :rubyplot
|
12
|
+
require_relative "rubyplot"
|
13
|
+
Charty::Rubyplot.new
|
14
|
+
else
|
15
|
+
raise NotImplementedError
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def table=(data, **kwargs)
|
20
|
+
@table = Charty::Table.new(data)
|
21
|
+
end
|
22
|
+
|
23
|
+
attr_reader :table
|
24
|
+
|
25
|
+
def to_bar(x, y, **args, &block)
|
26
|
+
seriesx, seriesy = *table.to_a(x, y)
|
27
|
+
xrange = (seriesx.min)..(seriesx.max)
|
28
|
+
yrange = (seriesy.min)..(seriesy.max)
|
29
|
+
bar = bar do
|
30
|
+
series seriesx, seriesy
|
31
|
+
range x: xrange, y: yrange
|
32
|
+
xlabel x
|
33
|
+
ylabel y
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def to_boxplot(x, y, **args, &block)
|
38
|
+
serieses = table.to_a(x, y)
|
39
|
+
xrange = 0..serieses.size
|
40
|
+
yrange = (serieses.flatten.min - 1)..(serieses.flatten.max + 1)
|
41
|
+
boxplot = boxplot do
|
42
|
+
data serieses
|
43
|
+
range x: xrange, y: yrange
|
44
|
+
xlabel x
|
45
|
+
ylabel y
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def to_bubble(x, y, z, **args, &block)
|
50
|
+
seriesx, seriesy, seriesz = *table.to_a(x, y, z)
|
51
|
+
xrange = (seriesx.min)..(seriesx.max)
|
52
|
+
yrange = (seriesy.min)..(seriesy.max)
|
53
|
+
bubble = bubble do
|
54
|
+
series seriesx, seriesy, seriesz
|
55
|
+
range x: xrange, y: yrange
|
56
|
+
xlabel x
|
57
|
+
ylabel y
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def to_curve(x, y, **args, &block)
|
62
|
+
seriesx, seriesy = *table.to_a(x, y)
|
63
|
+
xrange = (seriesx.min)..(seriesx.max)
|
64
|
+
yrange = (seriesy.min)..(seriesy.max)
|
65
|
+
curve = curve do
|
66
|
+
series seriesx, seriesy
|
67
|
+
range x: xrange, y: yrange
|
68
|
+
xlabel x
|
69
|
+
ylabel y
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def to_scatter(x, y, **args, &block)
|
74
|
+
seriesx, seriesy = *table.to_a(x, y)
|
75
|
+
xrange = (seriesx.min)..(seriesx.max)
|
76
|
+
yrange = (seriesy.min)..(seriesy.max)
|
77
|
+
scatter = scatter do
|
78
|
+
series seriesx, seriesy
|
79
|
+
range x: xrange, y: yrange
|
80
|
+
xlabel x
|
81
|
+
ylabel y
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def to_errorbar(x, y, **args, &block)
|
86
|
+
# TODO: It is not yet decided how to include data including xerror and yerror.
|
87
|
+
seriesx, seriesy = *table.to_a(x, y)
|
88
|
+
xrange = (seriesx.min)..(seriesx.max)
|
89
|
+
yrange = (seriesy.min)..(seriesy.max)
|
90
|
+
errorbar = errorbar do
|
91
|
+
series seriesx, seriesy
|
92
|
+
range x: xrange, y: yrange
|
93
|
+
xlabel x
|
94
|
+
ylabel y
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def to_hst(x, y, **args, &block)
|
99
|
+
serieses = table.to_a(x, y)
|
100
|
+
xrange = (serieses.flatten.min - 1)..(serieses.flatten.max + 1)
|
101
|
+
yrange = 0..serieses[0].size
|
102
|
+
hist = hist do
|
103
|
+
data serieses
|
104
|
+
range x: xrange, y: yrange
|
105
|
+
xlabel x
|
106
|
+
ylabel y
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def bar(**args, &block)
|
111
|
+
context = RenderContext.new :bar, **args, &block
|
112
|
+
context.apply(@frontend)
|
113
|
+
end
|
114
|
+
|
115
|
+
def boxplot(**args, &block)
|
116
|
+
context = RenderContext.new :boxplot, **args, &block
|
117
|
+
context.apply(@frontend)
|
118
|
+
end
|
119
|
+
|
120
|
+
def bubble(**args, &block)
|
121
|
+
context = RenderContext.new :bubble, **args, &block
|
122
|
+
context.apply(@frontend)
|
123
|
+
end
|
124
|
+
|
125
|
+
def curve(**args, &block)
|
126
|
+
context = RenderContext.new :curve, **args, &block
|
127
|
+
context.apply(@frontend)
|
128
|
+
end
|
129
|
+
|
130
|
+
def scatter(**args, &block)
|
131
|
+
context = RenderContext.new :scatter, **args, &block
|
132
|
+
context.apply(@frontend)
|
133
|
+
end
|
134
|
+
|
135
|
+
def errorbar(**args, &block)
|
136
|
+
context = RenderContext.new :errorbar, **args, &block
|
137
|
+
context.apply(@frontend)
|
138
|
+
end
|
139
|
+
|
140
|
+
def hist(**args, &block)
|
141
|
+
context = RenderContext.new :hist, **args, &block
|
142
|
+
context.apply(@frontend)
|
143
|
+
end
|
144
|
+
|
145
|
+
def layout(definition=:horizontal)
|
146
|
+
Layout.new(@frontend, definition)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
Series = Struct.new(:xs, :ys, :zs, :xerr, :yerr, :label)
|
151
|
+
|
152
|
+
class RenderContext
|
153
|
+
attr_reader :function, :range, :series, :method, :data, :title, :xlabel, :ylabel
|
154
|
+
|
155
|
+
def initialize(method, **args, &block)
|
156
|
+
@method = method
|
157
|
+
configurator = Configurator.new(**args)
|
158
|
+
configurator.instance_eval &block
|
159
|
+
# TODO: label も外から付けられた方がよさそう
|
160
|
+
(@range, @series, @function, @data, @title, @xlabel, @ylabel) = configurator.to_a
|
161
|
+
end
|
162
|
+
|
163
|
+
class Configurator
|
164
|
+
def initialize(**args)
|
165
|
+
@args = args
|
166
|
+
@series = []
|
167
|
+
end
|
168
|
+
|
169
|
+
def function(&block)
|
170
|
+
@function = block
|
171
|
+
end
|
172
|
+
|
173
|
+
def data(data)
|
174
|
+
@data = data
|
175
|
+
end
|
176
|
+
|
177
|
+
def title(title)
|
178
|
+
@title = title
|
179
|
+
end
|
180
|
+
|
181
|
+
def xlabel(xlabel)
|
182
|
+
@xlabel = xlabel
|
183
|
+
end
|
184
|
+
|
185
|
+
def ylabel(ylabel)
|
186
|
+
@ylabel = ylabel
|
187
|
+
end
|
188
|
+
|
189
|
+
def label(x, y)
|
190
|
+
|
191
|
+
end
|
192
|
+
|
193
|
+
def series(xs, ys=nil, zs=nil, xerr: nil, yerr: nil, label: nil)
|
194
|
+
@series << Series.new(xs, ys, zs, xerr, yerr, label)
|
195
|
+
end
|
196
|
+
|
197
|
+
def range(range)
|
198
|
+
@range = range
|
199
|
+
end
|
200
|
+
|
201
|
+
def to_a
|
202
|
+
[@range, @series, @function, @data, @title, @xlabel, @ylabel]
|
203
|
+
end
|
204
|
+
|
205
|
+
def method_missing(method, *args)
|
206
|
+
if (@args.has_key?(method))
|
207
|
+
@args[name]
|
208
|
+
else
|
209
|
+
super
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
def range_x
|
215
|
+
@range[:x]
|
216
|
+
end
|
217
|
+
|
218
|
+
def range_y
|
219
|
+
@range[:y]
|
220
|
+
end
|
221
|
+
|
222
|
+
def render(filename=nil)
|
223
|
+
@frontend.render(self, filename)
|
224
|
+
end
|
225
|
+
|
226
|
+
def apply(frontend)
|
227
|
+
case
|
228
|
+
when !@series.empty?
|
229
|
+
frontend.series = @series
|
230
|
+
when @function
|
231
|
+
linspace = Linspace.new(@range[:x], 100)
|
232
|
+
# TODO: set label with function
|
233
|
+
# TODO: set ys to xs when gruff curve with function
|
234
|
+
@series << Series.new(linspace.to_a, linspace.map{|x| @function.call(x) }, label: "function" )
|
235
|
+
end
|
236
|
+
|
237
|
+
@frontend = frontend
|
238
|
+
self
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|