charty 0.2.6 → 0.2.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/charty.gemspec +2 -1
  3. data/examples/bar_plot.rb +19 -0
  4. data/examples/box_plot.rb +17 -0
  5. data/examples/scatter_plot.rb +17 -0
  6. data/images/penguins_body_mass_g_flipper_length_mm_species_scatter_plot.png +0 -0
  7. data/images/penguins_body_mass_g_flipper_length_mm_species_sex_scatter_plot.png +0 -0
  8. data/images/penguins_species_body_mass_g_bar_plot_h.png +0 -0
  9. data/images/penguins_species_body_mass_g_bar_plot_v.png +0 -0
  10. data/images/penguins_species_body_mass_g_box_plot_h.png +0 -0
  11. data/images/penguins_species_body_mass_g_box_plot_v.png +0 -0
  12. data/images/penguins_species_body_mass_g_sex_bar_plot_v.png +0 -0
  13. data/images/penguins_species_body_mass_g_sex_box_plot_v.png +0 -0
  14. data/lib/charty.rb +2 -0
  15. data/lib/charty/backends/plotly.rb +127 -24
  16. data/lib/charty/backends/plotly_helpers/html_renderer.rb +203 -0
  17. data/lib/charty/backends/plotly_helpers/notebook_renderer.rb +89 -0
  18. data/lib/charty/backends/plotly_helpers/plotly_renderer.rb +121 -0
  19. data/lib/charty/backends/pyplot.rb +74 -0
  20. data/lib/charty/backends/unicode_plot.rb +9 -9
  21. data/lib/charty/cache_dir.rb +27 -0
  22. data/lib/charty/iruby_helper.rb +18 -0
  23. data/lib/charty/plot_methods.rb +82 -6
  24. data/lib/charty/plotters.rb +3 -0
  25. data/lib/charty/plotters/abstract_plotter.rb +56 -16
  26. data/lib/charty/plotters/bar_plotter.rb +39 -0
  27. data/lib/charty/plotters/categorical_plotter.rb +9 -1
  28. data/lib/charty/plotters/distribution_plotter.rb +180 -0
  29. data/lib/charty/plotters/histogram_plotter.rb +244 -0
  30. data/lib/charty/plotters/line_plotter.rb +38 -5
  31. data/lib/charty/plotters/scatter_plotter.rb +4 -2
  32. data/lib/charty/statistics.rb +9 -0
  33. data/lib/charty/table.rb +30 -23
  34. data/lib/charty/table_adapters/base_adapter.rb +88 -0
  35. data/lib/charty/table_adapters/daru_adapter.rb +41 -1
  36. data/lib/charty/table_adapters/hash_adapter.rb +59 -1
  37. data/lib/charty/table_adapters/pandas_adapter.rb +49 -1
  38. data/lib/charty/vector.rb +29 -1
  39. data/lib/charty/vector_adapters.rb +16 -0
  40. data/lib/charty/vector_adapters/pandas_adapter.rb +10 -1
  41. data/lib/charty/version.rb +1 -1
  42. metadata +39 -15
