charty 0.1.5.dev → 0.2.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (87) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +71 -0
  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 +176 -9
  8. data/Rakefile +4 -5
  9. data/charty.gemspec +10 -1
  10. data/examples/Gemfile +1 -0
  11. data/examples/active_record.ipynb +1 -1
  12. data/examples/daru.ipynb +1 -1
  13. data/examples/iris_dataset.ipynb +1 -1
  14. data/examples/nmatrix.ipynb +1 -1
  15. data/examples/{numo-narray.ipynb → numo_narray.ipynb} +1 -1
  16. data/examples/palette.rb +71 -0
  17. data/examples/sample.png +0 -0
  18. data/examples/sample_bokeh.ipynb +156 -0
  19. data/examples/sample_google_chart.ipynb +229 -68
  20. data/examples/sample_images/bar_bokeh.html +85 -0
  21. data/examples/sample_images/barh_bokeh.html +85 -0
  22. data/examples/sample_images/box_plot_bokeh.html +85 -0
  23. data/examples/sample_images/curve_bokeh.html +85 -0
  24. data/examples/sample_images/curve_with_function_bokeh.html +85 -0
  25. data/examples/sample_images/hist_gruff.png +0 -0
  26. data/examples/sample_images/scatter_bokeh.html +85 -0
  27. data/examples/sample_pyplot.ipynb +40 -38
  28. data/images/penguins_body_mass_g_flipper_length_mm_scatter_plot.png +0 -0
  29. data/images/penguins_body_mass_g_flipper_length_mm_species_scatter_plot.png +0 -0
  30. data/images/penguins_body_mass_g_flipper_length_mm_species_sex_scatter_plot.png +0 -0
  31. data/images/penguins_species_body_mass_g_bar_plot_h.png +0 -0
  32. data/images/penguins_species_body_mass_g_bar_plot_v.png +0 -0
  33. data/images/penguins_species_body_mass_g_box_plot_h.png +0 -0
  34. data/images/penguins_species_body_mass_g_box_plot_v.png +0 -0
  35. data/images/penguins_species_body_mass_g_sex_bar_plot_v.png +0 -0
  36. data/images/penguins_species_body_mass_g_sex_box_plot_v.png +0 -0
  37. data/lib/charty.rb +14 -1
  38. data/lib/charty/backend_methods.rb +8 -0
  39. data/lib/charty/backends.rb +80 -0
  40. data/lib/charty/backends/bokeh.rb +32 -26
  41. data/lib/charty/backends/google_charts.rb +267 -0
  42. data/lib/charty/backends/gruff.rb +102 -83
  43. data/lib/charty/backends/plotly.rb +685 -0
  44. data/lib/charty/backends/pyplot.rb +586 -92
  45. data/lib/charty/backends/rubyplot.rb +82 -74
  46. data/lib/charty/backends/unicode_plot.rb +79 -0
  47. data/lib/charty/index.rb +213 -0
  48. data/lib/charty/linspace.rb +1 -1
  49. data/lib/charty/missing_value_support.rb +14 -0
  50. data/lib/charty/plot_methods.rb +184 -0
  51. data/lib/charty/plotter.rb +48 -40
  52. data/lib/charty/plotters.rb +11 -0
  53. data/lib/charty/plotters/abstract_plotter.rb +183 -0
  54. data/lib/charty/plotters/bar_plotter.rb +201 -0
  55. data/lib/charty/plotters/box_plotter.rb +79 -0
  56. data/lib/charty/plotters/categorical_plotter.rb +380 -0
  57. data/lib/charty/plotters/count_plotter.rb +7 -0
  58. data/lib/charty/plotters/estimation_support.rb +84 -0
  59. data/lib/charty/plotters/random_support.rb +25 -0
  60. data/lib/charty/plotters/relational_plotter.rb +518 -0
  61. data/lib/charty/plotters/scatter_plotter.rb +104 -0
  62. data/lib/charty/plotters/vector_plotter.rb +6 -0
  63. data/lib/charty/statistics.rb +114 -0
  64. data/lib/charty/table.rb +80 -3
  65. data/lib/charty/table_adapters.rb +25 -0
  66. data/lib/charty/table_adapters/active_record_adapter.rb +63 -0
  67. data/lib/charty/table_adapters/base_adapter.rb +69 -0
  68. data/lib/charty/table_adapters/daru_adapter.rb +70 -0
  69. data/lib/charty/table_adapters/datasets_adapter.rb +49 -0
  70. data/lib/charty/table_adapters/hash_adapter.rb +224 -0
  71. data/lib/charty/table_adapters/narray_adapter.rb +76 -0
  72. data/lib/charty/table_adapters/nmatrix_adapter.rb +67 -0
  73. data/lib/charty/table_adapters/pandas_adapter.rb +81 -0
  74. data/lib/charty/util.rb +20 -0
  75. data/lib/charty/vector.rb +69 -0
  76. data/lib/charty/vector_adapters.rb +183 -0
  77. data/lib/charty/vector_adapters/array_adapter.rb +109 -0
  78. data/lib/charty/vector_adapters/daru_adapter.rb +171 -0
  79. data/lib/charty/vector_adapters/narray_adapter.rb +187 -0
  80. data/lib/charty/vector_adapters/nmatrix_adapter.rb +37 -0
  81. data/lib/charty/vector_adapters/numpy_adapter.rb +168 -0
  82. data/lib/charty/vector_adapters/pandas_adapter.rb +200 -0
  83. data/lib/charty/version.rb +1 -1
  84. metadata +179 -10
  85. data/.travis.yml +0 -11
  86. data/lib/charty/backends/google_chart.rb +0 -167
  87. data/lib/charty/plotter_adapter.rb +0 -17
