charty 0.2.3 → 0.2.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) 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/Gemfile +18 -0
  6. data/README.md +172 -4
  7. data/Rakefile +4 -5
  8. data/charty.gemspec +10 -6
  9. data/examples/sample_images/hist_gruff.png +0 -0
  10. data/images/penguins_body_mass_g_flipper_length_mm_scatter_plot.png +0 -0
  11. data/images/penguins_body_mass_g_flipper_length_mm_species_scatter_plot.png +0 -0
  12. data/images/penguins_body_mass_g_flipper_length_mm_species_sex_scatter_plot.png +0 -0
  13. data/images/penguins_species_body_mass_g_bar_plot_h.png +0 -0
  14. data/images/penguins_species_body_mass_g_bar_plot_v.png +0 -0
  15. data/images/penguins_species_body_mass_g_box_plot_h.png +0 -0
  16. data/images/penguins_species_body_mass_g_box_plot_v.png +0 -0
  17. data/images/penguins_species_body_mass_g_sex_bar_plot_v.png +0 -0
  18. data/images/penguins_species_body_mass_g_sex_box_plot_v.png +0 -0
  19. data/lib/charty.rb +8 -1
  20. data/lib/charty/backends/bokeh.rb +2 -2
  21. data/lib/charty/backends/google_charts.rb +1 -1
  22. data/lib/charty/backends/gruff.rb +14 -3
  23. data/lib/charty/backends/plotly.rb +731 -32
  24. data/lib/charty/backends/plotly_helpers/html_renderer.rb +203 -0
  25. data/lib/charty/backends/plotly_helpers/notebook_renderer.rb +87 -0
  26. data/lib/charty/backends/plotly_helpers/plotly_renderer.rb +121 -0
  27. data/lib/charty/backends/pyplot.rb +514 -66
  28. data/lib/charty/backends/rubyplot.rb +1 -1
  29. data/lib/charty/cache_dir.rb +27 -0
  30. data/lib/charty/dash_pattern_generator.rb +57 -0
  31. data/lib/charty/index.rb +213 -0
  32. data/lib/charty/iruby_helper.rb +18 -0
  33. data/lib/charty/linspace.rb +1 -1
  34. data/lib/charty/plot_methods.rb +283 -8
  35. data/lib/charty/plotter.rb +2 -2
  36. data/lib/charty/plotters.rb +11 -0
  37. data/lib/charty/plotters/abstract_plotter.rb +186 -16
  38. data/lib/charty/plotters/bar_plotter.rb +189 -7
  39. data/lib/charty/plotters/box_plotter.rb +64 -11
  40. data/lib/charty/plotters/categorical_plotter.rb +272 -40
  41. data/lib/charty/plotters/count_plotter.rb +7 -0
  42. data/lib/charty/plotters/distribution_plotter.rb +143 -0
  43. data/lib/charty/plotters/estimation_support.rb +84 -0
  44. data/lib/charty/plotters/histogram_plotter.rb +186 -0
  45. data/lib/charty/plotters/line_plotter.rb +300 -0
  46. data/lib/charty/plotters/random_support.rb +25 -0
  47. data/lib/charty/plotters/relational_plotter.rb +635 -0
  48. data/lib/charty/plotters/scatter_plotter.rb +80 -0
  49. data/lib/charty/plotters/vector_plotter.rb +6 -0
  50. data/lib/charty/statistics.rb +96 -2
  51. data/lib/charty/table.rb +160 -15
  52. data/lib/charty/table_adapters.rb +2 -0
  53. data/lib/charty/table_adapters/active_record_adapter.rb +17 -9
  54. data/lib/charty/table_adapters/base_adapter.rb +166 -0
  55. data/lib/charty/table_adapters/daru_adapter.rb +39 -3
  56. data/lib/charty/table_adapters/datasets_adapter.rb +13 -2
  57. data/lib/charty/table_adapters/hash_adapter.rb +141 -16
  58. data/lib/charty/table_adapters/narray_adapter.rb +25 -6
  59. data/lib/charty/table_adapters/nmatrix_adapter.rb +15 -5
  60. data/lib/charty/table_adapters/pandas_adapter.rb +163 -0
  61. data/lib/charty/util.rb +28 -0
  62. data/lib/charty/vector.rb +69 -0
  63. data/lib/charty/vector_adapters.rb +187 -0
  64. data/lib/charty/vector_adapters/array_adapter.rb +101 -0
  65. data/lib/charty/vector_adapters/daru_adapter.rb +163 -0
  66. data/lib/charty/vector_adapters/narray_adapter.rb +182 -0
  67. data/lib/charty/vector_adapters/nmatrix_adapter.rb +37 -0
  68. data/lib/charty/vector_adapters/numpy_adapter.rb +168 -0
  69. data/lib/charty/vector_adapters/pandas_adapter.rb +199 -0
  70. data/lib/charty/version.rb +1 -1
  71. metadata +92 -25
@@ -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