@@ -0,0 +1,89 @@
1
+ module Charty
2
+ module Backends
3
+ module PlotlyHelpers
4
+ class NotebookRenderer < HtmlRenderer
5
+ def initialize(use_cdn: false)
6
+ super(use_cdn: use_cdn, full_html: false, requirejs: true)
7
+ @initialized = false
8
+ end
9
+
10
+ def activate
11
+ return if @initialized
12
+
13
+ unless IRubyHelper.iruby_notebook?
14
+ raise "IRuby is unavailable"
15
+ end
16
+
17
+ if @use_cdn
18
+ script = <<~END_SCRIPT % {win_config: window_plotly_config, mathjax_config: mathjax_config}
19
+ <script type="text/javascript">
20
+ %{win_config}
21
+ %{mathjax_config}
22
+ if (typeof require !== 'undefined') {
23
+ require.undef("plotly");
24
+ requirejs.config({
25
+ paths: {
26
+ 'plotly': ['https://cdn.plot.ly/plotly-latest.min']
27
+ }
28
+ });
29
+ require(['plotly'], function (Plotly) {
30
+ window._Plotly = Plotly;
31
+ });
32
+ }
33
+ </script>
34
+ END_SCRIPT
35
+ else
36
+ script = <<~END_SCRIPT % {script: get_plotlyjs, win_config: window_plotly_config, mathjax_config: mathjax_config}
37
+ <script type="text/javascript">
38
+ %{win_config}
39
+ %{mathjax_config}
40
+ if (typeof require !== 'undefined') {
41
+ require.undef("plotly");
42
+ define('plotly', function (require, exports, module) {
43
+ %{script}
44
+ });
45
+ require(['plotly'], function (Plotly) {
46
+ window._Plotly = Plotly;
47
+ });
48
+ }
49
+ </script>
50
+ END_SCRIPT
51
+ end
52
+ IRuby.display(script, mime: "text/html")
53
+ @initialized = true
54
+ nil
55
+ end
56
+
57
+ def render(figure, element_id: nil, post_script: nil)
58
+ ary = Array.try_convert(post_script)
59
+ post_script = ary || [post_script]
60
+ post_script.unshift(<<~END_POST_SCRIPT)
61
+ var gd = document.getElementById('%{plot_id}');
62
+ var x = new MutationObserver(function (mutations, observer) {
63
+ var display = window.getComputedStyle(gd).display;
64
+ if (!display || display === 'none') {
65
+ console.log([gd, 'removed']);
66
+ Plotly.purge(gd);
67
+ observer.disconnect();
68
+ }
69
+ });
70
+
71
+ // Listen for the removal of the full notebook cell
72
+ var notebookContainer = gd.closest('#notebook-container');
73
+ if (notebookContainer) {
74
+ x.observe(notebookContainer, {childList: true});
75
+ }
76
+
77
+ // Listen for the clearing of the current output cell
78
+ var outputEl = gd.closest('.output');
79
+ if (outputEl) {
80
+ x.observe(outputEl, {childList: true});
81
+ }
82
+ END_POST_SCRIPT
83
+
84
+ super(figure, element_id: element_id, post_script: post_script)
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,121 @@
1
+ require "date"
2
+ require "json"
3
+ require "time"
4
+
5
+ module Charty
6
+ module Backends
7
+ module PlotlyHelpers
8
+ class PlotlyRenderer
9
+ def render(figure)
10
+ json = JSON.generate(figure, allow_nan: true)
11
+ case json
12
+ when /\b(?:Infinity|NaN)\b/
13
+ visit(figure)
14
+ else
15
+ JSON.load(json)
16
+ end
17
+ end
18
+
19
+ private def visit(obj)
20
+ case obj
21
+ when Integer, String, Symbol, true, false, nil
22
+ obj
23
+
24
+ when Numeric
25
+ visit_float(obj)
26
+
27
+ when Time
28
+ visit_time(obj)
29
+
30
+ when Date
31
+ visit_date(obj)
32
+
33
+ when DateTime
34
+ visit_datetime(obj)
35
+
36
+ when Array
37
+ visit_array(obj)
38
+
39
+ when Hash
40
+ visit_hash(obj)
41
+
42
+ when ->(x) { defined?(Numo::NArray) && obj.is_a?(Numo::NArray) }
43
+ visit_array(obj.to_a)
44
+
45
+ when ->(x) { defined?(NMatrix) && obj.is_a?(NMatrix) }
46
+ visit_array(obj.to_a)
47
+
48
+ when ->(x) { defined?(Numpy::NDArray) && obj.is_a?(Numpy::NDArray) }
49
+ visit_array(obj.to_a)
50
+
51
+ when ->(x) { defined?(PyCall::List) && obj.is_a?(PyCall::List) }
52
+ visit_array(obj.to_a)
53
+
54
+ when ->(x) { defined?(PyCall::Tuple) && obj.is_a?(PyCall::Tuple) }
55
+ visit_array(obj.to_a)
56
+
57
+ when ->(x) { defined?(PyCall::Dict) && obj.is_a?(PyCall::Dict) }
58
+ visit_hash(obj.to_h)
59
+
60
+ when ->(x) { defined?(Pandas::Series) && obj.is_a?(Pandas::Series) }
61
+ visit_array(obj.to_a)
62
+
63
+ else
64
+ str = String.try_convert(obj)
65
+ return str unless str.nil?
66
+
67
+ ary = Array.try_convert(obj)
68
+ return visit_array(ary) unless ary.nil?
69
+
70
+ hsh = Hash.try_convert(obj)
71
+ return visit_hash(hsh) unless hsh.nil?
72
+
73
+ type_error(obj)
74
+ end
75
+ end
76
+
77
+ private def visit_float(obj)
78
+ obj = obj.to_f
79
+ rescue RangeError
80
+ type_error(obj)
81
+ else
82
+ case
83
+ when obj.finite?
84
+ obj
85
+ else
86
+ nil
87
+ end
88
+ end
89
+
90
+ private def visit_time(obj)
91
+ obj.iso8601(6)
92
+ end
93
+
94
+ private def visit_date(obj)
95
+ obj.iso8601(6)
96
+ end
97
+
98
+ private def visit_datetime(obj)
99
+ obj.iso8601(6)
100
+ end
101
+
102
+ private def visit_array(obj)
103
+ obj.map {|x| visit(x) }
104
+ end
105
+
106
+ private def visit_hash(obj)
107
+ obj.map { |key, value|
108
+ [
109
+ key,
110
+ visit(value)
111
+ ]
112
+ }.to_h
113
+ end
114
+
115
+ private def type_error(obj)
116
+ raise TypeError, "Unable to convert to JSON: %p" % obj
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end
@@ -14,6 +14,7 @@ module Charty
14
14
 
