charty 0.1.4.dev → 0.2.4

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 (91) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +71 -0
  3. data/.github/workflows/nmatrix.yml +67 -0
  4. data/.github/workflows/pycall.yml +86 -0
  5. data/Dockerfile.dev +9 -1
  6. data/Gemfile +18 -0
  7. data/README.md +128 -9
  8. data/Rakefile +4 -5
  9. data/charty.gemspec +7 -2
  10. data/examples/Gemfile +1 -0
  11. data/examples/active_record.ipynb +34 -34
  12. data/examples/daru.ipynb +71 -29
  13. data/examples/iris_dataset.ipynb +12 -5
  14. data/examples/nmatrix.ipynb +30 -30
  15. data/examples/numo_narray.ipynb +245 -0
  16. data/examples/palette.rb +71 -0
  17. data/examples/sample.png +0 -0
  18. data/examples/sample_bokeh.ipynb +156 -0
  19. data/examples/sample_google_chart.ipynb +229 -68
  20. data/examples/sample_gruff.ipynb +148 -133
  21. data/examples/sample_images/bar_bokeh.html +85 -0
  22. data/examples/sample_images/barh_bokeh.html +85 -0
  23. data/examples/sample_images/barh_gruff.png +0 -0
  24. data/examples/sample_images/box_plot_bokeh.html +85 -0
  25. data/examples/sample_images/{boxplot_pyplot.png → box_plot_pyplot.png} +0 -0
  26. data/examples/sample_images/curve_bokeh.html +85 -0
  27. data/examples/sample_images/curve_with_function_bokeh.html +85 -0
  28. data/examples/sample_images/{errorbar_pyplot.png → error_bar_pyplot.png} +0 -0
  29. data/examples/sample_images/hist_gruff.png +0 -0
  30. data/examples/sample_images/scatter_bokeh.html +85 -0
  31. data/examples/sample_pyplot.ipynb +37 -35
  32. data/images/penguins_body_mass_g_flipper_length_mm_scatter_plot.png +0 -0
  33. data/images/penguins_body_mass_g_flipper_length_mm_species_scatter_plot.png +0 -0
  34. data/images/penguins_body_mass_g_flipper_length_mm_species_sex_scatter_plot.png +0 -0
  35. data/images/penguins_species_body_mass_g_bar_plot_h.png +0 -0
  36. data/images/penguins_species_body_mass_g_bar_plot_v.png +0 -0
  37. data/images/penguins_species_body_mass_g_box_plot_h.png +0 -0
  38. data/images/penguins_species_body_mass_g_box_plot_v.png +0 -0
  39. data/images/penguins_species_body_mass_g_sex_bar_plot_v.png +0 -0
  40. data/images/penguins_species_body_mass_g_sex_box_plot_v.png +0 -0
  41. data/lib/charty.rb +13 -7
  42. data/lib/charty/backend_methods.rb +8 -0
  43. data/lib/charty/backends.rb +80 -0
  44. data/lib/charty/backends/bokeh.rb +80 -0
  45. data/lib/charty/backends/google_charts.rb +267 -0
  46. data/lib/charty/backends/gruff.rb +104 -67
  47. data/lib/charty/backends/plotly.rb +549 -0
  48. data/lib/charty/backends/pyplot.rb +584 -86
  49. data/lib/charty/backends/rubyplot.rb +82 -74
  50. data/lib/charty/backends/unicode_plot.rb +79 -0
  51. data/lib/charty/index.rb +213 -0
  52. data/lib/charty/linspace.rb +1 -1
  53. data/lib/charty/missing_value_support.rb +14 -0
  54. data/lib/charty/plot_methods.rb +184 -0
  55. data/lib/charty/plotter.rb +57 -41
  56. data/lib/charty/plotters.rb +11 -0
  57. data/lib/charty/plotters/abstract_plotter.rb +156 -0
  58. data/lib/charty/plotters/bar_plotter.rb +216 -0
  59. data/lib/charty/plotters/box_plotter.rb +94 -0
  60. data/lib/charty/plotters/categorical_plotter.rb +380 -0
  61. data/lib/charty/plotters/count_plotter.rb +7 -0
  62. data/lib/charty/plotters/estimation_support.rb +84 -0
  63. data/lib/charty/plotters/random_support.rb +25 -0
  64. data/lib/charty/plotters/relational_plotter.rb +518 -0
  65. data/lib/charty/plotters/scatter_plotter.rb +115 -0
  66. data/lib/charty/plotters/vector_plotter.rb +6 -0
  67. data/lib/charty/statistics.rb +114 -0
  68. data/lib/charty/table.rb +82 -3
  69. data/lib/charty/table_adapters.rb +25 -0
  70. data/lib/charty/table_adapters/active_record_adapter.rb +63 -0
  71. data/lib/charty/table_adapters/base_adapter.rb +69 -0
  72. data/lib/charty/table_adapters/daru_adapter.rb +70 -0
  73. data/lib/charty/table_adapters/datasets_adapter.rb +49 -0
  74. data/lib/charty/table_adapters/hash_adapter.rb +224 -0
  75. data/lib/charty/table_adapters/narray_adapter.rb +76 -0
  76. data/lib/charty/table_adapters/nmatrix_adapter.rb +67 -0
  77. data/lib/charty/table_adapters/pandas_adapter.rb +81 -0
  78. data/lib/charty/vector.rb +69 -0
  79. data/lib/charty/vector_adapters.rb +183 -0
  80. data/lib/charty/vector_adapters/array_adapter.rb +109 -0
  81. data/lib/charty/vector_adapters/daru_adapter.rb +171 -0
  82. data/lib/charty/vector_adapters/narray_adapter.rb +187 -0
  83. data/lib/charty/vector_adapters/nmatrix_adapter.rb +37 -0
  84. data/lib/charty/vector_adapters/numpy_adapter.rb +168 -0
  85. data/lib/charty/vector_adapters/pandas_adapter.rb +200 -0
  86. data/lib/charty/version.rb +1 -1
  87. metadata +127 -13
  88. data/.travis.yml +0 -11
  89. data/examples/numo-narray.ipynb +0 -234
  90. data/lib/charty/backends/google_chart.rb +0 -167
  91. data/lib/charty/plotter_adapter.rb +0 -17
