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.
@@ -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(@palette, @norm, @plotter.input_format, @plotter.var_types[:color])
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
- raise NotImplementedError,
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, @palette, @order)
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
- missing_keys = levels - palette.keys
56
- unless missing_keys.empty?
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, :order, :norm, :levels, :lookup_table, :map_type
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
- raise NotImplementedError,
111
- "Palette interpolation is not implemented yet"
112
- # begin
113
- # normed = @norm.(key)
114
- # rescue ArgumentError, TypeError => err
115
- # if key.respond_to?(:nan?) && key.nan?
116
- # return "#000000"
117
- # else
118
- # raise err
119
- # end
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
- raise NotImplementedError,
249
- "A categorical variable for size is not supported"
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, :marker_order
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
- unless val.nil?
382
- raise NotImplementedError,
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 marker_order=(val)
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 marker_order is not supported yet"
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.nl?
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
- # TODO: detect flat data
451
- flat = false
452
-
558
+ flat = data.is_a?(Charty::Vector)
453
559
  if flat
454
- # TODO: Support flat data
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? || data.empty?
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.column_names.include?(val)
597
+ if data.column?(val)
481
598
  plot_data[key] = data[val]
482
599
  variables[key] = val
483
600
  else