15
15
  def initialize
16
16
  @pyplot = ::Matplotlib::Pyplot
17
+ @default_edgecolor = Colors["white"].to_rgb
17
18
  @default_line_width = ::Matplotlib.rcParams["lines.linewidth"]
18
19
  @default_marker_size = ::Matplotlib.rcParams["lines.markersize"]
19
20
  end
@@ -575,6 +576,47 @@ module Charty
575
576
  min + x * (max - min)
576
577
  end
577
578
 
579
+ def univariate_histogram(hist, name, variable_name, stat,
580
+ alpha, color, key_color, color_mapper,
581
+ multiple, element, fill, shrink)
582
+ mid_points = hist.edges.each_cons(2).map {|a, b| a + (b - a) / 2 }
583
+ orient = variable_name == :x ? :v : :h
584
+ width = shrink * (hist.edges[1] - hist.edges[0])
585
+
586
+ kw = {align: :edge}
587
+
588
+ color = if color.nil?
589
+ key_color.to_rgb
590
+ else
591
+ color_mapper[color].to_rgb
592
+ end
593
+
594
+ alpha = 1r unless fill
595
+
596
+ if fill
597
+ kw[:facecolor] = color.to_rgba(alpha: alpha).to_hex_string
598
+ if multiple == :stack || multiple == :fill || element == :bars
599
+ kw[:edgecolor] = @default_edgecolor.to_hex_string
600
+ else
601
+ kw[:edgecolor] = color.to_hex_string
602
+ end
603
+ elsif element == :bars
604
+ kw.delete(:facecolor)
605
+ kw[:edgecolor] = color.to_rgba(alpha: alpha).to_hex_string
606
+ else
607
+ kw[:color] = color.to_rgba(alpha: alpha).to_hex_string
608
+ end
609
+
610
+ kw[:label] = name unless name.nil?
611
+
612
+ ax = @pyplot.gca
613
+ if orient == :v
614
+ ax.bar(mid_points, hist.weights, width, **kw)
615
+ else
616
+ ax.barh(mid_points, hist.weights, width, **kw)
617
+ end
618
+ end
619
+
578
620
  private def locator_to_legend_entries(locator, limits)
579
621
  vmin, vmax = limits
580
622
  dtype = case vmin
@@ -620,6 +662,10 @@ module Charty
620
662
  end
621
663
  end
622
664
 
665
+ def set_title(title)
666
+ @pyplot.gca.set_title(String(title))
667
+ end
668
+
623
669
  def set_xlabel(label)
624
670
  @pyplot.gca.set_xlabel(String(label))
625
671
  end
@@ -652,6 +698,27 @@ module Charty
652
698
  @pyplot.gca.set_ylim(Float(min), Float(max))
653
699
  end
654
700
 