@@ -8,7 +8,7 @@ module Charty
8
8
  end
9
9
 
10
10
  def each(&block)
11
- step = (@range.end - @range.begin).to_f / @num_step
11
+ step = (@range.end - @range.begin).to_r / (@num_step - 1)
12
12
  (@num_step - 1).times do |i|
13
13
  block.call(@range.begin + i * step)
14
14
  end
@@ -0,0 +1,14 @@
1
+ module Charty
2
+ module MissingValueSupport
3
+ def missing_value?(val)
4
+ case
5
+ when val.nil?
6
+ true
7
+ when val.respond_to?(:nan?) && val.nan?
8
+ true
9
+ else
10
+ false
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,184 @@
1
+ module Charty
2
+ module PlotMethods
3
+ # Show the given data as rectangular bars.
4
+ #
5
+ # @param x x-dimension input for plotting long-form data.
6
+ # @param y y-dimension input for plotting long-form data.
7
+ # @param color color-dimension input for plotting long-form data.
8
+ # @param data Dataset for plotting.
9
+ # @param order Order of the categorical dimension to plot the categorical levels in.
10
+ # @param color_order Order of the color dimension to plot the categorical levels in.
11
+ # @param estimator Statistical function to estimate withint each categorical bin.
12
+ # @param ci Size of confidence intervals to draw around estimated values.
13
+ # @param n_boot The size of bootstrap sample to use when computing confidence intervals.
14
+ # @param units Identifier of sampling unit.
15
+ # @param random Random seed or random number generator for reproducible bootstrapping.
16
+ # @param orient Orientation of the plot (:v for vertical, or :h for
17
+ # horizontal).
18
+ # @param key_color Color for all of the elements, or seed for a gradient palette.
19
+ # @param palette Colors to use for the different levels of the color-dimension variable.
20
+ # @param saturation Propotion of the original saturation to draw colors.
21
+ # @param error_color Color for the lines that represent the confidence intervals.
22
+ # @param error_width Thickness of error bar lines (and caps).
23
+ # @param cap_size Width of the caps on error bars.
24
+ # @param dodge [true,false] If true, bar position is shifted along the
25
+ # categorical axis for avoid overlapping when the color-dimension is used.
26
+ def bar_plot(x: nil, y: nil, color: nil, data: nil,
27
+ order: nil, color_order: nil,
28
+ estimator: :mean, ci: 95, n_boot: 1000, units: nil, random: nil,
29
+ orient: nil, key_color: nil, palette: nil, saturation: 1r,
30
+ error_color: [0.26, 0.26, 0.26], error_width: nil, cap_size: nil,
31
+ dodge: true, **options, &block)
32
+ Plotters::BarPlotter.new(
33
+ data: data, variables: { x: x, y: y, color: color },
34
+ order: order, orient: orient,
35
+ estimator: estimator, ci: ci, n_boot: n_boot, units: units, random: random,
36
+ color_order: color_order, key_color: key_color, palette: palette, saturation: saturation,
37
+ error_color: error_color, error_width: error_width, cap_size: cap_size,
38
+ dodge: dodge,
39
+ **options, &block
40
+ )
41
+ end
42
+
43
+ def count_plot(x: nil, y: nil, color: nil, data: nil,
44
+ order: nil, color_order: nil,
45
+ orient: nil, key_color: nil, palette: nil, saturation: 1r,
46
+ dodge: true, **options, &block)
47
+ case
48
+ when x.nil? && !y.nil?
49
+ x = y
50
+ orient = :h
51
+ when y.nil? && !x.nil?
52
+ y = x
53
+ orient = :v
54
+ when !x.nil? && !y.nil?
55
+ raise ArgumentError,
56
+ "Unable to pass both x and y to count_plot"
57
+ end
58
+
59
+ Plotters::CountPlotter.new(
60
+ data: data,
61
+ variables: { x: x, y: y, color: color },
62
+ order: order,
63
+ orient: orient,
64
+ estimator: :count,
65
+ ci: nil,
66
+ units: nil,
67
+ random: nil,
68
+ color_order: color_order,
69
+ key_color: key_color,
70
+ palette: palette,
71
+ saturation: saturation,
72
+ dodge: dodge,
73
+ **options
74
+ ) do |plotter|
75
+ plotter.value_label = "count"
76
+ block.(plotter) unless block.nil?
77
+ end
78
+ end
79
+
80
+ # Show the distributions of the given data by boxes and whiskers.
81
+ #
82
+ # @param x X-dimension input for plotting long-Form data.
83
+ # @param y Y-dimension input for plotting long-form data.
84
+ # @param color Color-dimension input for plotting long-form data.
85
+ # @param data Dataset for plotting.
86
+ # @param order Order of the categorical dimension to plot the categorical
87
+ # levels in.
88
+ # @param color_order Order of the color dimension to plot the categorical
89
+ # levels in.
90
+ # @param orient Orientation of the plot (:v for vertical, or :h for
91
+ # horizontal).
92
+ # @param key_color Color for all of the elements, or seed for a gradient
93
+ # palette.
94
+ # @param palette Colors to use for the different levels of the
95
+ # color-dimension variable.
96
+ # @param saturation Propotion of the original saturation to draw colors.
97
+ # @param width Width of a full element when not using the color-dimension,
98
+ # or width of all the elements for one level of the major grouping
99
+ # variable.
100
+ # @param dodge [true,false] If true, bar position is shifted along the
101
+ # categorical axis for avoid overlapping when the color-dimension
102
+ # is used.
103
+ # @param flier_size Size of the markers used to indicate outlier
104
+ # observations.
105
+ # @param line_width Width of the gray lines that frame the plot elements.
106
+ # @param whisker Propotion of the IQR past the low and high quartiles to
107
+ # extend the plot whiskers. Points outside of this range will be
108
+ # treated as outliers.
109
+ def box_plot(x: nil, y: nil, color: nil, data: nil,
110
+ order: nil, color_order: nil,
111
+ orient: nil, key_color: nil, palette: nil, saturation: 1r,
112
+ width: 0.8r, dodge: true, flier_size: 5, line_width: nil,
113
+ whisker: 1.5, **options, &block)
114
+ Plotters::BoxPlotter.new(
115
+ data: data,
116
+ variables: { x: x, y: y, color: color },
117
+ order: order,
118
+ color_order: color_order,
119
+ orient: orient,
120
+ key_color: key_color,
121
+ palette: palette,
122
+ saturation: saturation,
123
+ width: width,
124
+ dodge: dodge,
125
+ flier_size: flier_size,
126
+ line_width: line_width,
127
+ whisker: whisker,
128
+ **options,
129
+ &block
130
+ )
131
+ end
132
+
133
+ # Scatter plot
134
+ #
135
+ # @param x [vector-like object, key in data]
136
+ # @param y [vector-like object, key in data]
137
+ # @param color [vector-like object, key in data]
138
+ # @param style [vector-like object, key in data]
139
+ # @param size [vector-like object, key in data]
140
+ # @param data [table-like object]
141
+ # @param key_color [color object]
142
+ # @param palette [String,Array<Numeric>,Palette]
143
+ # @param color_order [Array<String>,Array<Symbol>]
144
+ # @param color_norm
145
+ # @param sizes [Array, Hash]
146
+ # @param size_order [Array]
147
+ # @param size_norm
148
+ # @param markers [true, false, Array, Hash]
149
+ # @param marker_order [Array]
150
+ # @param alpha [scalar number]
151
+ # Propotional opacity of the points.
152
+ # @param legend [:auto, :brief, :full, false]
153
+ # How to draw legend. If :brief, numeric color and size variables
154
+ # will be represented with a sample of evenly spaced values. If
155
+ # :full, every group will get an entry in the legend. If :auto,
156
+ # choose between brief or full representation based on number of
157
+ # levels. If false, no legend data is added and no legend is drawn.
158
+ def scatter_plot(x: nil, y: nil, color: nil, style: nil, size: nil,
159
+ data: nil, key_color: nil, palette: nil, color_order: nil,
160
+ color_norm: nil, sizes: nil, size_order: nil, size_norm: nil,
161
+ markers: true, marker_order: nil, alpha: nil, legend: :auto,
162
+ **options, &block)
163
+ Plotters::ScatterPlotter.new(
164
+ data: data,
165
+ variables: { x: x, y: y, color: color, style: style, size: size },
166
+ key_color: key_color,
167
+ palette: palette,
168
+ color_order: color_order,
169
+ color_norm: color_norm,
170
+ sizes: sizes,
171
+ size_order: size_order,
172
+ size_norm: size_norm,
173
+ markers: markers,
174
+ marker_order: marker_order,
175
+ alpha: alpha,
176
+ legend: legend,
177
+ **options,
178
+ &block
179
+ )
180
+ end
181
+ end
182
+
183
+ extend PlotMethods
184
+ end
@@ -1,7 +1,8 @@
1
1
  module Charty
