charty 0.1.4.dev → 0.2.4
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.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +71 -0
- data/.github/workflows/nmatrix.yml +67 -0
- data/.github/workflows/pycall.yml +86 -0
- data/Dockerfile.dev +9 -1
- data/Gemfile +18 -0
- data/README.md +128 -9
- data/Rakefile +4 -5
- data/charty.gemspec +7 -2
- data/examples/Gemfile +1 -0
- data/examples/active_record.ipynb +34 -34
- data/examples/daru.ipynb +71 -29
- data/examples/iris_dataset.ipynb +12 -5
- data/examples/nmatrix.ipynb +30 -30
- data/examples/numo_narray.ipynb +245 -0
- data/examples/palette.rb +71 -0
- data/examples/sample.png +0 -0
- data/examples/sample_bokeh.ipynb +156 -0
- data/examples/sample_google_chart.ipynb +229 -68
- data/examples/sample_gruff.ipynb +148 -133
- data/examples/sample_images/bar_bokeh.html +85 -0
- data/examples/sample_images/barh_bokeh.html +85 -0
- data/examples/sample_images/barh_gruff.png +0 -0
- data/examples/sample_images/box_plot_bokeh.html +85 -0
- data/examples/sample_images/{boxplot_pyplot.png → box_plot_pyplot.png} +0 -0
- data/examples/sample_images/curve_bokeh.html +85 -0
- data/examples/sample_images/curve_with_function_bokeh.html +85 -0
- data/examples/sample_images/{errorbar_pyplot.png → error_bar_pyplot.png} +0 -0
- data/examples/sample_images/hist_gruff.png +0 -0
- data/examples/sample_images/scatter_bokeh.html +85 -0
- data/examples/sample_pyplot.ipynb +37 -35
- data/images/penguins_body_mass_g_flipper_length_mm_scatter_plot.png +0 -0
- data/images/penguins_body_mass_g_flipper_length_mm_species_scatter_plot.png +0 -0
- data/images/penguins_body_mass_g_flipper_length_mm_species_sex_scatter_plot.png +0 -0
- data/images/penguins_species_body_mass_g_bar_plot_h.png +0 -0
- data/images/penguins_species_body_mass_g_bar_plot_v.png +0 -0
- data/images/penguins_species_body_mass_g_box_plot_h.png +0 -0
- data/images/penguins_species_body_mass_g_box_plot_v.png +0 -0
- data/images/penguins_species_body_mass_g_sex_bar_plot_v.png +0 -0
- data/images/penguins_species_body_mass_g_sex_box_plot_v.png +0 -0
- data/lib/charty.rb +13 -7
- data/lib/charty/backend_methods.rb +8 -0
- data/lib/charty/backends.rb +80 -0
- data/lib/charty/backends/bokeh.rb +80 -0
- data/lib/charty/backends/google_charts.rb +267 -0
- data/lib/charty/backends/gruff.rb +104 -67
- data/lib/charty/backends/plotly.rb +549 -0
- data/lib/charty/backends/pyplot.rb +584 -86
- data/lib/charty/backends/rubyplot.rb +82 -74
- data/lib/charty/backends/unicode_plot.rb +79 -0
- data/lib/charty/index.rb +213 -0
- data/lib/charty/linspace.rb +1 -1
- data/lib/charty/missing_value_support.rb +14 -0
- data/lib/charty/plot_methods.rb +184 -0
- data/lib/charty/plotter.rb +57 -41
- data/lib/charty/plotters.rb +11 -0
- data/lib/charty/plotters/abstract_plotter.rb +156 -0
- data/lib/charty/plotters/bar_plotter.rb +216 -0
- data/lib/charty/plotters/box_plotter.rb +94 -0
- data/lib/charty/plotters/categorical_plotter.rb +380 -0
- data/lib/charty/plotters/count_plotter.rb +7 -0
- data/lib/charty/plotters/estimation_support.rb +84 -0
- data/lib/charty/plotters/random_support.rb +25 -0
- data/lib/charty/plotters/relational_plotter.rb +518 -0
- data/lib/charty/plotters/scatter_plotter.rb +115 -0
- data/lib/charty/plotters/vector_plotter.rb +6 -0
- data/lib/charty/statistics.rb +114 -0
- data/lib/charty/table.rb +82 -3
- data/lib/charty/table_adapters.rb +25 -0
- data/lib/charty/table_adapters/active_record_adapter.rb +63 -0
- data/lib/charty/table_adapters/base_adapter.rb +69 -0
- data/lib/charty/table_adapters/daru_adapter.rb +70 -0
- data/lib/charty/table_adapters/datasets_adapter.rb +49 -0
- data/lib/charty/table_adapters/hash_adapter.rb +224 -0
- data/lib/charty/table_adapters/narray_adapter.rb +76 -0
- data/lib/charty/table_adapters/nmatrix_adapter.rb +67 -0
- data/lib/charty/table_adapters/pandas_adapter.rb +81 -0
- data/lib/charty/vector.rb +69 -0
- data/lib/charty/vector_adapters.rb +183 -0
- data/lib/charty/vector_adapters/array_adapter.rb +109 -0
- data/lib/charty/vector_adapters/daru_adapter.rb +171 -0
- data/lib/charty/vector_adapters/narray_adapter.rb +187 -0
- data/lib/charty/vector_adapters/nmatrix_adapter.rb +37 -0
- data/lib/charty/vector_adapters/numpy_adapter.rb +168 -0
- data/lib/charty/vector_adapters/pandas_adapter.rb +200 -0
- data/lib/charty/version.rb +1 -1
- metadata +127 -13
- data/.travis.yml +0 -11
- data/examples/numo-narray.ipynb +0 -234
- data/lib/charty/backends/google_chart.rb +0 -167
- data/lib/charty/plotter_adapter.rb +0 -17
@@ -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,156 @@
|
|
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 data=(data)
|
18
|
+
@data = case data
|
19
|
+
when nil, Charty::Table
|
20
|
+
data
|
21
|
+
else
|
22
|
+
Charty::Table.new(data)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def x=(x)
|
27
|
+
@x = check_dimension(x, :x)
|
28
|
+
end
|
29
|
+
|
30
|
+
def y=(y)
|
31
|
+
@y = check_dimension(y, :y)
|
32
|
+
end
|
33
|
+
|
34
|
+
def color=(color)
|
35
|
+
@color = check_dimension(color, :color)
|
36
|
+
end
|
37
|
+
|
38
|
+
def color_order=(color_order)
|
39
|
+
#@color_order = XXX
|
40
|
+
unless color_order.nil?
|
41
|
+
raise NotImplementedError,
|
42
|
+
"Specifying color_order is not supported yet"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# TODO: move to categorical_plotter
|
47
|
+
def key_color=(key_color)
|
48
|
+
#@key_color = XXX
|
49
|
+
unless key_color.nil?
|
50
|
+
raise NotImplementedError,
|
51
|
+
"Specifying key_color is not supported yet"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def palette=(palette)
|
56
|
+
@palette = case palette
|
57
|
+
when nil, Palette, Symbol, String
|
58
|
+
palette
|
59
|
+
else
|
60
|
+
raise ArgumentError,
|
61
|
+
"invalid type for palette (given #{palette.class}, " +
|
62
|
+
"expected Palette, Symbol, or String)"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
private def substitute_options(options)
|
67
|
+
options.each do |key, val|
|
68
|
+
send("#{key}=", val)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
private def check_dimension(value, name)
|
73
|
+
case value
|
74
|
+
when nil, Symbol, String
|
75
|
+
value
|
76
|
+
when ->(x) { x.respond_to?(:to_str) }
|
77
|
+
value.to_str
|
78
|
+
when method(:array?)
|
79
|
+
Charty::Vector.new(value)
|
80
|
+
else
|
81
|
+
raise ArgumentError,
|
82
|
+
"invalid type of dimension for #{name} (given #{value.inspect})",
|
83
|
+
caller
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
private def check_number(value, name, allow_nil: false)
|
88
|
+
case value
|
89
|
+
when Numeric
|
90
|
+
value
|
91
|
+
else
|
92
|
+
if allow_nil && value.nil?
|
93
|
+
nil
|
94
|
+
else
|
95
|
+
expected = if allow_nil
|
96
|
+
"number or nil"
|
97
|
+
else
|
98
|
+
"number"
|
99
|
+
end
|
100
|
+
raise ArgumentError,
|
101
|
+
"invalid value for #{name} (%p for #{expected})" % value,
|
102
|
+
caller
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
private def check_boolean(value, name, allow_nil: false)
|
108
|
+
case value
|
109
|
+
when true, false
|
110
|
+
value
|
111
|
+
else
|
112
|
+
expected = if allow_nil
|
113
|
+
"true, false, or nil"
|
114
|
+
else
|
115
|
+
"true or false"
|
116
|
+
end
|
117
|
+
raise ArgumentError,
|
118
|
+
"invalid value for #{name} (%p for #{expected})" % value,
|
119
|
+
caller
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
private def variable_type(vector, boolean_type=:numeric)
|
124
|
+
if vector.numeric?
|
125
|
+
:numeric
|
126
|
+
elsif vector.categorical?
|
127
|
+
:categorical
|
128
|
+
else
|
129
|
+
case vector[0]
|
130
|
+
when true, false
|
131
|
+
boolean_type
|
132
|
+
else
|
133
|
+
:categorical
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
private def array?(value)
|
139
|
+
TableAdapters::HashAdapter.array?(value)
|
140
|
+
end
|
141
|
+
|
142
|
+
private def remove_na!(ary)
|
143
|
+
ary.reject! do |x|
|
144
|
+
next true if x.nil?
|
145
|
+
x.respond_to?(:nan?) && x.nan?
|
146
|
+
end
|
147
|
+
ary
|
148
|
+
end
|
149
|
+
|
150
|
+
def to_iruby
|
151
|
+
result = render
|
152
|
+
["text/html", result] if result
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
@@ -0,0 +1,216 @@
|
|
1
|
+
module Charty
|
2
|
+
module Plotters
|
3
|
+
class BarPlotter < CategoricalPlotter
|
4
|
+
self.default_palette = :light
|
5
|
+
self.require_numeric = true
|
6
|
+
|
7
|
+
def initialize(data: nil, variables: {}, **options, &block)
|
8
|
+
x, y, color = variables.values_at(:x, :y, :color)
|
9
|
+
super(x, y, color, data: data, **options, &block)
|
10
|
+
end
|
11
|
+
|
12
|
+
attr_reader :error_color
|
13
|
+
|
14
|
+
def error_color=(error_color)
|
15
|
+
@error_color = check_error_color(error_color)
|
16
|
+
end
|
17
|
+
|
18
|
+
private def check_error_color(value)
|
19
|
+
case value
|
20
|
+
when Colors::AbstractColor
|
21
|
+
value
|
22
|
+
when Array
|
23
|
+
Colors::RGB.new(*value)
|
24
|
+
when String
|
25
|
+
# TODO: Use Colors.parse when it'll be available
|
26
|
+
Colors::RGB.parse(value)
|
27
|
+
else
|
28
|
+
raise ArgumentError,
|
29
|
+
"invalid value for error_color (%p for a color, a RGB tripret, or a RGB hex string)" % value
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
attr_reader :error_width
|
34
|
+
|
35
|
+
def error_width=(error_width)
|
36
|
+
@error_width = check_number(error_width, :error_width, allow_nil: true)
|
37
|
+
end
|
38
|
+
|
39
|
+
attr_reader :cap_size
|
40
|
+
|
41
|
+
def cap_size=(cap_size)
|
42
|
+
@cap_size = check_number(cap_size, :cap_size, allow_nil: true)
|
43
|
+
end
|
44
|
+
|
45
|
+
def render
|
46
|
+
backend = Backends.current
|
47
|
+
backend.begin_figure
|
48
|
+
draw_bars(backend)
|
49
|
+
annotate_axes(backend)
|
50
|
+
backend.invert_yaxis if orient == :h
|
51
|
+
backend.show
|
52
|
+
end
|
53
|
+
|
54
|
+
# TODO:
|
55
|
+
# - Should infer mime type from file's extname
|
56
|
+
# - Should check backend's supported mime type before begin_figure
|
57
|
+
def save(filename, **opts)
|
58
|
+
backend = Backends.current
|
59
|
+
backend.begin_figure
|
60
|
+
draw_bars(backend)
|
61
|
+
annotate_axes(backend)
|
62
|
+
backend.invert_yaxis if orient == :h
|
63
|
+
backend.save(filename, **opts)
|
64
|
+
end
|
65
|
+
|
66
|
+
private def draw_bars(backend)
|
67
|
+
setup_estimations
|
68
|
+
|
69
|
+
if @plot_colors.nil?
|
70
|
+
bar_pos = (0 ... @estimations.length).to_a
|
71
|
+
error_colors = bar_pos.map { error_color }
|
72
|
+
if @conf_int.empty?
|
73
|
+
ci_params = {}
|
74
|
+
else
|
75
|
+
ci_params = {conf_int: @conf_int, error_colors: error_colors,
|
76
|
+
error_width: error_width, cap_size: cap_size}
|
77
|
+
end
|
78
|
+
backend.bar(bar_pos, nil, @estimations, @colors, orient, **ci_params)
|
79
|
+
else
|
80
|
+
bar_pos = (0 ... @estimations[0].length).to_a
|
81
|
+
error_colors = bar_pos.map { error_color }
|
82
|
+
offsets = color_offsets
|
83
|
+
width = nested_width
|
84
|
+
@color_names.each_with_index do |color_name, i|
|
85
|
+
pos = bar_pos.map {|x| x + offsets[i] }
|
86
|
+
colors = Array.new(@estimations[i].length) { @colors[i] }
|
87
|
+
if @conf_int[i].empty?
|
88
|
+
ci_params = {}
|
89
|
+
else
|
90
|
+
ci_params = {conf_int: @conf_int[i], error_colors: error_colors,
|
91
|
+
error_width: error_width, cap_size: cap_size}
|
92
|
+
end
|
93
|
+
backend.bar(pos, @group_names, @estimations[i], colors, orient,
|
94
|
+
label: color_name, width: width, **ci_params)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
private def setup_estimations
|
100
|
+
if @color_names.nil?
|
101
|
+
setup_estimations_with_single_color_group
|
102
|
+
else
|
103
|
+
setup_estimations_with_multiple_color_groups
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
private def setup_estimations_with_single_color_group
|
108
|
+
estimations = []
|
109
|
+
conf_int = []
|
110
|
+
|
111
|
+
@plot_data.each do |group_data|
|
112
|
+
# Single color group
|
113
|
+
if @plot_units.nil?
|
114
|
+
stat_data = group_data.drop_na
|
115
|
+
unit_data = nil
|
116
|
+
else
|
117
|
+
# TODO: Support units
|
118
|
+
end
|
119
|
+
|
120
|
+
estimation = if stat_data.size == 0
|
121
|
+
Float::NAN
|
122
|
+
else
|
123
|
+
estimate(estimator, stat_data)
|
124
|
+
end
|
125
|
+
estimations << estimation
|
126
|
+
|
127
|
+
unless ci.nil?
|
128
|
+
if stat_data.size < 2
|
129
|
+
conf_int << [Float::NAN, Float::NAN]
|
130
|
+
next
|
131
|
+
end
|
132
|
+
|
133
|
+
if ci == :sd
|
134
|
+
sd = stat_data.stdev
|
135
|
+
conf_int << [estimation - sd, estimation + sd]
|
136
|
+
else
|
137
|
+
conf_int << Statistics.bootstrap_ci(stat_data, ci, func: estimator, n_boot: n_boot,
|
138
|
+
units: unit_data, random: random)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
@estimations = estimations
|
144
|
+
@conf_int = conf_int
|
145
|
+
end
|
146
|
+
|
147
|
+
private def setup_estimations_with_multiple_color_groups
|
148
|
+
estimations = Array.new(@color_names.length) { [] }
|
149
|
+
conf_int = Array.new(@color_names.length) { [] }
|
150
|
+
|
151
|
+
@plot_data.each_with_index do |group_data, i|
|
152
|
+
@color_names.each_with_index do |color_name, j|
|
153
|
+
if @plot_colors[i].length == 0
|
154
|
+
estimations[j] << Float::NAN
|
155
|
+
unless ci.nil?
|
156
|
+
conf_int[j] << [Float::NAN, Float::NAN]
|
157
|
+
end
|
158
|
+
next
|
159
|
+
end
|
160
|
+
|
161
|
+
color_mask = @plot_colors[i].eq(color_name)
|
162
|
+
if @plot_units.nil?
|
163
|
+
begin
|
164
|
+
stat_data = group_data[color_mask].drop_na
|
165
|
+
rescue
|
166
|
+
@plot_data.each_with_index {|pd, k| p k => pd }
|
167
|
+
@plot_colors.each_with_index {|pc, k| p k => pc }
|
168
|
+
raise
|
169
|
+
end
|
170
|
+
unit_data = nil
|
171
|
+
else
|
172
|
+
# TODO: Support units
|
173
|
+
end
|
174
|
+
|
175
|
+
estimation = if stat_data.size == 0
|
176
|
+
Float::NAN
|
177
|
+
else
|
178
|
+
estimate(estimator, stat_data)
|
179
|
+
end
|
180
|
+
estimations[j] << estimation
|
181
|
+
|
182
|
+
unless ci.nil?
|
183
|
+
if stat_data.size < 2
|
184
|
+
conf_int[j] << [Float::NAN, Float::NAN]
|
185
|
+
next
|
186
|
+
end
|
187
|
+
|
188
|
+
if ci == :sd
|
189
|
+
sd = stat_data.stdev
|
190
|
+
conf_int[j] << [estimation - sd, estimation + sd]
|
191
|
+
else
|
192
|
+
conf_int[j] << Statistics.bootstrap_ci(stat_data, ci, func: estimator, n_boot: n_boot,
|
193
|
+
units: unit_data, random: random)
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
@estimations = estimations
|
200
|
+
@conf_int = conf_int
|
201
|
+
end
|
202
|
+
|
203
|
+
private def estimate(estimator, data)
|
204
|
+
case estimator
|
205
|
+
when :count
|
206
|
+
data.length
|
207
|
+
when :mean
|
208
|
+
data.mean
|
209
|
+
else
|
210
|
+
# TODO: Support other estimations
|
211
|
+
raise NotImplementedError, "#{estimator} estimator is not supported yet"
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
module Charty
|
2
|
+
module Plotters
|
3
|
+
class BoxPlotter < CategoricalPlotter
|
4
|
+
self.default_palette = :light
|
5
|
+
self.require_numeric = true
|
6
|
+
|
7
|
+
def initialize(data: nil, variables: {}, **options, &block)
|
8
|
+
x, y, color = variables.values_at(:x, :y, :color)
|
9
|
+
super(x, y, color, data: data, **options, &block)
|
10
|
+
end
|
11
|
+
|
12
|
+
attr_reader :flier_size
|
13
|
+
|
14
|
+
def flier_size=(val)
|
15
|
+
@flier_size = check_number(val, :flier_size, allow_nil: true)
|
16
|
+
end
|
17
|
+
|
18
|
+
attr_reader :line_width
|
19
|
+
|
20
|
+
def line_width=(val)
|
21
|
+
@line_width = check_number(val, :line_width, allow_nil: true)
|
22
|
+
end
|
23
|
+
|
24
|
+
attr_reader :whisker
|
25
|
+
|
26
|
+
def whisker=(val)
|
27
|
+
@whisker = check_number(val, :whisker, allow_nil: true)
|
28
|
+
end
|
29
|
+
|
30
|
+
def render
|
31
|
+
backend = Backends.current
|
32
|
+
backend.begin_figure
|
33
|
+
draw_box_plot(backend)
|
34
|
+
annotate_axes(backend)
|
35
|
+
backend.invert_yaxis if orient == :h
|
36
|
+
backend.show
|
37
|
+
end
|
38
|
+
|
39
|
+
# TODO:
|
40
|
+
# - Should infer mime type from file's extname
|
41
|
+
# - Should check backend's supported mime type before begin_figure
|
42
|
+
def save(filename, **opts)
|
43
|
+
backend = Backends.current
|
44
|
+
backend.begin_figure
|
45
|
+
draw_box_plot(backend)
|
46
|
+
annotate_axes(backend)
|
47
|
+
backend.invert_yaxis if orient == :h
|
48
|
+
backend.save(filename, **opts)
|
49
|
+
end
|
50
|
+
|
51
|
+
private def draw_box_plot(backend)
|
52
|
+
if @plot_colors.nil?
|
53
|
+
plot_data = @plot_data.map do |group_data|
|
54
|
+
unless group_data.empty?
|
55
|
+
group_data = group_data.drop_na
|
56
|
+
group_data unless group_data.empty?
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
backend.box_plot(plot_data,
|
61
|
+
@group_names,
|
62
|
+
orient: orient,
|
63
|
+
colors: @colors,
|
64
|
+
gray: @gray,
|
65
|
+
dodge: dodge,
|
66
|
+
width: @width,
|
67
|
+
flier_size: flier_size,
|
68
|
+
whisker: whisker)
|
69
|
+
else
|
70
|
+
grouped_box_data = @color_names.map.with_index do |color_name, i|
|
71
|
+
@plot_data.map.with_index do |group_data, j|
|
72
|
+
unless group_data.empty?
|
73
|
+
color_mask = @plot_colors[j].eq(color_name)
|
74
|
+
group_data = group_data[color_mask].drop_na
|
75
|
+
group_data unless group_data.empty?
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
backend.grouped_box_plot(grouped_box_data,
|
81
|
+
@group_names,
|
82
|
+
@color_names,
|
83
|
+
orient: orient,
|
84
|
+
colors: @colors,
|
85
|
+
gray: @gray,
|
86
|
+
dodge: dodge,
|
87
|
+
width: @width,
|
88
|
+
flier_size: flier_size,
|
89
|
+
whisker: whisker)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|