701
+ def set_xscale(scale)
702
+ scale = check_scale_type(scale, :xscale)
703
+ @pyplot.gca.set_xscale(scale)
704
+ end
705
+
706
+ def set_yscale(scale)
707
+ scale = check_scale_type(scale, :yscale)
708
+ @pyplot.gca.set_yscale(scale)
709
+ end
710
+
711
+ private def check_scale_type(val, name)
712
+ case
713
+ when :linear, :log
714
+ val
715
+ else
716
+ raise ArgumentError,
717
+ "Invalid #{name} type: %p" % val,
718
+ caller
719
+ end
720
+ end
721
+
655
722
  def disable_xaxis_grid
656
723
  @pyplot.gca.xaxis.grid(false)
657
724
  end
@@ -670,6 +737,12 @@ module Charty
670
737
 
671
738
  def render(notebook: false)
672
739
  show
740
+ nil
741
+ end
742
+
743
+ def render_mimebundle(include: [], exclude: [])
744
+ show
745
+ {}
673
746
  end
674
747
 
675
748
  SAVEFIG_OPTIONAL_PARAMS = [
@@ -685,6 +758,7 @@ module Charty
685
758
  params[key] = kwargs[key] if kwargs.key?(key)
686
759
  end
687
760
  @pyplot.savefig(filename, **params)
761
+ @pyplot.close
688
762
  end
689
763
 
690
764
  def show
@@ -16,7 +16,7 @@ module Charty
16
16
  @layout = {}
17
17
  end
18
18
 
19
- def bar(bar_pos, values, color: nil)
19
+ def bar(bar_pos, _group_names, values, colors, _orient, **kwargs)
20
20
  @figure = {
21
21
  type: :bar,
22
22
  bar_pos: bar_pos,
@@ -24,7 +24,7 @@ module Charty
24
24
  }
25
25
  end
26
26
 
27
- def box_plot(plot_data, positions, color:, gray:)
27
+ def box_plot(plot_data, positions, **kwargs)
28
28
  @figure = { type: :box, data: plot_data }
29
29
  end
30
30
 
@@ -52,13 +52,13 @@ module Charty
52
52
  # do nothing
53
53
  end
54
54
 
55
- def show
56
- case @figure[:type]
57
- when :bar
58
- plot = ::UnicodePlot.barplot(@layout[:xtick_labels], @figure[:values], xlabel: @layout[:xlabel])
59
- when :box
60
- plot = ::UnicodePlot.boxplot(@layout[:xtick_labels], @figure[:data], xlabel: @layout[:xlabel])
61
- end
55
+ def render(**kwargs)
56
+ plot = case @figure[:type]
57
+ when :bar
58
+ ::UnicodePlot.barplot(@layout[:xtick_labels], @figure[:values], xlabel: @layout[:xlabel])
59
+ when :box
60
+ ::UnicodePlot.boxplot(@layout[:xtick_labels], @figure[:data], xlabel: @layout[:xlabel])
61
+ end
62
62
  sio = StringIO.new
63
63
  class << sio
64
64
  def tty?; true; end
@@ -0,0 +1,27 @@
1
+ require "pathname"
2
+
3
+ module Charty
4
+ module CacheDir
5
+ module_function
6
+
7
+ def cache_dir_path
8
+ platform_cache_dir_path + "charty"
9
+ end
10
+
11
+ def platform_cache_dir_path
12
+ base_dir = case RUBY_PLATFORM
13
+ when /mswin/, /mingw/
14
+ ENV.fetch("LOCALAPPDATA", "~/AppData/Local")
15
+ when /darwin/
16
+ "~/Library/Caches"
17
+ else
18
+ ENV.fetch("XDG_CACHE_HOME", "~/.cache")
19
+ end
20
+ Pathname(base_dir).expand_path
21
+ end
22
+
23
+ def path(*path_components)
24
+ cache_dir_path.join(*path_components)
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,18 @@
1
+ module Charty
2
+ module IRubyHelper
3
+ module_function
4
+
5
+ def iruby_notebook?
6
+ # TODO: This cannot distinguish notebook and console.
7
+ defined?(IRuby)
8
+ end
9
+
10
+ def vscode?
11
+ ENV.key?("VSCODE_PID")
12
+ end
13
+
14
+ def nteract?
15
+ ENV.key?("NTERACT_EXE")
16
+ end
17
+ end
18
+ end
@@ -23,19 +23,24 @@ module Charty
23
23
  # @param cap_size Width of the caps on error bars.
24
24
  # @param dodge [true,false] If true, bar position is shifted along the
25
25
  # categorical axis for avoid overlapping when the color-dimension is used.
26
+ # @param log [true,false] Set the value-axis (e.g. Y-axis if orient is :v) to be log scale.
27
+ # @param x_label [String,Symbol,#to_str,nil] X-axis label.
28
+ # @param y_label [String,Symbol,#to_str,nil] Y-axis label.
29
+ # @param title [String,Symbol,#to_str,nil] Title text.
26
30
  def bar_plot(x: nil, y: nil, color: nil, data: nil,
27
31
  order: nil, color_order: nil,
28
32
  estimator: :mean, ci: 95, n_boot: 1000, units: nil, random: nil,
29
33
  orient: nil, key_color: nil, palette: nil, saturation: 1r,
30
34
  error_color: [0.26, 0.26, 0.26], error_width: nil, cap_size: nil,
31
- dodge: true, **options, &block)
35
+ dodge: true, log: false, x_label: nil, y_label: nil, title: nil,
36
+ **options, &block)
32
37
  Plotters::BarPlotter.new(
33
38
  data: data, variables: { x: x, y: y, color: color },
34
39
  order: order, orient: orient,
35
40
  estimator: estimator, ci: ci, n_boot: n_boot, units: units, random: random,
36
41
  color_order: color_order, key_color: key_color, palette: palette, saturation: saturation,
37
42
  error_color: error_color, error_width: error_width, cap_size: cap_size,
38
- dodge: dodge,
43
+ dodge: dodge, log: log, x_label: x_label, y_label: y_label, title: title,
39
44
  **options, &block
40
45
  )