2
2
  class Plotter
3
- def initialize(adapter_name)
4
- @plotter_adapter = PlotterAdapter.create(adapter_name)
3
+ def initialize(backend_name)
4
+ backend_class = Backends.find_backend_class(backend_name)
5
+ @backend = backend_class.new
5
6
  end
6
7
 
7
8
  def table=(data, **kwargs)
@@ -11,10 +12,11 @@ module Charty
11
12
  attr_reader :table
12
13
 
13
14
  def to_bar(x, y, **args, &block)
14
- seriesx, seriesy = *table.to_a(x, y)
15
+ seriesx = table[x]
16
+ seriesy = table[y]
15
17
  xrange = (seriesx.min)..(seriesx.max)
16
18
  yrange = (seriesy.min)..(seriesy.max)
17
- bar = bar do
19
+ bar do
18
20
  series seriesx, seriesy
19
21
  range x: xrange, y: yrange
20
22
  xlabel x
@@ -23,10 +25,11 @@ module Charty
23
25
  end
24
26
 
25
27
  def to_barh(x, y, **args, &block)
26
- seriesx, seriesy = *table.to_a(x, y)
28
+ seriesx = table[x]
29
+ seriesy = table[y]
27
30
  xrange = (seriesx.min)..(seriesx.max)
28
31
  yrange = (seriesy.min)..(seriesy.max)
