charty 0.2.1 → 0.2.7

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.
Files changed (76) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +56 -23
  3. data/.github/workflows/nmatrix.yml +67 -0
  4. data/.github/workflows/pycall.yml +86 -0
  5. data/Dockerfile.dev +9 -1
  6. data/Gemfile +18 -0
  7. data/README.md +177 -9
  8. data/Rakefile +4 -5
  9. data/charty.gemspec +10 -5
  10. data/examples/palette.rb +1 -1
  11. data/examples/sample_images/hist_gruff.png +0 -0
  12. data/images/penguins_body_mass_g_flipper_length_mm_scatter_plot.png +0 -0
  13. data/images/penguins_body_mass_g_flipper_length_mm_species_scatter_plot.png +0 -0
  14. data/images/penguins_body_mass_g_flipper_length_mm_species_sex_scatter_plot.png +0 -0
  15. data/images/penguins_species_body_mass_g_bar_plot_h.png +0 -0
  16. data/images/penguins_species_body_mass_g_bar_plot_v.png +0 -0
  17. data/images/penguins_species_body_mass_g_box_plot_h.png +0 -0
  18. data/images/penguins_species_body_mass_g_box_plot_v.png +0 -0
  19. data/images/penguins_species_body_mass_g_sex_bar_plot_v.png +0 -0
  20. data/images/penguins_species_body_mass_g_sex_box_plot_v.png +0 -0
  21. data/lib/charty.rb +9 -2
  22. data/lib/charty/backends.rb +1 -0
  23. data/lib/charty/backends/bokeh.rb +2 -2
  24. data/lib/charty/backends/google_charts.rb +1 -1
  25. data/lib/charty/backends/gruff.rb +14 -3
  26. data/lib/charty/backends/plotly.rb +731 -32
  27. data/lib/charty/backends/plotly_helpers/html_renderer.rb +203 -0
  28. data/lib/charty/backends/plotly_helpers/notebook_renderer.rb +86 -0
  29. data/lib/charty/backends/plotly_helpers/plotly_renderer.rb +121 -0
  30. data/lib/charty/backends/pyplot.rb +515 -67
  31. data/lib/charty/backends/rubyplot.rb +1 -1
  32. data/lib/charty/backends/unicode_plot.rb +79 -0
  33. data/lib/charty/cache_dir.rb +27 -0
  34. data/lib/charty/dash_pattern_generator.rb +57 -0
  35. data/lib/charty/index.rb +213 -0
  36. data/lib/charty/iruby_helper.rb +18 -0
  37. data/lib/charty/linspace.rb +1 -1
  38. data/lib/charty/plot_methods.rb +283 -8
  39. data/lib/charty/plotter.rb +2 -2
  40. data/lib/charty/plotters.rb +11 -0
  41. data/lib/charty/plotters/abstract_plotter.rb +188 -18
  42. data/lib/charty/plotters/bar_plotter.rb +189 -7
  43. data/lib/charty/plotters/box_plotter.rb +64 -11
  44. data/lib/charty/plotters/categorical_plotter.rb +272 -40
  45. data/lib/charty/plotters/count_plotter.rb +7 -0
  46. data/lib/charty/plotters/distribution_plotter.rb +143 -0
  47. data/lib/charty/plotters/estimation_support.rb +84 -0
  48. data/lib/charty/plotters/histogram_plotter.rb +182 -0
  49. data/lib/charty/plotters/line_plotter.rb +300 -0
  50. data/lib/charty/plotters/random_support.rb +25 -0
  51. data/lib/charty/plotters/relational_plotter.rb +635 -0
  52. data/lib/charty/plotters/scatter_plotter.rb +80 -0
  53. data/lib/charty/plotters/vector_plotter.rb +6 -0
  54. data/lib/charty/statistics.rb +96 -2
  55. data/lib/charty/table.rb +160 -15
  56. data/lib/charty/table_adapters.rb +2 -0
  57. data/lib/charty/table_adapters/active_record_adapter.rb +17 -9
  58. data/lib/charty/table_adapters/base_adapter.rb +166 -0
  59. data/lib/charty/table_adapters/daru_adapter.rb +39 -3
  60. data/lib/charty/table_adapters/datasets_adapter.rb +13 -2
  61. data/lib/charty/table_adapters/hash_adapter.rb +141 -16
  62. data/lib/charty/table_adapters/narray_adapter.rb +25 -6
  63. data/lib/charty/table_adapters/nmatrix_adapter.rb +15 -5
  64. data/lib/charty/table_adapters/pandas_adapter.rb +163 -0
  65. data/lib/charty/util.rb +28 -0
  66. data/lib/charty/vector.rb +69 -0
  67. data/lib/charty/vector_adapters.rb +187 -0
  68. data/lib/charty/vector_adapters/array_adapter.rb +101 -0
  69. data/lib/charty/vector_adapters/daru_adapter.rb +163 -0
  70. data/lib/charty/vector_adapters/narray_adapter.rb +182 -0
  71. data/lib/charty/vector_adapters/nmatrix_adapter.rb +37 -0
  72. data/lib/charty/vector_adapters/numpy_adapter.rb +168 -0
  73. data/lib/charty/vector_adapters/pandas_adapter.rb +199 -0
  74. data/lib/charty/version.rb +1 -1
  75. metadata +105 -24
  76. data/lib/charty/palette.rb +0 -235
