charty 0.2.5 → 0.2.6

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