29
- bar = barh do
32
+ barh do
30
33
  series seriesx, seriesy
31
34
  range x: xrange, y: yrange
32
35
  xlabel x
@@ -35,10 +38,10 @@ module Charty
35
38
  end
36
39
 
37
40
  def to_box_plot(x, y, **args, &block)
38
- serieses = table.to_a(x, y)
41
+ serieses = [table[x], table[y]]
39
42
  xrange = 0..serieses.size
40
43
  yrange = (serieses.flatten.min - 1)..(serieses.flatten.max + 1)
41
- box_plot = box_plot do
44
+ box_plot do
42
45
  data serieses
43
46
  range x: xrange, y: yrange
44
47
  xlabel x
@@ -47,10 +50,12 @@ module Charty
47
50
  end
48
51
 
49
52
  def to_bubble(x, y, z, **args, &block)
50
- seriesx, seriesy, seriesz = *table.to_a(x, y, z)
53
+ seriesx = table[x]
54
+ seriesy = table[y]
55
+ seriesz = table[z]
51
56
  xrange = (seriesx.min)..(seriesx.max)
52
57
  yrange = (seriesy.min)..(seriesy.max)
53
- bubble = bubble do
58
+ bubble do
54
59
  series seriesx, seriesy, seriesz
55
60
  range x: xrange, y: yrange
