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.
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