@@ -0,0 +1,25 @@
1
+ module Charty
2
+ module Plotters
3
+ module RandomSupport
4
+ attr_reader :random
5
+
6
+ def random=(random)
7
+ @random = check_random(random)
8
+ end
9
+
10
+ module_function def check_random(random)
11
+ case random
12
+ when nil
13
+ Random.new
14
+ when Integer
15
+ Random.new(random)
16
+ when Random
17
+ random
18
+ else
19
+ raise ArgumentError,
20
+ "invalid value for random (%p for a generator or a seed value)" % value
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,635 @@
1
+ module Charty
2
+ module Plotters
3
+ class BaseMapper
4
+ def initialize(plotter, *params)
5
+ @plotter = plotter
6
+ initialize_mapping(*params)
7
+ end
8
+
9
+ attr_reader :plotter
10
+
11
+ def [](key, *args)
12
+ case key
13
+ when Array, Charty::Vector
14
+ key.map {|k| lookup_single_value(k, *args) }
15
+ else
16
+ lookup_single_value(key, *args)
17
+ end
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
76
+ end
77
+
78
+ class ColorMapper < BaseMapper
79
+ private def initialize_mapping(palette, order, norm)
80
+ @palette = palette
81
+ @order = order
82
+
83
+ if plotter.variables.key?(:color)
84
+ data = plotter.plot_data[:color]
85
+ end
86
+
87
+ if data && data.notnull.any?
88
+ @map_type = infer_map_type(palette, norm, @plotter.input_format, @plotter.var_types[:color])
89
+
90
+ case @map_type
91
+ when :numeric
92
+ @levels, @lookup_table, @norm, @cmap = numeric_mapping(data, palette, norm)
93
+ when :categorical
94
+ @cmap = nil
95
+ @norm = nil
96
+ @levels, @lookup_table = categorical_mapping(data, palette, order)
97
+ else
98
+ raise NotImplementedError,
99
+ "datetime color mapping is not supported"
100
+ end
101
+ end
102
+ end
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
+
149
+ private def categorical_mapping(data, palette, order)
150
+ levels = data.categorical_order(order)
151
+ n_colors = levels.length
152
+
153
+ case palette
154
+ when Hash
155
+ lookup_table = categorical_lut_from_hash(levels, palette, :palette)
156
+ return levels, lookup_table
157
+
158
+ when nil
159
+ current_palette = Palette.default
160
+ if n_colors <= current_palette.n_colors
161
+ colors = Palette.new(current_palette.colors, n_colors).colors
162
+ else
163
+ colors = Palette.husl_colors(n_colors)
164
+ end
165
+
166
+ when Array
167
+ colors = palette
168
+
169
+ else
170
+ colors = Palette.new(palette, n_colors).colors
171
+ end
172
+
173
+ lookup_table = categorical_lut_from_array(levels, colors, :palette)
174
+ return levels, lookup_table
175
+ end
176
+
177
+ private def infer_map_type(palette, norm, input_format, var_type)
178
+ case
179
+ when false # palette is qualitative_palette
180
+ :categorical
181
+ when ! norm.nil?
182
+ :numeric
183
+ when palette.is_a?(Array),
184
+ palette.is_a?(Hash)
185
+ :categorical
186
+ when input_format == :wide
187
+ :categorical
188
+ else
189
+ var_type
190
+ end
191
+ end
192
+
193
+ attr_reader :palette, :norm, :levels, :lookup_table, :map_type
194
+
195
+ def inverse_lookup_table
196
+ lookup_table.invert
197
+ end
198
+
199
+ def lookup_single_value(key)
200
+ if @lookup_table.key?(key)
201
+ @lookup_table[key]
202
+ elsif @norm
203
+ # Use the colormap to interpolate between existing datapoints
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
214
+ end
215
+ end
216
+ end
217
+
218
+ class SizeMapper < BaseMapper
219
+ private def initialize_mapping(sizes, order, norm)
220
+ @sizes = sizes
221
+ @order = order
222
+ @norm = norm
223
+
224
+ return unless plotter.variables.key?(:size)
225
+
226
+ data = plotter.plot_data[:size]
227
+ return unless data.notnull.any?
228
+
229
+ @map_type = infer_map_type(sizes, norm, @plotter.var_types[:size])
230
+ case @map_type
231
+ when :numeric
232
+ @levels, @lookup_table, @norm = numeric_mapping(data, sizes, norm)
233
+ when :categorical
234
+ @levels, @lookup_table = categorical_mapping(data, sizes, order)
235
+ else
236
+ raise NotImplementedError,
237
+ "datetime color mapping is not supported"
238
+ end
239
+ end
240
+
241
+ private def infer_map_type(sizes, norm, var_type)
242
+ case
243
+ when ! norm.nil?
244
+ :numeric
245
+ when sizes.is_a?(Hash),
246
+ sizes.is_a?(Array)
247
+ :categorical
248
+ else
249
+ var_type
250
+ end
251
+ end
252
+
253
+ private def numeric_mapping(data, sizes, norm)
254
+ case sizes
255
+ when Hash
256
+ # The presence of a norm object overrides a dictionary of sizes
257
+ # in specifying a numeric mapping, so we need to process the
258
+ # dictionary here
259
+ levels = sizes.keys.sort
260
+ size_values = sizes.values
261
+ size_range = [size_values.min, size_values.max]
262
+ else
263
+ levels = Charty::Vector.new(data.unique_values).drop_na.to_a
264
+ levels.sort!
265
+
266
+ case sizes
267
+ when Range
268
+ size_range = [sizes.begin, sizes.end]
269
+ when nil
270
+ size_range = [0r, 1r]
271
+ else
272
+ raise ArgumentError,
273
+ "Unable to recognize the value for `sizes`: %p" % sizes
274
+ end
275
+ end
276
+
277
+ # Now we have the minimum and the maximum values of sizes
278
+ case norm
279
+ when nil
280
+ norm = SimpleNormalizer.new
281
+ sizes_scaled = norm.(levels)
282
+ # when Colors::Normalize
283
+ # TODO: Must support red-color's Normalize feature
284
+ else
285
+ raise ArgumentError,
286
+ "Unable to recognize the value for `norm`: %p" % norm
287
+ end
288
+
289
+ case sizes
290
+ when Hash
291
+ # do nothing
292
+ else
293
+ lo, hi = size_range
294
+ sizes = sizes_scaled.map {|x| lo + x * (hi - lo) }
295
+ lookup_table = levels.zip(sizes).to_h
296
+ end
297
+
298
+ return levels, lookup_table, norm
299
+ end
300
+
301
+ private def categorical_mapping(data, sizes, order)
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
323
+ end
324
+
325
+ attr_reader :palette, :order, :norm, :levels, :lookup_table, :map_type
326
+
327
+ def lookup_single_value(key)
328
+ if @lookup_table.key?(key)
329
+ @lookup_table[key]
330
+ else
331
+ normed = @norm.(key) || Float::NAN
332
+ size_values = @lookup_table.values
333
+ min, max = size_values.min, size_values.max
334
+ min + normed * (max - min)
335
+ end
336
+ end
337
+ end
338
+
339
+ class StyleMapper < BaseMapper
340
+ private def initialize_mapping(markers, dashes, order)
341
+ @markers = markers
342
+ @dashes = dashes
343
+ @order = order
344
+
345
+ return unless plotter.variables.key?(:style)
346
+
347
+ data = plotter.plot_data[:style]
348
+ return unless data.notnull.any?
349
+
350
+ @levels = data.categorical_order(order)
351
+
352
+ markers = map_attributes(markers, @levels, unique_markers(@levels.length), :markers)
353
+ dashes = map_attributes(dashes, @levels, unique_dashes(@levels.length), :dashes)
354
+
355
+ @lookup_table = @levels.map {|key|
356
+ record = {
357
+ marker: markers[key],
358
+ dashes: dashes[key]
359
+ }
360
+ record.compact!
361
+ [key, record]
362
+ }.to_h
363
+ end
364
+
365
+ MARKER_NAMES = [
366
+ :circle, :x, :square, :cross, :diamond, :star_diamond,
367
+ :triangle_up, :star_square, :triangle_down, :hexagon, :star, :pentagon,
368
+ ].freeze
369
+
370
+ private def unique_markers(n)
371
+ if n > MARKER_NAMES.length
372
+ raise ArgumentError,
373
+ "Too many markers are required (%p for %p)" % [n, MARKER_NAMES.length]
374
+ end
375
+ MARKER_NAMES[0, n]
376
+ end
377
+
378
+ private def unique_dashes(n)
379
+ DashPatternGenerator.take(n)
380
+ end
381
+
382
+ private def map_attributes(vals, levels, defaults, attr)
383
+ case vals
384
+ when true
385
+ return levels.zip(defaults).to_h
386
+ when Hash
387
+ missing_keys = lavels - vals.keys
388
+ unless missing_keys.empty?
389
+ raise ArgumentError,
390
+ "The `%s` levels are missing values: %p" % [attr, missing_keys]
391
+ end
392
+ return vals
393
+ when Array, Enumerable
394
+ if levels.length != vals.length
395
+ raise ArgumentError,
396
+ "%he `%s` argument has the wrong number of values" % attr
397
+ end
398
+ return levels.zip(vals).to_h
399
+ when nil, false
400
+ return {}
401
+ else
402
+ raise ArgumentError,
403
+ "Unable to recognize the value for `%s`: %p" % [attr, vals]
404
+ end
405
+ end
406
+
407
+ attr_reader :palette, :order, :norm, :lookup_table, :levels
408
+
409
+ def inverse_lookup_table(attr)
410
+ lookup_table.map { |k, v| [v[attr], k] }.to_h
411
+ end
412
+
413
+ def lookup_single_value(key, attr=nil)
414
+ case attr
415
+ when nil
416
+ @lookup_table[key]
417
+ else
418
+ @lookup_table[key][attr]
419
+ end
420
+ end
421
+ end
422
+
423
+ class RelationalPlotter < AbstractPlotter
424
+ def flat_structure
425
+ {
426
+ x: :index,
427
+ y: :values
428
+ }
429
+ end
430
+
431
+ def initialize(x, y, color, style, size, data: nil, **options, &block)
432
+ super(x, y, color, data: data, **options, &block)
433
+
434
+ self.style = style
435
+ self.size = size
436
+
437
+ setup_variables
438
+ end
439
+
440
+ attr_reader :style, :size, :units
441
+
442
+ attr_reader :color_norm
443
+
444
+ attr_reader :sizes, :size_order, :size_norm
445
+
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
453
+
454
+ def style=(val)
455
+ @style = check_dimension(val, :style)
456
+ end
457
+
458
+ def size=(val)
459
+ @size = check_dimension(val, :size)
460
+ end
461
+
462
+ def color_norm=(val)
463
+ unless val.nil?
464
+ raise NotImplementedError,
465
+ "Specifying color_norm is not supported yet"
466
+ end
467
+ end
468
+
469
+ def sizes=(val)
470
+ # NOTE: the value check will be perfomed in SizeMapper
471
+ @sizes = val
472
+ end
473
+
474
+ def size_order=(val)
475
+ unless val.nil?
476
+ raise NotImplementedError,
477
+ "Specifying size_order is not supported yet"
478
+ end
479
+ end
480
+
481
+ def size_norm=(val)
482
+ unless val.nil?
483
+ raise NotImplementedError,
484
+ "Specifying size_order is not supported yet"
485
+ end
486
+ end
487
+
488
+ def markers=(val)
489
+ @markers = check_markers(val)
490
+ end
491
+
492
+ private def check_markers(val)
493
+ # TODO
494
+ val
495
+ end
496
+
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)
507
+ unless val.nil?
508
+ raise NotImplementedError,
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
522
+ end
523
+ end
524
+
525
+ attr_reader :input_format, :plot_data, :variables, :var_types
526
+
527
+ private def setup_variables
528
+ if x.nil? && y.nil?
529
+ @input_format = :wide
530
+ setup_variables_with_wide_form_dataset
531
+ else
532
+ @input_format = :long
533
+ setup_variables_with_long_form_dataset
534
+ end
535
+
536
+ @var_types = @plot_data.columns.map { |k|
537
+ [k, variable_type(@plot_data[k], :categorical)]
538
+ }.to_h
539
+ end
540
+
541
+ private def setup_variables_with_wide_form_dataset
542
+ unless color.nil? && style.nil? && size.nil?
543
+ vars = []
544
+ vars << "color" unless color.nil?
545
+ vars << "style" unless style.nil?
546
+ vars << "size" unless size.nil?
547
+ raise ArgumentError,
548
+ "Unable to assign the following variables in wide-form data: " +
549
+ vars.join(", ")
550
+ end
551
+
552
+ if data.nil? || data.empty?
553
+ @plot_data = Charty::Table.new({})
554
+ @variables = {}
555
+ return
556
+ end
557
+
558
+ flat = data.is_a?(Charty::Vector)
559
+ if flat
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)
575
+ else
576
+ raise NotImplementedError,
577
+ "wide-form input is not supported"
578
+ end
579
+ end
580
+
581
+ private def setup_variables_with_long_form_dataset
582
+ self.data = {} if data.nil?
583
+
584
+ plot_data = {}
585
+ variables = {}
586
+
587
+ {
588
+ x: self.x,
589
+ y: self.y,
590
+ color: self.color,
591
+ style: self.style,
592
+ size: self.size,
593
+ units: self.units
594
+ }.each do |key, val|
595
+ next if val.nil?
596
+
597
+ if data.column?(val)
598
+ plot_data[key] = data[val]
599
+ variables[key] = val
600
+ else
601
+ case val
602
+ when Charty::Vector
603
+ plot_data[key] = val
604
+ variables[key] = val.name
605
+ else
606
+ raise ArgumentError,
607
+ "Could not interpret value %p for parameter %p" % [val, key]
608
+ end
609
+ end
610
+ end
611
+
612
+ @plot_data = Charty::Table.new(plot_data)
613
+ @variables = variables.select do |var, name|
614
+ @plot_data[var].notnull.any?
615
+ end
616
+ end
617
+
618
+ private def annotate_axes(backend)
619
+ # TODO
620
+ end
621
+
622
+ private def map_color(palette: nil, order: nil, norm: nil)
623
+ @color_mapper = ColorMapper.new(self, palette, order, norm)
624
+ end
625
+
626
+ private def map_size(sizes: nil, order: nil, norm: nil)
627
+ @size_mapper = SizeMapper.new(self, sizes, order, norm)
628
+ end
629
+
630
+ private def map_style(markers: nil, dashes: nil, order: nil)
631
+ @style_mapper = StyleMapper.new(self, markers, dashes, order)
632
+ end
633
+ end
634
+ end
635
+ end