56
61
  xlabel x
@@ -59,10 +64,11 @@ module Charty
59
64
  end
60
65
 
61
66
  def to_curve(x, y, **args, &block)
62
- seriesx, seriesy = *table.to_a(x, y)
67
+ seriesx = table[x]
68
+ seriesy = table[y]
63
69
  xrange = (seriesx.min)..(seriesx.max)
64
70
  yrange = (seriesy.min)..(seriesy.max)
65
- curve = curve do
71
+ curve do
66
72
  series seriesx, seriesy
67
73
  range x: xrange, y: yrange
68
74
  xlabel x
@@ -71,10 +77,11 @@ module Charty
71
77
  end
72
78
 
73
79
  def to_scatter(x, y, **args, &block)
74
- seriesx, seriesy = *table.to_a(x, y)
80
+ seriesx = table[x]
81
+ seriesy = table[y]
75
82
  xrange = (seriesx.min)..(seriesx.max)
76
83
  yrange = (seriesy.min)..(seriesy.max)
77
- scatter = scatter do
84
+ scatter do
78
85
  series seriesx, seriesy
79
86
  range x: xrange, y: yrange
80
87
  xlabel x
@@ -84,10 +91,11 @@ module Charty
84
91
 
85
92
  def to_error_bar(x, y, **args, &block)
86
93
  # TODO: It is not yet decided how to include data including xerror and yerror.
87
- seriesx, seriesy = *table.to_a(x, y)
94
+ seriesx = table[x]
95
+ seriesy = table[y]
88
96
  xrange = (seriesx.min)..(seriesx.max)
89
97
  yrange = (seriesy.min)..(seriesy.max)
90
- error_bar = error_bar do
98
+ error_bar do
91
99
  series seriesx, seriesy
92
100
  range x: xrange, y: yrange
93
101
  xlabel x
@@ -96,10 +104,10 @@ module Charty
96
104
  end
97
105
 
98
106
  def to_hst(x, y, **args, &block)
99
- serieses = table.to_a(x, y)
107
+ serieses = [table[x], table[y]]
100
108
  xrange = (serieses.flatten.min - 1)..(serieses.flatten.max + 1)
101
109
  yrange = 0..serieses[0].size
102
- hist = hist do
110
+ hist do
103
111
  data serieses
104
112
  range x: xrange, y: yrange
105
113
  xlabel x
@@ -109,46 +117,46 @@ module Charty
109
117
 
110
118
  def bar(**args, &block)
111
119
  context = RenderContext.new :bar, **args, &block
112
- context.apply(@plotter_adapter)
120
+ context.apply(@backend)
113
121
  end
114
122
 
115
123
  def barh(**args, &block)
116
124
  context = RenderContext.new :barh, **args, &block
117
- context.apply(@plotter_adapter)
125
+ context.apply(@backend)
118
126
  end
119
127
 
120
128
  def box_plot(**args, &block)
121
129
  context = RenderContext.new :box_plot, **args, &block
122
- context.apply(@plotter_adapter)
130
+ context.apply(@backend)
123
131
  end
124
132
 
125
133
  def bubble(**args, &block)
126
134
  context = RenderContext.new :bubble, **args, &block
127
- context.apply(@plotter_adapter)
135
+ context.apply(@backend)
128
136
  end
129
137
 
130
138
  def curve(**args, &block)
131
139
  context = RenderContext.new :curve, **args, &block
132
- context.apply(@plotter_adapter)
140
+ context.apply(@backend)
133
141
  end
134
142
 
135
143
  def scatter(**args, &block)
136
144
  context = RenderContext.new :scatter, **args, &block
