charty 0.1.1.dev
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/.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
|