41
46
  end
@@ -43,7 +48,8 @@ module Charty
43
48
  def count_plot(x: nil, y: nil, color: nil, data: nil,
44
49
  order: nil, color_order: nil,
45
50
  orient: nil, key_color: nil, palette: nil, saturation: 1r,
46
- dodge: true, **options, &block)
51
+ dodge: true, log: false, x_label: nil, y_label: nil, title: nil,
52
+ **options, &block)
47
53
  case
48
54
  when x.nil? && !y.nil?
49
55
  x = y
@@ -70,6 +76,9 @@ module Charty
70
76
  palette: palette,
71
77
  saturation: saturation,
72
78
  dodge: dodge,
79
+ x_label: x_label,
80
+ y_label: y_label,
81
+ title: title,
73
82
  **options
74
83
  ) do |plotter|
75
84
  plotter.value_label = "count"
@@ -106,11 +115,15 @@ module Charty
106
115
  # @param whisker Propotion of the IQR past the low and high quartiles to
107
116
  # extend the plot whiskers. Points outside of this range will be
108
117
  # treated as outliers.
118
+ # @param x_label [String,Symbol,#to_str,nil] X-axis label.
119
+ # @param y_label [String,Symbol,#to_str,nil] Y-axis label.
120
+ # @param title [String,Symbol,#to_str,nil] Title text.
109
121
  def box_plot(x: nil, y: nil, color: nil, data: nil,
110
122
  order: nil, color_order: nil,
111
123
  orient: nil, key_color: nil, palette: nil, saturation: 1r,
112
124
  width: 0.8r, dodge: true, flier_size: 5, line_width: nil,
113
- whisker: 1.5, **options, &block)
125
+ whisker: 1.5, x_label: nil, y_label: nil, title: nil,
126
+ **options, &block)
114
127
  Plotters::BoxPlotter.new(
115
128
  data: data,
116
129
  variables: { x: x, y: y, color: color },
@@ -125,6 +138,9 @@ module Charty
125
138
  flier_size: flier_size,
126
139
  line_width: line_width,
127
140
  whisker: whisker,
141
+ x_label: x_label,
142
+ y_label: y_label,
143
+ title: title,
128
144
  **options,
129
145
  &block
130
146
  )
@@ -164,13 +180,18 @@ module Charty
164
180
  # :full, every group will get an entry in the legend. If :auto,
165
181
  # choose between brief or full representation based on number of
166
182
  # levels. If false, no legend data is added and no legend is drawn.