137
- context.apply(@plotter_adapter)
145
+ context.apply(@backend)
138
146
  end
139
147
 
140
148
  def error_bar(**args, &block)
141
149
  context = RenderContext.new :error_bar, **args, &block
142
- context.apply(@plotter_adapter)
150
+ context.apply(@backend)
143
151
  end
144
152
 
145
153
  def hist(**args, &block)
146
154
  context = RenderContext.new :hist, **args, &block
147
- context.apply(@plotter_adapter)
155
+ context.apply(@backend)
148
156
  end
149
157
 
150
158
  def layout(definition=:horizontal)
151
- Layout.new(@plotter_adapter, definition)
159
+ Layout.new(@backend, definition)
152
160
  end
153
161
  end
154
162
 
@@ -160,7 +168,7 @@ module Charty
160
168
  def initialize(method, **args, &block)
161
169
  @method = method
162
170
  configurator = Configurator.new(**args)
163
- configurator.instance_eval &block
171
+ configurator.instance_eval(&block)
164
172
  # TODO: label も外から付けられた方がよさそう
165
173
  (@range, @series, @function, @data, @title, @xlabel, @ylabel, @labels) = configurator.to_a
166
174
  end
@@ -229,25 +237,25 @@ module Charty
229
237
  end
230
238
 
231
239
  def render(filename=nil)
232
- @plotter_adapter.render(self, filename)
240
+ @backend.old_style_render(self, filename)
233
241
  end
234
242
 
235
- def save(filename=nil)
236
- @plotter_adapter.save(self, filename)
243
+ def save(filename=nil, **kw)
244
+ @backend.old_style_save(self, filename, **kw)
237
245
  end
238
246
 
239
- def apply(plotter_adapter)
247
+ def apply(backend)
240
248
  case
241
- when !@series.empty?
242
- plotter_adapter.series = @series
243
- when @function
244
- linspace = Linspace.new(@range[:x], 100)
245
- # TODO: set label with function
246
- # TODO: set ys to xs when gruff curve with function
247
- @series << Series.new(linspace.to_a, linspace.map{|x| @function.call(x) }, label: "function" )
249
+ when !@series.empty?
250
+ backend.series = @series
251
+ when @function
252
+ linspace = Linspace.new(@range[:x], 100)
253
+ # TODO: set label with function
254
+ # TODO: set ys to xs when gruff curve with function
255
+ @series << Series.new(linspace.to_a, linspace.map{|x| @function.call(x) }, label: "function" )
248
256
  end
249
257
 
250
- @plotter_adapter = plotter_adapter
258
+ @backend = backend
251
259
  self
252
260
  end
253
261
  end
