charty 0.1.5.dev → 0.2.5

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 (87) 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 +176 -9
  8. data/Rakefile +4 -5
  9. data/charty.gemspec +10 -1
  10. data/examples/Gemfile +1 -0
  11. data/examples/active_record.ipynb +1 -1
  12. data/examples/daru.ipynb +1 -1
  13. data/examples/iris_dataset.ipynb +1 -1
  14. data/examples/nmatrix.ipynb +1 -1
  15. data/examples/{numo-narray.ipynb → numo_narray.ipynb} +1 -1
  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_images/bar_bokeh.html +85 -0
  21. data/examples/sample_images/barh_bokeh.html +85 -0
  22. data/examples/sample_images/box_plot_bokeh.html +85 -0
  23. data/examples/sample_images/curve_bokeh.html +85 -0
  24. data/examples/sample_images/curve_with_function_bokeh.html +85 -0
  25. data/examples/sample_images/hist_gruff.png +0 -0
  26. data/examples/sample_images/scatter_bokeh.html +85 -0
  27. data/examples/sample_pyplot.ipynb +40 -38
  28. data/images/penguins_body_mass_g_flipper_length_mm_scatter_plot.png +0 -0
  29. data/images/penguins_body_mass_g_flipper_length_mm_species_scatter_plot.png +0 -0
  30. data/images/penguins_body_mass_g_flipper_length_mm_species_sex_scatter_plot.png +0 -0
  31. data/images/penguins_species_body_mass_g_bar_plot_h.png +0 -0
  32. data/images/penguins_species_body_mass_g_bar_plot_v.png +0 -0
  33. data/images/penguins_species_body_mass_g_box_plot_h.png +0 -0
  34. data/images/penguins_species_body_mass_g_box_plot_v.png +0 -0
  35. data/images/penguins_species_body_mass_g_sex_bar_plot_v.png +0 -0
  36. data/images/penguins_species_body_mass_g_sex_box_plot_v.png +0 -0
  37. data/lib/charty.rb +14 -1
  38. data/lib/charty/backend_methods.rb +8 -0
  39. data/lib/charty/backends.rb +80 -0
  40. data/lib/charty/backends/bokeh.rb +32 -26
  41. data/lib/charty/backends/google_charts.rb +267 -0
  42. data/lib/charty/backends/gruff.rb +102 -83
  43. data/lib/charty/backends/plotly.rb +685 -0
  44. data/lib/charty/backends/pyplot.rb +586 -92
  45. data/lib/charty/backends/rubyplot.rb +82 -74
  46. data/lib/charty/backends/unicode_plot.rb +79 -0
  47. data/lib/charty/index.rb +213 -0
  48. data/lib/charty/linspace.rb +1 -1
  49. data/lib/charty/missing_value_support.rb +14 -0
  50. data/lib/charty/plot_methods.rb +184 -0
  51. data/lib/charty/plotter.rb +48 -40
  52. data/lib/charty/plotters.rb +11 -0
  53. data/lib/charty/plotters/abstract_plotter.rb +183 -0
  54. data/lib/charty/plotters/bar_plotter.rb +201 -0
  55. data/lib/charty/plotters/box_plotter.rb +79 -0
  56. data/lib/charty/plotters/categorical_plotter.rb +380 -0
  57. data/lib/charty/plotters/count_plotter.rb +7 -0
  58. data/lib/charty/plotters/estimation_support.rb +84 -0
  59. data/lib/charty/plotters/random_support.rb +25 -0
  60. data/lib/charty/plotters/relational_plotter.rb +518 -0
  61. data/lib/charty/plotters/scatter_plotter.rb +104 -0
  62. data/lib/charty/plotters/vector_plotter.rb +6 -0
  63. data/lib/charty/statistics.rb +114 -0
  64. data/lib/charty/table.rb +80 -3
  65. data/lib/charty/table_adapters.rb +25 -0
  66. data/lib/charty/table_adapters/active_record_adapter.rb +63 -0
  67. data/lib/charty/table_adapters/base_adapter.rb +69 -0
  68. data/lib/charty/table_adapters/daru_adapter.rb +70 -0
  69. data/lib/charty/table_adapters/datasets_adapter.rb +49 -0
  70. data/lib/charty/table_adapters/hash_adapter.rb +224 -0
  71. data/lib/charty/table_adapters/narray_adapter.rb +76 -0
  72. data/lib/charty/table_adapters/nmatrix_adapter.rb +67 -0
  73. data/lib/charty/table_adapters/pandas_adapter.rb +81 -0
  74. data/lib/charty/util.rb +20 -0
  75. data/lib/charty/vector.rb +69 -0
  76. data/lib/charty/vector_adapters.rb +183 -0
  77. data/lib/charty/vector_adapters/array_adapter.rb +109 -0
  78. data/lib/charty/vector_adapters/daru_adapter.rb +171 -0
  79. data/lib/charty/vector_adapters/narray_adapter.rb +187 -0
  80. data/lib/charty/vector_adapters/nmatrix_adapter.rb +37 -0
  81. data/lib/charty/vector_adapters/numpy_adapter.rb +168 -0
  82. data/lib/charty/vector_adapters/pandas_adapter.rb +200 -0
  83. data/lib/charty/version.rb +1 -1
  84. metadata +179 -10
  85. data/.travis.yml +0 -11
  86. data/lib/charty/backends/google_chart.rb +0 -167
  87. 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 old_style_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,98 +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 old_style_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
- p = plot::SideBar.new
49
- p.title = context.title if context.title
50
- p.x_axis_label = context.xlabel if context.xlabel
51
- p.y_axis_label = context.ylabel if context.ylabel
52
- labels = context.series.map {|data| data.xs.to_a}.flatten.uniq
53
- labels.each do |label|
54
- data_ys = context.series.map do |data|
55
- if data.xs.to_a.index(label)
56
- data.ys.to_a[data.xs.to_a.index(label)]
57
- else
58
- 0
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
59
67
  end
68
+ p.data(label, data_ys)
60
69
  end
61
- p.data(label, data_ys)
62
- end
63
- p.labels = context.series.each_with_index.inject({}) do |attr, (data, i)|
64
- attr[i] = data.label
65
- attr
66
- end
67
- p
68
- when :box_plot
69
- # refs. https://github.com/topfunky/gruff/issues/155
70
- raise NotImplementedError
71
- when :bubble
72
- raise NotImplementedError
73
- when :curve
74
- p = plot::Line.new
75
- p.title = context.title if context.title
76
- p.x_axis_label = context.xlabel if context.xlabel
77
- p.y_axis_label = context.ylabel if context.ylabel
78
- context.series.each do |data|
79
- p.data(data.label, data.xs.to_a)
80
- end
81
- p
82
- when :scatter
83
- p = plot::Scatter.new
84
- p.title = context.title if context.title
85
- p.x_axis_label = context.xlabel if context.xlabel
86
- p.y_axis_label = context.ylabel if context.ylabel
87
- context.series.each do |data|
88
- p.data(data.label, data.xs.to_a, data.ys.to_a)
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
89
114
  end
90
- p
91
- when :error_bar
92
- # refs. https://github.com/topfunky/gruff/issues/163
93
- raise NotImplementedError
94
- when :hist
95
- raise NotImplementedError
96
115
  end
97
116
  end
98
117
  end