charty 0.2.5 → 0.2.6
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/README.md +2 -1
- data/charty.gemspec +8 -5
- data/lib/charty.rb +2 -2
- data/lib/charty/backends/plotly.rb +197 -8
- data/lib/charty/backends/pyplot.rb +135 -44
- data/lib/charty/dash_pattern_generator.rb +57 -0
- data/lib/charty/index.rb +1 -1
- data/lib/charty/plot_methods.rb +73 -3
- data/lib/charty/plotters.rb +1 -0
- data/lib/charty/plotters/abstract_plotter.rb +69 -9
- data/lib/charty/plotters/line_plotter.rb +300 -0
- data/lib/charty/plotters/relational_plotter.rb +213 -96
- data/lib/charty/plotters/scatter_plotter.rb +7 -31
- data/lib/charty/statistics.rb +2 -2
- data/lib/charty/table.rb +124 -14
- data/lib/charty/table_adapters/base_adapter.rb +97 -0
- data/lib/charty/table_adapters/daru_adapter.rb +2 -0
- data/lib/charty/table_adapters/datasets_adapter.rb +7 -0
- data/lib/charty/table_adapters/hash_adapter.rb +13 -2
- data/lib/charty/table_adapters/pandas_adapter.rb +82 -0
- data/lib/charty/util.rb +8 -0
- data/lib/charty/vector_adapters.rb +5 -1
- data/lib/charty/vector_adapters/array_adapter.rb +2 -10
- data/lib/charty/vector_adapters/daru_adapter.rb +3 -11
- data/lib/charty/vector_adapters/narray_adapter.rb +1 -6
- data/lib/charty/vector_adapters/numpy_adapter.rb +1 -1
- data/lib/charty/vector_adapters/pandas_adapter.rb +0 -1
- data/lib/charty/version.rb +1 -1
- metadata +54 -25
- data/lib/charty/missing_value_support.rb +0 -14
@@ -16,29 +16,84 @@ module Charty
|
|
16
16
|
lookup_single_value(key, *args)
|
17
17
|
end
|
18
18
|
end
|
19
|
+
|
20
|
+
private def categorical_lut_from_hash(levels, pairs, name)
|
21
|
+
missing_keys = levels - pairs.keys
|
22
|
+
unless missing_keys.empty?
|
23
|
+
raise ArgumentError,
|
24
|
+
"The `#{name}` hash is missing keys: %p" % missing_keys
|
25
|
+
end
|
26
|
+
pairs.dup
|
27
|
+
end
|
28
|
+
|
29
|
+
private def categorical_lut_from_array(levels, values, name)
|
30
|
+
if levels.length != values.length
|
31
|
+
raise ArgumentError,
|
32
|
+
"The `#{name}` array has the wrong number of values " +
|
33
|
+
"(%d for %d)." % [values.length, levels.length]
|
34
|
+
end
|
35
|
+
levels.zip(values).to_h
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# TODO: This should be replaced with red-colors's Normalize feature
|
40
|
+
class SimpleNormalizer
|
41
|
+
def initialize(vmin=nil, vmax=nil)
|
42
|
+
@vmin = vmin
|
43
|
+
@vmax = vmax
|
44
|
+
end
|
45
|
+
|
46
|
+
attr_accessor :vmin, :vmax
|
47
|
+
|
48
|
+
def call(value, clip=nil)
|
49
|
+
scalar_p = false
|
50
|
+
vector_p = false
|
51
|
+
case value
|
52
|
+
when Charty::Vector
|
53
|
+
vector_p = true
|
54
|
+
value = value.to_a
|
55
|
+
when Array
|
56
|
+
# do nothing
|
57
|
+
else
|
58
|
+
scalar_p = true
|
59
|
+
value = [value]
|
60
|
+
end
|
61
|
+
|
62
|
+
@vmin = value.min if vmin.nil?
|
63
|
+
@vmax = value.max if vmax.nil?
|
64
|
+
|
65
|
+
result = value.map {|x| (x - vmin) / (vmax - vmin).to_f }
|
66
|
+
|
67
|
+
case
|
68
|
+
when scalar_p
|
69
|
+
result[0]
|
70
|
+
when vector_p
|
71
|
+
Charty::Vector.new(result, index: value.index)
|
72
|
+
else
|
73
|
+
result
|
74
|
+
end
|
75
|
+
end
|
19
76
|
end
|
20
77
|
|
21
78
|
class ColorMapper < BaseMapper
|
22
79
|
private def initialize_mapping(palette, order, norm)
|
23
80
|
@palette = palette
|
24
81
|
@order = order
|
25
|
-
@norm = norm
|
26
82
|
|
27
83
|
if plotter.variables.key?(:color)
|
28
84
|
data = plotter.plot_data[:color]
|
29
85
|
end
|
30
86
|
|
31
87
|
if data && data.notnull.any?
|
32
|
-
@map_type = infer_map_type(
|
88
|
+
@map_type = infer_map_type(palette, norm, @plotter.input_format, @plotter.var_types[:color])
|
33
89
|
|
34
90
|
case @map_type
|
35
91
|
when :numeric
|
36
|
-
|
37
|
-
"numeric color mapping is not supported"
|
92
|
+
@levels, @lookup_table, @norm, @cmap = numeric_mapping(data, palette, norm)
|
38
93
|
when :categorical
|
39
94
|
@cmap = nil
|
40
95
|
@norm = nil
|
41
|
-
@levels, @lookup_table = categorical_mapping(data,
|
96
|
+
@levels, @lookup_table = categorical_mapping(data, palette, order)
|
42
97
|
else
|
43
98
|
raise NotImplementedError,
|
44
99
|
"datetime color mapping is not supported"
|
@@ -46,18 +101,59 @@ module Charty
|
|
46
101
|
end
|
47
102
|
end
|
48
103
|
|
104
|
+
private def numeric_mapping(data, palette, norm)
|
105
|
+
case palette
|
106
|
+
when Hash
|
107
|
+
levels = palette.keys.sort
|
108
|
+
colors = palette.values_at(*levels)
|
109
|
+
cmap = Colors::ListedColormap.new(colors)
|
110
|
+
lookup_table = palette.dup
|
111
|
+
else
|
112
|
+
levels = data.drop_na.unique_values
|
113
|
+
levels.sort!
|
114
|
+
|
115
|
+
palette ||= "ch:"
|
116
|
+
cmap = case palette
|
117
|
+
when Colors::Colormap
|
118
|
+
palette
|
119
|
+
else
|
120
|
+
Palette.new(palette, 256).to_colormap
|
121
|
+
end
|
122
|
+
|
123
|
+
case norm
|
124
|
+
when nil
|
125
|
+
norm = SimpleNormalizer.new
|
126
|
+
when Array
|
127
|
+
norm = SimpleNormalizer.new(*norm)
|
128
|
+
#when Colors::Normalizer
|
129
|
+
# TODO: Must support red-color's Normalize feature
|
130
|
+
else
|
131
|
+
raise ArgumentError,
|
132
|
+
"`color_norm` must be nil, Array, or Normalizer object"
|
133
|
+
end
|
134
|
+
|
135
|
+
# initialize norm
|
136
|
+
norm.(data.drop_na.to_a)
|
137
|
+
|
138
|
+
lookup_table = levels.map { |level|
|
139
|
+
[
|
140
|
+
level,
|
141
|
+
cmap[norm.(level)]
|
142
|
+
]
|
143
|
+
}.to_h
|
144
|
+
end
|
145
|
+
|
146
|
+
return levels, lookup_table, norm, cmap
|
147
|
+
end
|
148
|
+
|
49
149
|
private def categorical_mapping(data, palette, order)
|
50
150
|
levels = data.categorical_order(order)
|
51
151
|
n_colors = levels.length
|
52
152
|
|
53
153
|
case palette
|
54
154
|
when Hash
|
55
|
-
|
56
|
-
|
57
|
-
raise ArgumentError,
|
58
|
-
"The palette hash is missing keys: %p" % missing_keys
|
59
|
-
end
|
60
|
-
return levels, palette
|
155
|
+
lookup_table = categorical_lut_from_hash(levels, palette, :palette)
|
156
|
+
return levels, lookup_table
|
61
157
|
|
62
158
|
when nil
|
63
159
|
current_palette = Palette.default
|
@@ -66,17 +162,15 @@ module Charty
|
|
66
162
|
else
|
67
163
|
colors = Palette.husl_colors(n_colors)
|
68
164
|
end
|
165
|
+
|
69
166
|
when Array
|
70
|
-
if palette.length != n_colors
|
71
|
-
raise ArgumentError,
|
72
|
-
"The palette list has the wrong number of colors"
|
73
|
-
end
|
74
167
|
colors = palette
|
168
|
+
|
75
169
|
else
|
76
170
|
colors = Palette.new(palette, n_colors).colors
|
77
171
|
end
|
78
|
-
lookup_table = levels.zip(colors).to_h
|
79
172
|
|
173
|
+
lookup_table = categorical_lut_from_array(levels, colors, :palette)
|
80
174
|
return levels, lookup_table
|
81
175
|
end
|
82
176
|
|
@@ -96,7 +190,7 @@ module Charty
|
|
96
190
|
end
|
97
191
|
end
|
98
192
|
|
99
|
-
attr_reader :palette, :
|
193
|
+
attr_reader :palette, :norm, :levels, :lookup_table, :map_type
|
100
194
|
|
101
195
|
def inverse_lookup_table
|
102
196
|
lookup_table.invert
|
@@ -107,61 +201,21 @@ module Charty
|
|
107
201
|
@lookup_table[key]
|
108
202
|
elsif @norm
|
109
203
|
# Use the colormap to interpolate between existing datapoints
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
# end
|
204
|
+
begin
|
205
|
+
normed = @norm.(key)
|
206
|
+
@cmap[normed]
|
207
|
+
rescue ArgumentError, TypeError => err
|
208
|
+
if Util.nan?(key)
|
209
|
+
return "#000000"
|
210
|
+
else
|
211
|
+
raise err
|
212
|
+
end
|
213
|
+
end
|
121
214
|
end
|
122
215
|
end
|
123
216
|
end
|
124
217
|
|
125
218
|
class SizeMapper < BaseMapper
|
126
|
-
# TODO: This should be replaced with red-colors's Normalize feature
|
127
|
-
class SimpleNormalizer
|
128
|
-
def initialize(vmin=nil, vmax=nil)
|
129
|
-
@vmin = vmin
|
130
|
-
@vmax = vmax
|
131
|
-
end
|
132
|
-
|
133
|
-
attr_accessor :vmin, :vmax
|
134
|
-
|
135
|
-
def call(value, clip=nil)
|
136
|
-
scalar_p = false
|
137
|
-
vector_p = false
|
138
|
-
case value
|
139
|
-
when Charty::Vector
|
140
|
-
vector_p = true
|
141
|
-
value = value.to_a
|
142
|
-
when Array
|
143
|
-
# do nothing
|
144
|
-
else
|
145
|
-
scalar_p = true
|
146
|
-
value = [value]
|
147
|
-
end
|
148
|
-
|
149
|
-
@vmin = value.min if vmin.nil?
|
150
|
-
@vmax = value.max if vmax.nil?
|
151
|
-
|
152
|
-
result = value.map {|x| (x - vmin) / (vmax - vmin).to_f }
|
153
|
-
|
154
|
-
case
|
155
|
-
when scalar_p
|
156
|
-
result[0]
|
157
|
-
when vector_p
|
158
|
-
Charty::Vector.new(result, index: value.index)
|
159
|
-
else
|
160
|
-
result
|
161
|
-
end
|
162
|
-
end
|
163
|
-
end
|
164
|
-
|
165
219
|
private def initialize_mapping(sizes, order, norm)
|
166
220
|
@sizes = sizes
|
167
221
|
@order = order
|
@@ -245,11 +299,30 @@ module Charty
|
|
245
299
|
end
|
246
300
|
|
247
301
|
private def categorical_mapping(data, sizes, order)
|
248
|
-
|
249
|
-
|
302
|
+
levels = data.categorical_order(order)
|
303
|
+
|
304
|
+
case sizes
|
305
|
+
when Hash
|
306
|
+
lookup_table = categorical_lut_from_hash(levels, sizes, :sizes)
|
307
|
+
return levels, lookup_table
|
308
|
+
|
309
|
+
when Array
|
310
|
+
# nothing to do
|
311
|
+
|
312
|
+
when Range, nil
|
313
|
+
# Reverse values to use the largest size for the first category
|
314
|
+
size_range = sizes || (0r .. 1r)
|
315
|
+
sizes = Linspace.new(size_range, levels.length).reverse_each.to_a
|
316
|
+
else
|
317
|
+
raise ArgumentError,
|
318
|
+
"Unable to recognize the value for `sizes`: %p" % sizes
|
319
|
+
end
|
320
|
+
|
321
|
+
lookup_table = categorical_lut_from_array(levels, sizes, :sizes)
|
322
|
+
return levels, lookup_table
|
250
323
|
end
|
251
324
|
|
252
|
-
attr_reader :palette, :order, :norm, :levels
|
325
|
+
attr_reader :palette, :order, :norm, :levels, :lookup_table, :map_type
|
253
326
|
|
254
327
|
def lookup_single_value(key)
|
255
328
|
if @lookup_table.key?(key)
|
@@ -261,8 +334,6 @@ module Charty
|
|
261
334
|
min + normed * (max - min)
|
262
335
|
end
|
263
336
|
end
|
264
|
-
|
265
|
-
# TODO
|
266
337
|
end
|
267
338
|
|
268
339
|
class StyleMapper < BaseMapper
|
@@ -279,13 +350,14 @@ module Charty
|
|
279
350
|
@levels = data.categorical_order(order)
|
280
351
|
|
281
352
|
markers = map_attributes(markers, @levels, unique_markers(@levels.length), :markers)
|
282
|
-
|
283
|
-
# TODO: dashes support
|
353
|
+
dashes = map_attributes(dashes, @levels, unique_dashes(@levels.length), :dashes)
|
284
354
|
|
285
355
|
@lookup_table = @levels.map {|key|
|
286
356
|
record = {
|
287
|
-
marker: markers[key]
|
357
|
+
marker: markers[key],
|
358
|
+
dashes: dashes[key]
|
288
359
|
}
|
360
|
+
record.compact!
|
289
361
|
[key, record]
|
290
362
|
}.to_h
|
291
363
|
end
|
@@ -303,6 +375,10 @@ module Charty
|
|
303
375
|
MARKER_NAMES[0, n]
|
304
376
|
end
|
305
377
|
|
378
|
+
private def unique_dashes(n)
|
379
|
+
DashPatternGenerator.take(n)
|
380
|
+
end
|
381
|
+
|
306
382
|
private def map_attributes(vals, levels, defaults, attr)
|
307
383
|
case vals
|
308
384
|
when true
|
@@ -320,7 +396,7 @@ module Charty
|
|
320
396
|
"%he `%s` argument has the wrong number of values" % attr
|
321
397
|
end
|
322
398
|
return levels.zip(vals).to_h
|
323
|
-
when nil
|
399
|
+
when nil, false
|
324
400
|
return {}
|
325
401
|
else
|
326
402
|
raise ArgumentError,
|
@@ -345,6 +421,13 @@ module Charty
|
|
345
421
|
end
|
346
422
|
|
347
423
|
class RelationalPlotter < AbstractPlotter
|
424
|
+
def flat_structure
|
425
|
+
{
|
426
|
+
x: :index,
|
427
|
+
y: :values
|
428
|
+
}
|
429
|
+
end
|
430
|
+
|
348
431
|
def initialize(x, y, color, style, size, data: nil, **options, &block)
|
349
432
|
super(x, y, color, data: data, **options, &block)
|
350
433
|
|
@@ -354,13 +437,19 @@ module Charty
|
|
354
437
|
setup_variables
|
355
438
|
end
|
356
439
|
|
357
|
-
attr_reader :style, :size
|
440
|
+
attr_reader :style, :size, :units
|
358
441
|
|
359
442
|
attr_reader :color_norm
|
360
443
|
|
361
444
|
attr_reader :sizes, :size_order, :size_norm
|
362
445
|
|
363
|
-
attr_reader :markers, :
|
446
|
+
attr_reader :markers, :dashes, :style_order
|
447
|
+
|
448
|
+
attr_reader :legend
|
449
|
+
|
450
|
+
def units=(units)
|
451
|
+
@units = check_dimension(units, :units)
|
452
|
+
end
|
364
453
|
|
365
454
|
def style=(val)
|
366
455
|
@style = check_dimension(val, :style)
|
@@ -378,10 +467,8 @@ module Charty
|
|
378
467
|
end
|
379
468
|
|
380
469
|
def sizes=(val)
|
381
|
-
|
382
|
-
|
383
|
-
"Specifying sizes is not supported yet"
|
384
|
-
end
|
470
|
+
# NOTE: the value check will be perfomed in SizeMapper
|
471
|
+
@sizes = val
|
385
472
|
end
|
386
473
|
|
387
474
|
def size_order=(val)
|
@@ -407,17 +494,38 @@ module Charty
|
|
407
494
|
val
|
408
495
|
end
|
409
496
|
|
410
|
-
def
|
497
|
+
def dashes=(val)
|
498
|
+
@dashes = check_dashes(val)
|
499
|
+
end
|
500
|
+
|
501
|
+
private def check_dashes(val)
|
502
|
+
# TODO
|
503
|
+
val
|
504
|
+
end
|
505
|
+
|
506
|
+
def style_order=(val)
|
411
507
|
unless val.nil?
|
412
508
|
raise NotImplementedError,
|
413
|
-
"Specifying
|
509
|
+
"Specifying style_order is not supported yet"
|
510
|
+
end
|
511
|
+
end
|
512
|
+
|
513
|
+
def legend=(val)
|
514
|
+
case val
|
515
|
+
when :auto, :brief, :full, false
|
516
|
+
@legend = val
|
517
|
+
when "auto", "brief", "full"
|
518
|
+
@legend = val.to_sym
|
519
|
+
else
|
520
|
+
raise ArgumentError,
|
521
|
+
"invalid value of legend (%p for :auto, :brief, :full, or false)" % val
|
414
522
|
end
|
415
523
|
end
|
416
524
|
|
417
525
|
attr_reader :input_format, :plot_data, :variables, :var_types
|
418
526
|
|
419
527
|
private def setup_variables
|
420
|
-
if x.nil? && y.
|
528
|
+
if x.nil? && y.nil?
|
421
529
|
@input_format = :wide
|
422
530
|
setup_variables_with_wide_form_dataset
|
423
531
|
else
|
@@ -447,11 +555,23 @@ module Charty
|
|
447
555
|
return
|
448
556
|
end
|
449
557
|
|
450
|
-
|
451
|
-
flat = false
|
452
|
-
|
558
|
+
flat = data.is_a?(Charty::Vector)
|
453
559
|
if flat
|
454
|
-
|
560
|
+
@plot_data = {}
|
561
|
+
@variables = {}
|
562
|
+
|
563
|
+
[:x, :y].each do |var|
|
564
|
+
case self.flat_structure[var]
|
565
|
+
when :index
|
566
|
+
@plot_data[var] = data.index.to_a
|
567
|
+
@variables[var] = data.index.name
|
568
|
+
when :values
|
569
|
+
@plot_data[var] = data.to_a
|
570
|
+
@variables[var] = data.name
|
571
|
+
end
|
572
|
+
end
|
573
|
+
|
574
|
+
@plot_data = Charty::Table.new(@plot_data)
|
455
575
|
else
|
456
576
|
raise NotImplementedError,
|
457
577
|
"wide-form input is not supported"
|
@@ -459,11 +579,7 @@ module Charty
|
|
459
579
|
end
|
460
580
|
|
461
581
|
private def setup_variables_with_long_form_dataset
|
462
|
-
if data.nil?
|
463
|
-
@plot_data = Charty::Table.new({})
|
464
|
-
@variables = {}
|
465
|
-
return
|
466
|
-
end
|
582
|
+
self.data = {} if data.nil?
|
467
583
|
|
468
584
|
plot_data = {}
|
469
585
|
variables = {}
|
@@ -473,11 +589,12 @@ module Charty
|
|
473
589
|
y: self.y,
|
474
590
|
color: self.color,
|
475
591
|
style: self.style,
|
476
|
-
size: self.size
|
592
|
+
size: self.size,
|
593
|
+
units: self.units
|
477
594
|
}.each do |key, val|
|
478
595
|
next if val.nil?
|
479
596
|
|
480
|
-
if data.
|
597
|
+
if data.column?(val)
|
481
598
|
plot_data[key] = data[val]
|
482
599
|
variables[key] = val
|
483
600
|
else
|