@@ -0,0 +1,11 @@
1
+ require_relative "plotters/abstract_plotter"
2
+ require_relative "plotters/random_support"
3
+ require_relative "plotters/estimation_support"
4
+ require_relative "plotters/categorical_plotter"
5
+ require_relative "plotters/bar_plotter"
6
+ require_relative "plotters/box_plotter"
7
+ require_relative "plotters/count_plotter"
8
+
9
+ require_relative "plotters/vector_plotter"
10
+ require_relative "plotters/relational_plotter"
11
+ require_relative "plotters/scatter_plotter"
@@ -0,0 +1,183 @@
1
+ module Charty
2
+ module Plotters
3
+ class AbstractPlotter
4
+ def initialize(x, y, color, **options)
5
+ self.x = x
6
+ self.y = y
7
+ self.color = color
8
+ self.data = data
9
+ self.palette = palette
10
+ substitute_options(options)
11
+ yield self if block_given?
12
+ end
13
+
14
+ attr_reader :data, :x, :y, :color
15
+ attr_reader :color_order, :key_color, :palette
16
+
17
+ def inspect
18
+ "#<#{self.class}:0x%016x>" % self.object_id
19
+ end
20
+
21
+ def data=(data)
22
+ @data = case data
23
+ when nil, Charty::Table
24
+ data
25
+ else
26
+ Charty::Table.new(data)
27
+ end
28
+ end
29
+
30
+ def x=(x)
31
+ @x = check_dimension(x, :x)
32
+ end
33
+
34
+ def y=(y)
35
+ @y = check_dimension(y, :y)
36
+ end
37
+
38
+ def color=(color)
39
+ @color = check_dimension(color, :color)
40
+ end
41
+
42
+ def color_order=(color_order)
43
+ #@color_order = XXX
44
+ unless color_order.nil?
45
+ raise NotImplementedError,
46
+ "Specifying color_order is not supported yet"
47
+ end
48
+ end
49
+
50
+ # TODO: move to categorical_plotter
51
+ def key_color=(key_color)
52
+ #@key_color = XXX
53
+ unless key_color.nil?
54
+ raise NotImplementedError,
55
+ "Specifying key_color is not supported yet"
56
+ end
57
+ end
58
+
59
+ def palette=(palette)
60
+ @palette = case palette
61
+ when nil, Palette, Symbol, String
62
+ palette
63
+ else
64
+ raise ArgumentError,
65
+ "invalid type for palette (given #{palette.class}, " +
66
+ "expected Palette, Symbol, or String)"
67
+ end
68
+ end
69
+
70
+ private def substitute_options(options)
71
+ options.each do |key, val|
72
+ send("#{key}=", val)
73
+ end
74
+ end
75
+
76
+ private def check_dimension(value, name)
77
+ case value
78
+ when nil, Symbol, String
79
+ value
80
+ when ->(x) { x.respond_to?(:to_str) }
81
+ value.to_str
82
+ when method(:array?)
83
+ Charty::Vector.new(value)
84
+ else
85
+ raise ArgumentError,
86
+ "invalid type of dimension for #{name} (given #{value.inspect})",
87
+ caller
88
+ end
89
+ end
90
+
91
+ private def check_number(value, name, allow_nil: false)
92
+ case value
93
+ when Numeric
94
+ value
95
+ else
96
+ if allow_nil && value.nil?
97
+ nil
98
+ else
99
+ expected = if allow_nil
100
+ "number or nil"
101
+ else
102
+ "number"
103
+ end
104
+ raise ArgumentError,
105
+ "invalid value for #{name} (%p for #{expected})" % value,
106
+ caller
107
+ end
108
+ end
109
+ end
110
+
111
+ private def check_boolean(value, name, allow_nil: false)
112
+ case value
113
+ when true, false
114
+ value
115
+ else
116
+ expected = if allow_nil
117
+ "true, false, or nil"
118
+ else
119
+ "true or false"
120
+ end
121
+ raise ArgumentError,
122
+ "invalid value for #{name} (%p for #{expected})" % value,
123
+ caller
124
+ end
125
+ end
126
+
127
+ private def variable_type(vector, boolean_type=:numeric)
128
+ if vector.numeric?
129
+ :numeric
130
+ elsif vector.categorical?
131
+ :categorical
132
+ else
133
+ case vector[0]
134
+ when true, false
135
+ boolean_type
136
+ else
137
+ :categorical
138
+ end
139
+ end
140
+ end
141
+
142
+ private def array?(value)
143
+ TableAdapters::HashAdapter.array?(value)
144
+ end
145
+
146
+ private def remove_na!(ary)
147
+ ary.reject! do |x|
148
+ next true if x.nil?
149
+ x.respond_to?(:nan?) && x.nan?
150
+ end
151
+ ary
152
+ end
153
+
154
+ def save(filename, **kwargs)
155
+ backend = Backends.current
156
+ backend.begin_figure
157
+ render_plot(backend, **kwargs)
158
+ backend.save(filename, **kwargs)
159
+ end
160
+
161
+ def render(notebook: false, **kwargs)
162
+ backend = Backends.current
163
+ backend.begin_figure
164
+ render_plot(backend, notebook: notebook, **kwargs)
165
+ backend.render(notebook: notebook, **kwargs)
166
+ end
167
+
168
+ private def render_plot(*, **)
169
+ raise NotImplementedError,
170
+ "subclass must implement #{__method__}"
171
+ end
172
+
173
+ def to_iruby
174
+ render(notebook: iruby_notebook?)
175
+ end
176
+
177
+ private def iruby_notebook?
178
+ return false unless defined?(IRuby)
179
+ true # TODO: Check the server is notebook or not
180
+ end
181
+ end
182
+ end
183
+ end