charty 0.1.1.dev

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +62 -0
  3. data/Gemfile +6 -0
  4. data/LICENSE +21 -0
  5. data/LICENSE.txt +21 -0
  6. data/README.md +388 -0
  7. data/Rakefile +2 -0
  8. data/bin/console +14 -0
  9. data/bin/setup +8 -0
  10. data/charty.gemspec +31 -0
  11. data/examples/Gemfile +29 -0
  12. data/examples/active_record.ipynb +281 -0
  13. data/examples/daru.ipynb +308 -0
  14. data/examples/nmatrix.ipynb +242 -0
  15. data/examples/numo-narray.ipynb +245 -0
  16. data/examples/sample_gruff.ipynb +465 -0
  17. data/examples/sample_images/bar_gruff.png +0 -0
  18. data/examples/sample_images/bar_matplot.png +0 -0
  19. data/examples/sample_images/bar_rubyplot.png +0 -0
  20. data/examples/sample_images/boxplot_matplot.png +0 -0
  21. data/examples/sample_images/bubble_matplot.png +0 -0
  22. data/examples/sample_images/bubble_rubyplot.png +0 -0
  23. data/examples/sample_images/curve_gruff.png +0 -0
  24. data/examples/sample_images/curve_matplot.png +0 -0
  25. data/examples/sample_images/curve_rubyplot.png +0 -0
  26. data/examples/sample_images/curve_with_function_matplot.png +0 -0
  27. data/examples/sample_images/curve_with_function_rubyplot.png +0 -0
  28. data/examples/sample_images/errorbar_matplot.png +0 -0
  29. data/examples/sample_images/hist_matplot.png +0 -0
  30. data/examples/sample_images/scatter_gruff.png +0 -0
  31. data/examples/sample_images/scatter_matplot.png +0 -0
  32. data/examples/sample_images/scatter_rubyplot.png +0 -0
  33. data/examples/sample_images/subplot2_matplot.png +0 -0
  34. data/examples/sample_images/subplot_matplot.png +0 -0
  35. data/examples/sample_matplotlib.ipynb +372 -0
  36. data/examples/sample_rubyplot.ipynb +432 -0
  37. data/images/design_concept.png +0 -0
  38. data/lib/charty.rb +12 -0
  39. data/lib/charty/gruff.rb +75 -0
  40. data/lib/charty/layout.rb +75 -0
  41. data/lib/charty/linspace.rb +21 -0
  42. data/lib/charty/matplot.rb +91 -0
  43. data/lib/charty/plotter.rb +241 -0
  44. data/lib/charty/rubyplot.rb +90 -0
  45. data/lib/charty/table.rb +41 -0
  46. data/lib/charty/version.rb +9 -0
  47. 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
@@ -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