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.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +71 -0
- data/.github/workflows/nmatrix.yml +67 -0
- data/.github/workflows/pycall.yml +86 -0
- data/Dockerfile.dev +9 -1
- data/Gemfile +18 -0
- data/README.md +128 -9
- data/Rakefile +4 -5
- data/charty.gemspec +7 -2
- data/examples/Gemfile +1 -0
- data/examples/active_record.ipynb +34 -34
- data/examples/daru.ipynb +71 -29
- data/examples/iris_dataset.ipynb +12 -5
- data/examples/nmatrix.ipynb +30 -30
- data/examples/numo_narray.ipynb +245 -0
- data/examples/palette.rb +71 -0
- data/examples/sample.png +0 -0
- data/examples/sample_bokeh.ipynb +156 -0
- data/examples/sample_google_chart.ipynb +229 -68
- data/examples/sample_gruff.ipynb +148 -133
- data/examples/sample_images/bar_bokeh.html +85 -0
- data/examples/sample_images/barh_bokeh.html +85 -0
- data/examples/sample_images/barh_gruff.png +0 -0
- data/examples/sample_images/box_plot_bokeh.html +85 -0
- data/examples/sample_images/{boxplot_pyplot.png → box_plot_pyplot.png} +0 -0
- data/examples/sample_images/curve_bokeh.html +85 -0
- data/examples/sample_images/curve_with_function_bokeh.html +85 -0
- data/examples/sample_images/{errorbar_pyplot.png → error_bar_pyplot.png} +0 -0
- data/examples/sample_images/hist_gruff.png +0 -0
- data/examples/sample_images/scatter_bokeh.html +85 -0
- data/examples/sample_pyplot.ipynb +37 -35
- data/images/penguins_body_mass_g_flipper_length_mm_scatter_plot.png +0 -0
- data/images/penguins_body_mass_g_flipper_length_mm_species_scatter_plot.png +0 -0
- data/images/penguins_body_mass_g_flipper_length_mm_species_sex_scatter_plot.png +0 -0
- data/images/penguins_species_body_mass_g_bar_plot_h.png +0 -0
- data/images/penguins_species_body_mass_g_bar_plot_v.png +0 -0
- data/images/penguins_species_body_mass_g_box_plot_h.png +0 -0
- data/images/penguins_species_body_mass_g_box_plot_v.png +0 -0
- data/images/penguins_species_body_mass_g_sex_bar_plot_v.png +0 -0
- data/images/penguins_species_body_mass_g_sex_box_plot_v.png +0 -0
- data/lib/charty.rb +13 -7
- data/lib/charty/backend_methods.rb +8 -0
- data/lib/charty/backends.rb +80 -0
- data/lib/charty/backends/bokeh.rb +80 -0
- data/lib/charty/backends/google_charts.rb +267 -0
- data/lib/charty/backends/gruff.rb +104 -67
- data/lib/charty/backends/plotly.rb +549 -0
- data/lib/charty/backends/pyplot.rb +584 -86
- data/lib/charty/backends/rubyplot.rb +82 -74
- data/lib/charty/backends/unicode_plot.rb +79 -0
- data/lib/charty/index.rb +213 -0
- data/lib/charty/linspace.rb +1 -1
- data/lib/charty/missing_value_support.rb +14 -0
- data/lib/charty/plot_methods.rb +184 -0
- data/lib/charty/plotter.rb +57 -41
- data/lib/charty/plotters.rb +11 -0
- data/lib/charty/plotters/abstract_plotter.rb +156 -0
- data/lib/charty/plotters/bar_plotter.rb +216 -0
- data/lib/charty/plotters/box_plotter.rb +94 -0
- data/lib/charty/plotters/categorical_plotter.rb +380 -0
- data/lib/charty/plotters/count_plotter.rb +7 -0
- data/lib/charty/plotters/estimation_support.rb +84 -0
- data/lib/charty/plotters/random_support.rb +25 -0
- data/lib/charty/plotters/relational_plotter.rb +518 -0
- data/lib/charty/plotters/scatter_plotter.rb +115 -0
- data/lib/charty/plotters/vector_plotter.rb +6 -0
- data/lib/charty/statistics.rb +114 -0
- data/lib/charty/table.rb +82 -3
- data/lib/charty/table_adapters.rb +25 -0
- data/lib/charty/table_adapters/active_record_adapter.rb +63 -0
- data/lib/charty/table_adapters/base_adapter.rb +69 -0
- data/lib/charty/table_adapters/daru_adapter.rb +70 -0
- data/lib/charty/table_adapters/datasets_adapter.rb +49 -0
- data/lib/charty/table_adapters/hash_adapter.rb +224 -0
- data/lib/charty/table_adapters/narray_adapter.rb +76 -0
- data/lib/charty/table_adapters/nmatrix_adapter.rb +67 -0
- data/lib/charty/table_adapters/pandas_adapter.rb +81 -0
- data/lib/charty/vector.rb +69 -0
- data/lib/charty/vector_adapters.rb +183 -0
- data/lib/charty/vector_adapters/array_adapter.rb +109 -0
- data/lib/charty/vector_adapters/daru_adapter.rb +171 -0
- data/lib/charty/vector_adapters/narray_adapter.rb +187 -0
- data/lib/charty/vector_adapters/nmatrix_adapter.rb +37 -0
- data/lib/charty/vector_adapters/numpy_adapter.rb +168 -0
- data/lib/charty/vector_adapters/pandas_adapter.rb +200 -0
- data/lib/charty/version.rb +1 -1
- metadata +127 -13
- data/.travis.yml +0 -11
- data/examples/numo-narray.ipynb +0 -234
- data/lib/charty/backends/google_chart.rb +0 -167
- 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 '
|
1
|
+
require 'fileutils'
|
2
2
|
|
3
3
|
module Charty
|
4
|
-
|
5
|
-
|
4
|
+
module Backends
|
5
|
+
class Gruff
|
6
|
+
Backends.register(:gruff, self)
|
6
7
|
|
7
|
-
|
8
|
-
|
9
|
-
|
8
|
+
class << self
|
9
|
+
def prepare
|
10
|
+
require 'gruff'
|
11
|
+
end
|
12
|
+
end
|
10
13
|
|
11
|
-
|
12
|
-
|
14
|
+
def initialize
|
15
|
+
@plot = ::Gruff
|
16
|
+
end
|
13
17
|
|
14
|
-
|
15
|
-
|
16
|
-
end
|
18
|
+
def label(x, y)
|
19
|
+
end
|
17
20
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
+
def series=(series)
|
22
|
+
@series = series
|
23
|
+
end
|
21
24
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
end
|
25
|
+
def render_layout(layout)
|
26
|
+
raise NotImplementedError
|
27
|
+
end
|
26
28
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
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
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
context.
|
44
|
-
|
45
|
-
end
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
p.
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
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
|