charty 0.1.5.dev → 0.2.5

Sign up to get free protection for your applications and to get access to all the features.
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