183
+ # @param x_label [String,Symbol,#to_str,nil] X-axis label.
184
+ # @param y_label [String,Symbol,#to_str,nil] Y-axis label.
185
+ # @param title [String,Symbol,#to_str,nil] Title text.
167
186
  def line_plot(x: nil, y: nil, color: nil, style: nil, size: nil,
168
187
  data: nil, key_color: nil, palette: nil, color_order: nil,
169
188
  color_norm: nil, sizes: nil, size_order: nil, size_norm: nil,
170
189
  markers: nil, dashes: true, style_order: nil,
171
190
  units: nil, estimator: :mean, n_boot: 1000, random: nil,
172
191
  sort: true, err_style: :band, err_params: nil, error_bar: [:ci, 95],
173
- x_scale: :linear, y_scale: :linear, legend: :auto, **options, &block)
192
+ x_scale: :linear, y_scale: :linear, legend: :auto,
193
+ x_label: nil, y_label: nil, title: nil,
194
+ **options, &block)
174
195
  Plotters::LinePlotter.new(
175
196
  data: data,
176
197
  variables: { x: x, y: y, color: color, style: style, size: size },
@@ -195,6 +216,9 @@ module Charty
195
216
  x_scale: x_scale,
196
217
  y_scale: y_scale,
197
218
  legend: legend,
219
+ x_label: x_label,
220
+ y_label: y_label,
221
+ title: title,
198
222
  **options,
199
223
  &block
200
224
  )
@@ -225,11 +249,14 @@ module Charty
225
249
  # :full, every group will get an entry in the legend. If :auto,
226
250
  # choose between brief or full representation based on number of
227
251
  # levels. If false, no legend data is added and no legend is drawn.
252
+ # @param x_label [String,Symbol,#to_str,nil] X-axis label.
253
+ # @param y_label [String,Symbol,#to_str,nil] Y-axis label.
254
+ # @param title [String,Symbol,#to_str,nil] Title text.
228
255
  def scatter_plot(x: nil, y: nil, color: nil, style: nil, size: nil,
229
256
  data: nil, key_color: nil, palette: nil, color_order: nil,
230
257
  color_norm: nil, sizes: nil, size_order: nil, size_norm: nil,
231
258
  markers: true, style_order: nil, alpha: nil, legend: :auto,
232
- **options, &block)
259
+ x_label: nil, y_label: nil, title: nil, **options, &block)
233
260
  Plotters::ScatterPlotter.new(
234
261
  data: data,
235
262
  variables: { x: x, y: y, color: color, style: style, size: size },
@@ -244,10 +271,59 @@ module Charty
244
271
  style_order: style_order,
245
272
  alpha: alpha,
246
273
  legend: legend,
274
+ x_label: x_label,
275
+ y_label: y_label,
276
+ title: title,
247
277
  **options,
248
278
  &block
249
279
  )
250
280
  end
281
+
282
+ def hist_plot(data: nil, x: nil, y: nil, color: nil, weights: nil,
283
+ stat: :count, bins: :auto,
284
+ bin_range: nil, common_bins: true,
285
+ key_color: nil, palette: nil, color_order: nil, color_norm: nil,
286
+ legend: true, x_label: nil, y_label: nil, title: nil,
287
+ **options, &block)
288
+ # TODO: support following arguments
289
+ # - wiehgts
290
+ # - binwidth
291
+ # - discrete
292
+ # - cumulative
293
+ # - common_norm
294
+ # - multiple
295
+ # - element
296
+ # - fill
297
+ # - shrink
298
+ # - kde
299
+ # - kde_params
300
+ # - line_params
301
+ # - thresh
302
+ # - pthresh
303
+ # - pmax
304
+ # - cbar
305
+ # - cbar_params
306
+ # - x_log_scale
307
+ # - y_log_scale
308
+ Plotters::HistogramPlotter.new(
309
+ data: data,
310
+ variables: { x: x, y: y, color: color },
311
+ weights: weights,
312
+ stat: stat,
313
+ bins: bins,
314
+ bin_range: bin_range,
315
+ common_bins: common_bins,
316
+ key_color: key_color,
317
+ palette: palette,
318
+ color_order: color_order,
319
+ color_norm: color_norm,
320
+ legend: legend,
321
+ x_label: x_label,
322
+ y_label: y_label,
323
+ title: title,
324
+ **options,
325
+ &block)
326
+ end
251
327
  end
252
328
 
253
329
  extend PlotMethods