charty 0.2.3 → 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 +56 -23
- data/.github/workflows/nmatrix.yml +67 -0
- data/.github/workflows/pycall.yml +86 -0
- data/Gemfile +18 -0
- data/README.md +123 -4
- data/Rakefile +4 -5
- data/charty.gemspec +1 -3
- data/examples/sample_images/hist_gruff.png +0 -0
- 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 +4 -0
- data/lib/charty/backends/gruff.rb +13 -2
- data/lib/charty/backends/plotly.rb +322 -20
- data/lib/charty/backends/pyplot.rb +416 -64
- 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 +173 -8
- data/lib/charty/plotters.rb +7 -0
- data/lib/charty/plotters/abstract_plotter.rb +87 -12
- data/lib/charty/plotters/bar_plotter.rb +200 -3
- data/lib/charty/plotters/box_plotter.rb +75 -7
- data/lib/charty/plotters/categorical_plotter.rb +272 -40
- 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 +87 -2
- data/lib/charty/table.rb +50 -15
- data/lib/charty/table_adapters.rb +2 -0
- data/lib/charty/table_adapters/active_record_adapter.rb +17 -9
- data/lib/charty/table_adapters/base_adapter.rb +69 -0
- data/lib/charty/table_adapters/daru_adapter.rb +37 -3
- data/lib/charty/table_adapters/datasets_adapter.rb +6 -2
- data/lib/charty/table_adapters/hash_adapter.rb +130 -16
- data/lib/charty/table_adapters/narray_adapter.rb +25 -6
- data/lib/charty/table_adapters/nmatrix_adapter.rb +15 -5
- 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 +33 -45
data/charty.gemspec
CHANGED
@@ -28,14 +28,12 @@ Gem::Specification.new do |spec|
|
|
28
28
|
|
29
29
|
spec.add_dependency "red-colors"
|
30
30
|
spec.add_dependency "red-palette", ">= 0.2.0"
|
31
|
+
|
31
32
|
spec.add_development_dependency "bundler", ">= 1.16"
|
32
33
|
spec.add_development_dependency "rake"
|
33
34
|
spec.add_development_dependency "test-unit"
|
34
|
-
spec.add_development_dependency "numo-narray"
|
35
|
-
spec.add_development_dependency "nmatrix"
|
36
35
|
spec.add_development_dependency "red-datasets", ">= 0.0.9"
|
37
36
|
spec.add_development_dependency "daru"
|
38
37
|
spec.add_development_dependency "activerecord"
|
39
38
|
spec.add_development_dependency "sqlite3"
|
40
|
-
spec.add_development_dependency "matplotlib"
|
41
39
|
end
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
data/lib/charty.rb
CHANGED
@@ -6,10 +6,14 @@ require "palette"
|
|
6
6
|
require_relative "charty/backends"
|
7
7
|
require_relative "charty/backend_methods"
|
8
8
|
require_relative "charty/plotter"
|
9
|
+
require_relative "charty/index"
|
9
10
|
require_relative "charty/layout"
|
10
11
|
require_relative "charty/linspace"
|
12
|
+
require_relative "charty/missing_value_support"
|
11
13
|
require_relative "charty/plotters"
|
12
14
|
require_relative "charty/plot_methods"
|
13
15
|
require_relative "charty/table_adapters"
|
14
16
|
require_relative "charty/table"
|
15
17
|
require_relative "charty/statistics"
|
18
|
+
require_relative "charty/vector_adapters"
|
19
|
+
require_relative "charty/vector"
|
@@ -83,7 +83,7 @@ module Charty
|
|
83
83
|
p.x_axis_label = context.xlabel if context.xlabel
|
84
84
|
p.y_axis_label = context.ylabel if context.ylabel
|
85
85
|
context.series.each do |data|
|
86
|
-
p.
|
86
|
+
p.dataxy(data.label, data.xs.to_a, data.ys.to_a)
|
87
87
|
end
|
88
88
|
p
|
89
89
|
when :scatter
|
@@ -99,7 +99,18 @@ module Charty
|
|
99
99
|
# refs. https://github.com/topfunky/gruff/issues/163
|
100
100
|
raise NotImplementedError
|
101
101
|
when :hist
|
102
|
-
|
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
|
103
114
|
end
|
104
115
|
end
|
105
116
|
end
|
@@ -1,4 +1,5 @@
|
|
1
|
-
require
|
1
|
+
require "json"
|
2
|
+
require "securerandom"
|
2
3
|
|
3
4
|
module Charty
|
4
5
|
module Backends
|
@@ -117,32 +118,254 @@ module Charty
|
|
117
118
|
|
118
119
|
def begin_figure
|
119
120
|
@traces = []
|
120
|
-
@layout = {}
|
121
|
+
@layout = {showlegend: false}
|
121
122
|
end
|
122
123
|
|
123
|
-
def bar(bar_pos, values,
|
124
|
-
|
125
|
-
|
124
|
+
def bar(bar_pos, group_names, values, colors, orient, label: nil, width: 0.8r,
|
125
|
+
align: :center, conf_int: nil, error_colors: nil, error_width: nil, cap_size: nil)
|
126
|
+
bar_pos = Array(bar_pos)
|
127
|
+
values = Array(values)
|
128
|
+
colors = Array(colors).map(&:to_hex_string)
|
129
|
+
|
130
|
+
if orient == :v
|
131
|
+
x, y = bar_pos, values
|
132
|
+
x = group_names unless group_names.nil?
|
133
|
+
else
|
134
|
+
x, y = values, bar_pos
|
135
|
+
y = group_names unless group_names.nil?
|
136
|
+
end
|
137
|
+
|
138
|
+
trace = {
|
126
139
|
type: :bar,
|
127
|
-
|
128
|
-
|
129
|
-
|
140
|
+
orientation: orient,
|
141
|
+
x: x,
|
142
|
+
y: y,
|
143
|
+
width: width,
|
144
|
+
marker: {color: colors}
|
130
145
|
}
|
131
|
-
|
146
|
+
trace[:name] = label unless label.nil?
|
147
|
+
|
148
|
+
unless conf_int.nil?
|
149
|
+
errors_low = conf_int.map.with_index {|(low, _), i| values[i] - low }
|
150
|
+
errors_high = conf_int.map.with_index {|(_, high), i| high - values[i] }
|
151
|
+
|
152
|
+
error_bar = {
|
153
|
+
type: :data,
|
154
|
+
visible: true,
|
155
|
+
symmetric: false,
|
156
|
+
array: errors_high,
|
157
|
+
arrayminus: errors_low,
|
158
|
+
color: error_colors[0].to_hex_string
|
159
|
+
}
|
160
|
+
error_bar[:thickness] = error_width unless error_width.nil?
|
161
|
+
error_bar[:width] = cap_size unless cap_size.nil?
|
162
|
+
|
163
|
+
error_bar_key = orient == :v ? :error_y : :error_x
|
164
|
+
trace[error_bar_key] = error_bar
|
165
|
+
end
|
166
|
+
|
167
|
+
@traces << trace
|
168
|
+
|
169
|
+
if group_names
|
170
|
+
@layout[:barmode] = :group
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
def box_plot(plot_data, group_names,
|
175
|
+
orient:, colors:, gray:, dodge:, width: 0.8r,
|
176
|
+
flier_size: 5, whisker: 1.5, notch: false)
|
177
|
+
colors = Array(colors).map(&:to_hex_string)
|
178
|
+
gray = gray.to_hex_string
|
179
|
+
width = Float(width)
|
180
|
+
flier_size = Float(width)
|
181
|
+
whisker = Float(whisker)
|
182
|
+
|
183
|
+
traces = plot_data.map.with_index do |group_data, i|
|
184
|
+
group_data = Array(group_data)
|
185
|
+
trace = {
|
186
|
+
type: :box,
|
187
|
+
orientation: orient,
|
188
|
+
name: group_names[i],
|
189
|
+
marker: {color: colors[i]}
|
190
|
+
}
|
191
|
+
if orient == :v
|
192
|
+
trace.update(y: group_data)
|
193
|
+
else
|
194
|
+
trace.update(x: group_data)
|
195
|
+
end
|
196
|
+
|
197
|
+
trace
|
198
|
+
end
|
199
|
+
|
200
|
+
traces.reverse! if orient == :h
|
201
|
+
|
202
|
+
@traces.concat(traces)
|
203
|
+
end
|
204
|
+
|
205
|
+
def grouped_box_plot(plot_data, group_names, color_names,
|
206
|
+
orient:, colors:, gray:, dodge:, width: 0.8r,
|
207
|
+
flier_size: 5, whisker: 1.5, notch: false)
|
208
|
+
colors = Array(colors).map(&:to_hex_string)
|
209
|
+
gray = gray.to_hex_string
|
210
|
+
width = Float(width)
|
211
|
+
flier_size = Float(width)
|
212
|
+
whisker = Float(whisker)
|
213
|
+
|
214
|
+
@layout[:boxmode] = :group
|
215
|
+
|
216
|
+
if orient == :h
|
217
|
+
@layout[:xaxis] ||= {}
|
218
|
+
@layout[:xaxis][:zeroline] = false
|
219
|
+
|
220
|
+
plot_data = plot_data.map {|d| d.reverse }
|
221
|
+
group_names = group_names.reverse
|
222
|
+
end
|
223
|
+
|
224
|
+
traces = color_names.map.with_index do |color_name, i|
|
225
|
+
group_keys = group_names.flat_map.with_index { |name, j|
|
226
|
+
Array.new(plot_data[i][j].length, name)
|
227
|
+
}.flatten
|
228
|
+
|
229
|
+
values = plot_data[i].flat_map {|d| Array(d) }
|
230
|
+
|
231
|
+
trace = {
|
232
|
+
type: :box,
|
233
|
+
orientation: orient,
|
234
|
+
name: color_name,
|
235
|
+
marker: {color: colors[i]}
|
236
|
+
}
|
237
|
+
|
238
|
+
if orient == :v
|
239
|
+
trace.update(y: values, x: group_keys)
|
240
|
+
else
|
241
|
+
trace.update(x: values, y: group_keys)
|
242
|
+
end
|
243
|
+
|
244
|
+
trace
|
245
|
+
end
|
246
|
+
|
247
|
+
@traces.concat(traces)
|
132
248
|
end
|
133
249
|
|
134
|
-
def
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
250
|
+
def scatter(x, y, variables, legend:, color:, color_mapper:,
|
251
|
+
style:, style_mapper:, size:, size_mapper:)
|
252
|
+
if legend == :full
|
253
|
+
warn("Plotly backend does not support full verbosity legend")
|
254
|
+
end
|
255
|
+
|
256
|
+
orig_x, orig_y = x, y
|
257
|
+
|
258
|
+
x = case x
|
259
|
+
when Charty::Vector
|
260
|
+
x.to_a
|
261
|
+
else
|
262
|
+
Array.try_convert(x)
|
263
|
+
end
|
264
|
+
if x.nil?
|
265
|
+
raise ArgumentError, "Invalid value for x: %p" % orig_x
|
266
|
+
end
|
267
|
+
|
268
|
+
y = case y
|
269
|
+
when Charty::Vector
|
270
|
+
y.to_a
|
271
|
+
else
|
272
|
+
Array.try_convert(y)
|
273
|
+
end
|
274
|
+
if y.nil?
|
275
|
+
raise ArgumentError, "Invalid value for y: %p" % orig_y
|
276
|
+
end
|
277
|
+
|
278
|
+
unless color.nil? && style.nil?
|
279
|
+
grouped_scatter(x, y, variables, legend: legend,
|
280
|
+
color: color, color_mapper: color_mapper,
|
281
|
+
style: style, style_mapper: style_mapper,
|
282
|
+
size: size, size_mapper: size_mapper)
|
283
|
+
return
|
144
284
|
end
|
145
|
-
|
285
|
+
|
286
|
+
trace = {
|
287
|
+
type: :scatter,
|
288
|
+
mode: :markers,
|
289
|
+
x: x,
|
290
|
+
y: y,
|
291
|
+
marker: {
|
292
|
+
line: {
|
293
|
+
width: 1,
|
294
|
+
color: "#fff"
|
295
|
+
},
|
296
|
+
size: 10
|
297
|
+
}
|
298
|
+
}
|
299
|
+
|
300
|
+
unless size.nil?
|
301
|
+
trace[:marker][:size] = size_mapper[size].map {|x| 6.0 + x * 6.0 }
|
302
|
+
end
|
303
|
+
|
304
|
+
@traces << trace
|
305
|
+
end
|
306
|
+
|
307
|
+
private def grouped_scatter(x, y, variables, legend:, color:, color_mapper:,
|
308
|
+
style:, style_mapper:, size:, size_mapper:)
|
309
|
+
@layout[:showlegend] = true
|
310
|
+
|
311
|
+
groups = (0 ... x.length).group_by do |i|
|
312
|
+
key = {}
|
313
|
+
key[:color] = color[i] unless color.nil?
|
314
|
+
key[:style] = style[i] unless style.nil?
|
315
|
+
key
|
316
|
+
end
|
317
|
+
|
318
|
+
groups.each do |group_key, indices|
|
319
|
+
trace = {
|
320
|
+
type: :scatter,
|
321
|
+
mode: :markers,
|
322
|
+
x: x.values_at(*indices),
|
323
|
+
y: y.values_at(*indices),
|
324
|
+
marker: {
|
325
|
+
line: {
|
326
|
+
width: 1,
|
327
|
+
color: "#fff"
|
328
|
+
},
|
329
|
+
size: 10
|
330
|
+
}
|
331
|
+
}
|
332
|
+
|
333
|
+
unless size.nil?
|
334
|
+
vals = size.values_at(*indices)
|
335
|
+
trace[:marker][:size] = size_mapper[vals].map(&method(:scale_scatter_point_size))
|
336
|
+
end
|
337
|
+
|
338
|
+
name = []
|
339
|
+
legend_title = []
|
340
|
+
|
341
|
+
if group_key.key?(:color)
|
342
|
+
trace[:marker][:color] = color_mapper[group_key[:color]].to_hex_string
|
343
|
+
name << group_key[:color]
|
344
|
+
legend_title << variables[:color]
|
345
|
+
end
|
346
|
+
|
347
|
+
if group_key.key?(:style)
|
348
|
+
trace[:marker][:symbol] = style_mapper[group_key[:style], :marker]
|
349
|
+
name << group_key[:style]
|
350
|
+
legend_title << variables[:style]
|
351
|
+
end
|
352
|
+
|
353
|
+
trace[:name] = name.uniq.join(", ") unless name.empty?
|
354
|
+
|
355
|
+
@traces << trace
|
356
|
+
|
357
|
+
unless legend_title.empty?
|
358
|
+
@layout[:legend] ||= {}
|
359
|
+
@layout[:legend][:title] = {text: legend_title.uniq.join(", ")}
|
360
|
+
end
|
361
|
+
end
|
362
|
+
end
|
363
|
+
|
364
|
+
private def scale_scatter_point_size(x)
|
365
|
+
min = 6
|
366
|
+
max = 12
|
367
|
+
|
368
|
+
min + x * (max - min)
|
146
369
|
end
|
147
370
|
|
148
371
|
def set_xlabel(label)
|
@@ -161,21 +384,100 @@ module Charty
|
|
161
384
|
@layout[:xaxis][:tickvals] = values
|
162
385
|
end
|
163
386
|
|
387
|
+
def set_yticks(values)
|
388
|
+
@layout[:yaxis] ||= {}
|
389
|
+
@layout[:yaxis][:tickmode] = "array"
|
390
|
+
@layout[:yaxis][:tickvals] = values
|
391
|
+
end
|
392
|
+
|
164
393
|
def set_xtick_labels(labels)
|
165
394
|
@layout[:xaxis] ||= {}
|
166
395
|
@layout[:xaxis][:tickmode] = "array"
|
167
396
|
@layout[:xaxis][:ticktext] = labels
|
168
397
|
end
|
169
398
|
|
399
|
+
def set_ytick_labels(labels)
|
400
|
+
@layout[:yaxis] ||= {}
|
401
|
+
@layout[:yaxis][:tickmode] = "array"
|
402
|
+
@layout[:yaxis][:ticktext] = labels
|
403
|
+
end
|
404
|
+
|
170
405
|
def set_xlim(min, max)
|
171
406
|
@layout[:xaxis] ||= {}
|
172
407
|
@layout[:xaxis][:range] = [min, max]
|
173
408
|
end
|
174
409
|
|
410
|
+
def set_ylim(min, max)
|
411
|
+
@layout[:yaxis] ||= {}
|
412
|
+
@layout[:yaxis][:range] = [min, max]
|
413
|
+
end
|
414
|
+
|
175
415
|
def disable_xaxis_grid
|
176
416
|
# do nothing
|
177
417
|
end
|
178
418
|
|
419
|
+
def disable_yaxis_grid
|
420
|
+
# do nothing
|
421
|
+
end
|
422
|
+
|
423
|
+
def invert_yaxis
|
424
|
+
@traces.each do |trace|
|
425
|
+
case trace[:type]
|
426
|
+
when :bar
|
427
|
+
trace[:y].reverse!
|
428
|
+
end
|
429
|
+
end
|
430
|
+
|
431
|
+
if @layout[:boxmode] == :group
|
432
|
+
@traces.reverse!
|
433
|
+
end
|
434
|
+
|
435
|
+
if @layout[:yaxis] && @layout[:yaxis][:ticktext]
|
436
|
+
@layout[:yaxis][:ticktext].reverse!
|
437
|
+
end
|
438
|
+
end
|
439
|
+
|
440
|
+
def legend(loc:, title:)
|
441
|
+
@layout[:showlegend] = true
|
442
|
+
@layout[:legend] = {
|
443
|
+
title: {
|
444
|
+
text: title
|
445
|
+
}
|
446
|
+
}
|
447
|
+
# TODO: Handle loc
|
448
|
+
end
|
449
|
+
|
450
|
+
def save(filename, title: nil)
|
451
|
+
html = <<~HTML
|
452
|
+
<!DOCTYPE html>
|
453
|
+
<html>
|
454
|
+
<head>
|
455
|
+
<meta charset="utf-8">
|
456
|
+
<title>%{title}</title>
|
457
|
+
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
|
458
|
+
</head>
|
459
|
+
<body>
|
460
|
+
<div id="%{id}" style="width: 100%%; height:100%%;"></div>
|
461
|
+
<script type="text/javascript">
|
462
|
+
Plotly.newPlot("%{id}", %{data}, %{layout});
|
463
|
+
</script>
|
464
|
+
</body>
|
465
|
+
</html>
|
466
|
+
HTML
|
467
|
+
html %= {
|
468
|
+
title: title || default_html_title,
|
469
|
+
id: SecureRandom.uuid,
|
470
|
+
data: JSON.dump(@traces),
|
471
|
+
layout: JSON.dump(@layout)
|
472
|
+
}
|
473
|
+
File.write(filename, html)
|
474
|
+
nil
|
475
|
+
end
|
476
|
+
|
477
|
+
private def default_html_title
|
478
|
+
"Charty plot"
|
479
|
+
end
|
480
|
+
|
179
481
|
def show
|
180
482
|
unless defined?(IRuby)
|
181
483
|
raise NotImplementedError,
|