@@ -0,0 +1,267 @@
1
+ module Charty
2
+ module Backends
3
+ class GoogleCharts
4
+ Backends.register(:google_charts, self)
5
+
6
+ attr_reader :context
7
+
8
+ class << self
9
+ attr_writer :chart_id, :google_charts_src, :with_api_load_tag
10
+
11
+ def chart_id
12
+ @chart_id ||= 0
13
+ end
14
+
15
+ def with_api_load_tag
16
+ return @with_api_load_tag unless @with_api_load_tag.nil?
17
+
18
+ @with_api_load_tag = true
19
+ end
20
+
21
+ def google_charts_src
22
+ @google_charts_src ||= 'https://www.gstatic.com/charts/loader.js'
23
+ end
24
+ end
25
+
26
+ def initilize
27
+ end
28
+
29
+ def label(x, y)
30
+ end
31
+
32
+ def series=(series)
33
+ @series = series
34
+ end
35
+
36
+ def render(context, filename)
37
+ plot(nil, context)
38
+ end
39
+
40
+ def plot(plot, context)
41
+ @context = context
42
+ self.class.chart_id = self.class.chart_id + 1
43
+
44
+ case context.method
45
+ when :bar
46
+ generate_render_js("ColumnChart")
47
+ when :barh
48
+ generate_render_js("BarChart")
49
+ when :scatter
50
+ generate_render_js("ScatterChart")
51
+ when :bubble
52
+ generate_render_js("BubbleChart")
53
+ when :curve
54
+ generate_render_js("LineChart")
55
+ else
56
+ raise NotImplementedError
57
+ end
58
+ end
59
+
60
+ private
61
+
62
+ def google_charts_load_tag
63
+ if self.class.with_api_load_tag
64
+ "<script type='text/javascript' src='#{self.class.google_charts_src}'></script>"
65
+ else
66
+ nil
67
+ end
68
+ end
69
+
70
+ def data_column_js
71
+ case context.method
72
+ when :bubble
73
+ schema = [
74
+ ["string", "ID"],
75
+ ["number", "X"],
76
+ ["number", "Y"],
77
+ ["string", "GROUP"],
78
+ ["number", "SIZE"],
79
+ ]
80
+ when :curve
81
+ schema = []
82
+ schema << [detect_type(context.series.first.xs), context.xlabel]
83
+ context.series.to_a.each_with_index do |series_data, index|
84
+ schema << ["number", series_data.label || index]
85
+ end
86
+ else
87
+ schema = ["string", context.xlabel]
88
+ context.series.to_a.each_with_index do |series_data, index|
89
+ schema << ["number", series_data.label || index]
90
+ end
91
+ end
92
+
93
+ columns = schema.collect do |type, label|
94
+ "data.addColumn(#{type.to_json}, #{label.to_s.to_json});"
95
+ end
96
+ columns.join
97
+ end
98
+
99
+ def detect_type(values)
100
+ case values.first
101
+ when Time
102
+ "date"
103
+ when String
104
+ "string"
105
+ else
106
+ "number"
107
+ end
108
+ end
109
+
110
+ def x_labels
111
+ labels = {}
112
+ have_string = false
113
+ context.series.each do |series|
114
+ series.xs.each do |x|
115
+ next if labels.key?(x)
116
+ have_string = true if x.is_a?(String)
117
+ labels[x] = true
118
+ end
119
+ end
120
+ if have_string
121
+ labels.keys.sort_by {|label| "%10s" % x.to_s}
122
+ else
123
+ labels.keys.sort
124
+ end
125
+ end
126
+
127
+ def data_hash
128
+ {}.tap do |hash|
129
+ _x_labels = x_labels
130
+ context.series.to_a.each_with_index do |series_data, series_index|
131
+ _x_labels.each do |x_label|
132
+ unless hash[x_label]
133
+ hash[x_label] = []
134
+ end
135
+
136
+ if data_index = series_data.xs.to_a.index(x_label)
137
+ hash[x_label] << series_data.ys.to_a[data_index]
138
+ else
139
+ hash[x_label] << nil
140
+ end
141
+ end
142
+ end
143
+ end
144
+ end
145
+
146
+ def rows
147
+ case context.method
148
+ when :bubble
149
+ [].tap do |data_array|
150
+ context.series.to_a.each_with_index do |series_data, series_index|
151
+ series_data.xs.to_a.each_with_index do |data, data_index|
152
+ data_array << [
153
+ "",
154
+ series_data.xs.to_a[data_index],
155
+ series_data.ys.to_a[data_index],
156
+ series_data[:label] || series_index.to_s,
157
+ series_data.zs.to_a[data_index],
158
+ ]
159
+ end
160
+ end
161
+ end
162
+ when :curve
163
+ [].tap do |data_array|
164
+ data_hash.each do |k, v|
165
+ data_array << [k, v].flatten
166
+ end
167
+ end
168
+ else
169
+ [].tap do |data_array|
170
+ data_hash.each do |k, v|
171
+ data_array << [k, v].flatten
172
+ end
173
+ end
174
+ end
175
+ end
176
+
177
+ def convert_to_javascript(data)
178
+ case data
179
+ when Array
180
+ converted_data = data.collect do |element|
181
+ convert_to_javascript(element)
182
+ end
183
+ "[#{converted_data.join(", ")}]"
184
+ when Time
185
+ time = data.dup.utc
186
+ args = [
187
+ time.year,
188
+ time.month - 1,
189
+ time.day,
190
+ time.hour,
191
+ time.min,
192
+ time.sec,
193
+ time.nsec / 1000 / 1000,
194
+ ]
195
+ "new Date(Date.UTC(#{args.join(", ")}))"
196
+ else
197
+ data.to_json
198
+ end
199
+ end
200
+
201
+ def x_range_option
202
+ x_range = if context.method != :barh
203
+ context&.range&.fetch(:x, nil)
204
+ else
205
+ context&.range&.fetch(:y, nil)
206
+ end
207
+ {
208
+ max: x_range&.max,
209
+ min: x_range&.min,
210
+ }.reject { |_k, v| v.nil? }
211
+ end
212
+
213
+ def y_range_option
214
+ y_range = if context.method != :barh
215
+ context&.range&.fetch(:y, nil)
216
+ else
217
+ context&.range&.fetch(:x, nil)
218
+ end
219
+ {
220
+ max: y_range&.max,
221
+ min: y_range&.min,
222
+ }.reject { |_k, v| v.nil? }
223
+ end
224
+
225
+ def generate_render_js(chart_type)
226
+ js = <<-JS
227
+ #{google_charts_load_tag unless self.class.chart_id > 1}
228
+ <script type="text/javascript">
229
+ google.charts.load("current", {packages:["corechart"]});
230
+ google.charts.setOnLoadCallback(drawChart);
231
+ function drawChart() {
232
+ const data = new google.visualization.DataTable();
233
+ #{data_column_js}
234
+ data.addRows(#{convert_to_javascript(rows)})
235
+
236
+ const view = new google.visualization.DataView(data);
237
+
238
+ const options = {
239
+ title: "#{context.title}",
240
+ vAxis: {
241
+ title: "#{context.ylabel}",
242
+ viewWindow: {
243
+ max: #{y_range_option[:max] || "null"},
244
+ min: #{y_range_option[:min] || "null"},
245
+ },
246
+ },
247
+ hAxis: {
248
+ title: "#{context.xlabel}",
249
+ viewWindow: {
250
+ max: #{x_range_option[:max] || "null"},
251
+ min: #{x_range_option[:min] || "null"},
252
+ }
253
+ },
254
+ legend: { position: "none" },
255
+ };
256
+ const chart = new google.visualization.#{chart_type}(document.getElementById("#{chart_type}-#{self.class.chart_id}"));
257
+ chart.draw(view, options);
258
+ }
259
+ </script>
260
+ <div id="#{chart_type}-#{self.class.chart_id}" style="width: 900px; height: 300px;"></div>
261
+ JS
262
+ js.gsub!(/\"null\"/, 'null')
263
+ js
264
+ end
265
+ end
266
+ end
267
+ end
@@ -1,80 +1,117 @@
1
- require 'gruff'
1
+ require 'fileutils'
2
2
 
3
3
  module Charty
4
- class Gruff < PlotterAdapter
5
- Name = "gruff"
4
+ module Backends
5
+ class Gruff
6
+ Backends.register(:gruff, self)
6
7
 
7
- def initialize
8
- @plot = ::Gruff
9
- end
8
+ class << self
9
+ def prepare
10
+ require 'gruff'
11
+ end
12
+ end
10
13
 
11
- def label(x, y)
12
- end
14
+ def initialize
15
+ @plot = ::Gruff
16
+ end
13
17
 
14
- def series=(series)
15
- @series = series
16
- end
18
+ def label(x, y)
19
+ end
17
20
 
18
- def render_layout(layout)
19
- raise NotImplementedError
20
- end
21
+ def series=(series)
22
+ @series = series
23
+ end
21
24
 
22
- def render(context, filename="")
23
- FileUtils.mkdir_p(File.dirname(filename))
24
- plot(@plot, context).write(filename)
25
- end
25
+ def render_layout(layout)
26
+ raise NotImplementedError
27
+ end
26
28
 
27
- def plot(plot, context)
28
- # case
29
- # when plot.respond_to?(:xlim)
30
- # plot.xlim(context.range_x.begin, context.range_x.end)
31
- # plot.ylim(context.range_y.begin, context.range_y.end)
32
- # when plot.respond_to?(:set_xlim)
33
- # plot.set_xlim(context.range_x.begin, context.range_x.end)
34
- # plot.set_ylim(context.range_y.begin, context.range_y.end)
35
- # end
29
+ def render(context, filename="")
30
+ FileUtils.mkdir_p(File.dirname(filename))
31
+ plot(@plot, context).write(filename)
32
+ end
36
33
 
37
- case context.method
38
- when :bar
39
- p = plot::Bar.new
40
- p.title = context.title if context.title
41
- p.x_axis_label = context.xlabel if context.xlabel
42
- p.y_axis_label = context.ylabel if context.ylabel
43
- context.series.each do |data|
44
- p.data(data.label, data.xs.to_a)
45
- end
46
- p
47
- when :barh
48
- # TODO: To implement
49
- raise NotImplementedError
50
- when :box_plot
51
- # refs. https://github.com/topfunky/gruff/issues/155
52
- raise NotImplementedError
53
- when :bubble
54
- raise NotImplementedError
55
- when :curve
56
- p = plot::Line.new
57
- p.title = context.title if context.title
58
- p.x_axis_label = context.xlabel if context.xlabel
59
- p.y_axis_label = context.ylabel if context.ylabel
60
- context.series.each do |data|
61
- p.data(data.label, data.xs.to_a)
62
- end
63
- p
64
- when :scatter
65
- p = plot::Scatter.new
66
- p.title = context.title if context.title
67
- p.x_axis_label = context.xlabel if context.xlabel
68
- p.y_axis_label = context.ylabel if context.ylabel
69
- context.series.each do |data|
70
- p.data(data.label, data.xs.to_a, data.ys.to_a)
34
+ def plot(plot, context)
35
+ # case
36
+ # when plot.respond_to?(:xlim)
37
+ # plot.xlim(context.range_x.begin, context.range_x.end)
38
+ # plot.ylim(context.range_y.begin, context.range_y.end)
39
+ # when plot.respond_to?(:set_xlim)
40
+ # plot.set_xlim(context.range_x.begin, context.range_x.end)
41
+ # plot.set_ylim(context.range_y.begin, context.range_y.end)
42
+ # end
43
+
44
+ case context.method
45
+ when :bar
46
+ p = plot::Bar.new
47
+ p.title = context.title if context.title
48
+ p.x_axis_label = context.xlabel if context.xlabel
49
+ p.y_axis_label = context.ylabel if context.ylabel
50
+ context.series.each do |data|
51
+ p.data(data.label, data.xs.to_a)
52
+ end
53
+ p
54
+ when :barh
55
+ p = plot::SideBar.new
56
+ p.title = context.title if context.title
57
+ p.x_axis_label = context.xlabel if context.xlabel
58
+ p.y_axis_label = context.ylabel if context.ylabel
59
+ labels = context.series.map {|data| data.xs.to_a}.flatten.uniq
60
+ labels.each do |label|
61
+ data_ys = context.series.map do |data|
62
+ if data.xs.to_a.index(label)
63
+ data.ys.to_a[data.xs.to_a.index(label)]
64
+ else
65
+ 0
66
+ end
67
+ end
68
+ p.data(label, data_ys)
69
+ end
70
+ p.labels = context.series.each_with_index.inject({}) do |attr, (data, i)|
71
+ attr[i] = data.label
72
+ attr
73
+ end
74
+ p
75
+ when :box_plot
76
+ # refs. https://github.com/topfunky/gruff/issues/155
77
+ raise NotImplementedError
78
+ when :bubble
79
+ raise NotImplementedError
80
+ when :curve
81
+ p = plot::Line.new
82
+ p.title = context.title if context.title
83
+ p.x_axis_label = context.xlabel if context.xlabel
84
+ p.y_axis_label = context.ylabel if context.ylabel
85
+ context.series.each do |data|
86
+ p.dataxy(data.label, data.xs.to_a, data.ys.to_a)
87
+ end
88
+ p
89
+ when :scatter
90
+ p = plot::Scatter.new
91
+ p.title = context.title if context.title
92
+ p.x_axis_label = context.xlabel if context.xlabel
93
+ p.y_axis_label = context.ylabel if context.ylabel
94
+ context.series.each do |data|
95
+ p.data(data.label, data.xs.to_a, data.ys.to_a)
96
+ end
97
+ p
98
+ when :error_bar
99
+ # refs. https://github.com/topfunky/gruff/issues/163
100
+ raise NotImplementedError
101
+ when :hist
102
+ p = plot::Histogram.new
103
+ p.title = context.title if context.title
104
+ p.x_axis_label = context.xlabel if context.xlabel
105
+ p.y_axis_label = context.ylabel if context.ylabel
106
+ if context.range_x
107
+ p.minimum_bin = context.range_x.first
108
+ p.maximum_bin = context.range_x.last
109
+ end
110
+ context.data.each do |data|
111
+ p.data('', data.to_a)
112
+ end
113
+ p
71
114
  end
72
- p
73
- when :error_bar
74
- # refs. https://github.com/topfunky/gruff/issues/163
75
- raise NotImplementedError
76
- when :hist
77
- raise NotImplementedError
78
115
  end
79
116
  end
80
117
  end