charty 0.2.0 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +38 -0
- data/charty.gemspec +1 -0
- data/examples/Gemfile +1 -0
- data/examples/active_record.ipynb +1 -1
- data/examples/daru.ipynb +1 -1
- data/examples/iris_dataset.ipynb +1 -1
- data/examples/nmatrix.ipynb +1 -1
- data/examples/{numo-narray.ipynb → numo_narray.ipynb} +1 -1
- data/examples/palette.rb +71 -0
- data/examples/sample.png +0 -0
- data/examples/sample_pyplot.ipynb +40 -38
- data/lib/charty.rb +7 -0
- data/lib/charty/backend_methods.rb +8 -0
- data/lib/charty/backends.rb +25 -1
- data/lib/charty/backends/bokeh.rb +29 -29
- data/lib/charty/backends/{google_chart.rb → google_charts.rb} +74 -32
- data/lib/charty/backends/plotly.rb +145 -7
- data/lib/charty/backends/pyplot.rb +163 -33
- data/lib/charty/backends/rubyplot.rb +1 -1
- data/lib/charty/palette.rb +235 -0
- data/lib/charty/plot_methods.rb +19 -0
- data/lib/charty/plotter.rb +9 -9
- data/lib/charty/plotters.rb +4 -0
- data/lib/charty/plotters/abstract_plotter.rb +81 -0
- data/lib/charty/plotters/bar_plotter.rb +19 -0
- data/lib/charty/plotters/box_plotter.rb +26 -0
- data/lib/charty/plotters/categorical_plotter.rb +148 -0
- data/lib/charty/statistics.rb +29 -0
- data/lib/charty/table.rb +1 -0
- data/lib/charty/table_adapters/active_record_adapter.rb +1 -1
- data/lib/charty/table_adapters/daru_adapter.rb +2 -0
- data/lib/charty/table_adapters/datasets_adapter.rb +4 -0
- data/lib/charty/table_adapters/hash_adapter.rb +2 -0
- data/lib/charty/table_adapters/narray_adapter.rb +1 -1
- data/lib/charty/table_adapters/nmatrix_adapter.rb +1 -1
- data/lib/charty/version.rb +1 -1
- metadata +30 -5
- data/.travis.yml +0 -10
@@ -12,7 +12,7 @@ module Charty
|
|
12
12
|
end
|
13
13
|
|
14
14
|
def initialize
|
15
|
-
@
|
15
|
+
@pyplot = ::Matplotlib::Pyplot
|
16
16
|
end
|
17
17
|
|
18
18
|
def self.activate_iruby_integration
|
@@ -28,91 +28,221 @@ module Charty
|
|
28
28
|
end
|
29
29
|
|
30
30
|
def render_layout(layout)
|
31
|
-
_fig, axes = @
|
31
|
+
_fig, axes = @pyplot.subplots(nrows: layout.num_rows, ncols: layout.num_cols)
|
32
32
|
layout.rows.each_with_index do |row, y|
|
33
33
|
row.each_with_index do |cel, x|
|
34
|
-
|
35
|
-
plot(
|
34
|
+
ax = layout.num_rows > 1 ? axes[y][x] : axes[x]
|
35
|
+
plot(ax, cel, subplot: true)
|
36
36
|
end
|
37
37
|
end
|
38
|
-
@
|
38
|
+
@pyplot.show
|
39
39
|
end
|
40
40
|
|
41
41
|
def render(context, filename)
|
42
|
-
plot(@
|
42
|
+
plot(@pyplot, context)
|
43
43
|
if filename
|
44
44
|
FileUtils.mkdir_p(File.dirname(filename))
|
45
|
-
@
|
45
|
+
@pyplot.savefig(filename)
|
46
46
|
end
|
47
|
-
@
|
47
|
+
@pyplot.show
|
48
48
|
end
|
49
49
|
|
50
|
-
def save(context, filename)
|
51
|
-
plot(
|
50
|
+
def save(context, filename, finish: true)
|
51
|
+
plot(context)
|
52
52
|
if filename
|
53
53
|
FileUtils.mkdir_p(File.dirname(filename))
|
54
|
-
@
|
54
|
+
@pyplot.savefig(filename)
|
55
55
|
end
|
56
|
+
@pyplot.clf if finish
|
56
57
|
end
|
57
58
|
|
58
|
-
def plot(
|
59
|
+
def plot(ax, context, subplot: false)
|
59
60
|
# TODO: Since it is not required, research and change conditions.
|
60
61
|
# case
|
61
|
-
# when
|
62
|
-
#
|
63
|
-
#
|
64
|
-
# when
|
65
|
-
#
|
66
|
-
#
|
62
|
+
# when @pyplot.respond_to?(:xlim)
|
63
|
+
# @pyplot.xlim(context.range_x.begin, context.range_x.end)
|
64
|
+
# @pyplot.ylim(context.range_y.begin, context.range_y.end)
|
65
|
+
# when @pyplot.respond_to?(:set_xlim)
|
66
|
+
# @pyplot.set_xlim(context.range_x.begin, context.range_x.end)
|
67
|
+
# @pyplot.set_ylim(context.range_y.begin, context.range_y.end)
|
67
68
|
# end
|
68
69
|
|
69
|
-
|
70
|
+
ax.title(context.title) if context.title
|
70
71
|
if !subplot
|
71
|
-
|
72
|
-
|
72
|
+
ax.xlabel(context.xlabel) if context.xlabel
|
73
|
+
ax.ylabel(context.ylabel) if context.ylabel
|
73
74
|
end
|
74
75
|
|
76
|
+
palette = Charty::Palette.default
|
77
|
+
colors = palette.colors.map {|c| c.to_rgb.to_hex_string }.cycle
|
75
78
|
case context.method
|
76
79
|
when :bar
|
77
80
|
context.series.each do |data|
|
78
|
-
|
81
|
+
ax.bar(data.xs.to_a.map(&:to_s), data.ys.to_a, label: data.label,
|
82
|
+
color: colors.next)
|
79
83
|
end
|
80
|
-
|
84
|
+
ax.legend()
|
81
85
|
when :barh
|
82
86
|
context.series.each do |data|
|
83
|
-
|
87
|
+
ax.barh(data.xs.to_a.map(&:to_s), data.ys.to_a, color: colors.next)
|
84
88
|
end
|
85
89
|
when :box_plot
|
86
|
-
|
90
|
+
min_l = palette.colors.map {|c| c.to_rgb.to_hsl.l }.min
|
91
|
+
lum = min_l*0.6
|
92
|
+
gray = Colors::RGB.new(lum, lum, lum).to_hex_string
|
93
|
+
draw_box_plot(context, subplot, colors, gray)
|
87
94
|
when :bubble
|
88
95
|
context.series.each do |data|
|
89
|
-
|
96
|
+
ax.scatter(data.xs.to_a, data.ys.to_a, s: data.zs.to_a, alpha: 0.5,
|
97
|
+
color: colors.next, label: data.label)
|
90
98
|
end
|
91
|
-
|
99
|
+
ax.legend()
|
92
100
|
when :curve
|
93
101
|
context.series.each do |data|
|
94
|
-
|
102
|
+
ax.plot(data.xs.to_a, data.ys.to_a, color: colors.next)
|
95
103
|
end
|
96
104
|
when :scatter
|
97
105
|
context.series.each do |data|
|
98
|
-
|
106
|
+
ax.scatter(data.xs.to_a, data.ys.to_a, label: data.label,
|
107
|
+
color: colors.next)
|
99
108
|
end
|
100
|
-
|
109
|
+
ax.legend()
|
101
110
|
when :error_bar
|
102
111
|
context.series.each do |data|
|
103
|
-
|
112
|
+
ax.errorbar(
|
104
113
|
data.xs.to_a,
|
105
114
|
data.ys.to_a,
|
106
115
|
data.xerr,
|
107
116
|
data.yerr,
|
108
117
|
label: data.label,
|
118
|
+
color: colors.next
|
109
119
|
)
|
110
120
|
end
|
111
|
-
|
121
|
+
ax.legend()
|
112
122
|
when :hist
|
113
|
-
|
123
|
+
data = Array(context.data)
|
124
|
+
ax.hist(data, color: colors.take(data.length), alpha: 0.4)
|
114
125
|
end
|
115
126
|
end
|
127
|
+
|
128
|
+
private def draw_box_plot(context, subplot, colors, gray)
|
129
|
+
Array(context.data).each_with_index do |group_data, i|
|
130
|
+
next if group_data.empty?
|
131
|
+
|
132
|
+
box_data = group_data.compact
|
133
|
+
next if box_data.empty?
|
134
|
+
|
135
|
+
artist_dict = @pyplot.boxplot(box_data, vert: "v", patch_artist: true,
|
136
|
+
positions: [i], widths: 0.8)
|
137
|
+
|
138
|
+
color = colors.next
|
139
|
+
artist_dict["boxes"].each do |box|
|
140
|
+
box.update({facecolor: color, zorder: 0.9, edgecolor: gray}, {})
|
141
|
+
end
|
142
|
+
artist_dict["whiskers"].each do |whisker|
|
143
|
+
whisker.update({color: gray, linestyle: "-"}, {})
|
144
|
+
end
|
145
|
+
artist_dict["caps"].each do |cap|
|
146
|
+
cap.update({color: gray}, {})
|
147
|
+
end
|
148
|
+
artist_dict["medians"].each do |median|
|
149
|
+
median.update({color: gray}, {})
|
150
|
+
end
|
151
|
+
artist_dict["fliers"].each do |flier|
|
152
|
+
flier.update({
|
153
|
+
markerfacecolor: gray,
|
154
|
+
marker: "d",
|
155
|
+
markeredgecolor: gray,
|
156
|
+
markersize: 5
|
157
|
+
}, {})
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
# ==== NEW PLOTTING API ====
|
163
|
+
|
164
|
+
def begin_figure
|
165
|
+
# do nothing
|
166
|
+
end
|
167
|
+
|
168
|
+
def bar(bar_pos, values, color: nil, width: 0.8r, align: :center, orient: :v)
|
169
|
+
bar_pos = Array(bar_pos)
|
170
|
+
values = Array(values)
|
171
|
+
color = Array(color).map(&:to_hex_string)
|
172
|
+
width = Float(width)
|
173
|
+
if orient == :v
|
174
|
+
@pyplot.bar(bar_pos, values, width: width, color: color, align: align)
|
175
|
+
else
|
176
|
+
@pyplot.barh(bar_pos, values, width: width, color: color, align: align)
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def box_plot(plot_data, positions, color:, gray:,
|
181
|
+
width: 0.8r, flier_size: 5, whisker: 1.5, notch: false)
|
182
|
+
color = Array(color).map(&:to_hex_string)
|
183
|
+
gray = gray.to_hex_string
|
184
|
+
width = Float(width)
|
185
|
+
flier_size = Float(flier_size)
|
186
|
+
whisker = Float(whisker)
|
187
|
+
plot_data.each_with_index do |group_data, i|
|
188
|
+
next if group_data.nil? || group_data.empty?
|
189
|
+
|
190
|
+
artist_dict = @pyplot.boxplot(group_data, vert: :v,
|
191
|
+
patch_artist: true,
|
192
|
+
positions: [i],
|
193
|
+
widths: width,
|
194
|
+
whis: whisker, )
|
195
|
+
|
196
|
+
artist_dict["boxes"].each do |box|
|
197
|
+
box.update({facecolor: color[i], zorder: 0.9, edgecolor: gray}, {})
|
198
|
+
end
|
199
|
+
artist_dict["whiskers"].each do |whisker|
|
200
|
+
whisker.update({color: gray, linestyle: "-"}, {})
|
201
|
+
end
|
202
|
+
artist_dict["caps"].each do |cap|
|
203
|
+
cap.update({color: gray}, {})
|
204
|
+
end
|
205
|
+
artist_dict["medians"].each do |median|
|
206
|
+
median.update({color: gray}, {})
|
207
|
+
end
|
208
|
+
artist_dict["fliers"].each do |flier|
|
209
|
+
flier.update({
|
210
|
+
markerfacecolor: gray,
|
211
|
+
marker: "d",
|
212
|
+
markeredgecolor: gray,
|
213
|
+
markersize: flier_size
|
214
|
+
}, {})
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
def set_xlabel(label)
|
220
|
+
@pyplot.gca.set_xlabel(String(label))
|
221
|
+
end
|
222
|
+
|
223
|
+
def set_ylabel(label)
|
224
|
+
@pyplot.gca.set_ylabel(String(label))
|
225
|
+
end
|
226
|
+
|
227
|
+
def set_xticks(values)
|
228
|
+
@pyplot.gca.set_xticks(Array(values))
|
229
|
+
end
|
230
|
+
|
231
|
+
def set_xtick_labels(labels)
|
232
|
+
@pyplot.gca.set_xticklabels(Array(labels).map(&method(:String)))
|
233
|
+
end
|
234
|
+
|
235
|
+
def set_xlim(min, max)
|
236
|
+
@pyplot.gca.set_xlim(Float(min), Float(max))
|
237
|
+
end
|
238
|
+
|
239
|
+
def disable_xaxis_grid
|
240
|
+
@pyplot.gca.xaxis.grid(false)
|
241
|
+
end
|
242
|
+
|
243
|
+
def show
|
244
|
+
@pyplot.show
|
245
|
+
end
|
116
246
|
end
|
117
247
|
end
|
118
248
|
end
|
@@ -23,7 +23,7 @@ module Charty
|
|
23
23
|
end
|
24
24
|
|
25
25
|
def render_layout(layout)
|
26
|
-
(
|
26
|
+
(_fig, axes) = *@plot.subplots(nrows: layout.num_rows, ncols: layout.num_cols)
|
27
27
|
layout.rows.each_with_index do |row, y|
|
28
28
|
row.each_with_index do |cel, x|
|
29
29
|
plot = layout.num_rows > 1 ? axes[y][x] : axes[x]
|
@@ -0,0 +1,235 @@
|
|
1
|
+
require "numo/narray"
|
2
|
+
|
3
|
+
module Charty
|
4
|
+
class Palette
|
5
|
+
SEABORN_PALETTES = {
|
6
|
+
"deep" => ["#4C72B0", "#DD8452", "#55A868", "#C44E52", "#8172B3",
|
7
|
+
"#937860", "#DA8BC3", "#8C8C8C", "#CCB974", "#64B5CD"].freeze,
|
8
|
+
"deep6" => ["#4C72B0", "#55A868", "#C44E52",
|
9
|
+
"#8172B3", "#CCB974", "#64B5CD"].freeze,
|
10
|
+
"muted" => ["#4878D0", "#EE854A", "#6ACC64", "#D65F5F", "#956CB4",
|
11
|
+
"#8C613C", "#DC7EC0", "#797979", "#D5BB67", "#82C6E2"].freeze,
|
12
|
+
"muted6" => ["#4878D0", "#6ACC64", "#D65F5F",
|
13
|
+
"#956CB4", "#D5BB67", "#82C6E2"].freeze,
|
14
|
+
"pastel" => ["#A1C9F4", "#FFB482", "#8DE5A1", "#FF9F9B", "#D0BBFF",
|
15
|
+
"#DEBB9B", "#FAB0E4", "#CFCFCF", "#FFFEA3", "#B9F2F0"].freeze,
|
16
|
+
"pastel6" => ["#A1C9F4", "#8DE5A1", "#FF9F9B",
|
17
|
+
"#D0BBFF", "#FFFEA3", "#B9F2F0"].freeze,
|
18
|
+
"bright" => ["#023EFF", "#FF7C00", "#1AC938", "#E8000B", "#8B2BE2",
|
19
|
+
"#9F4800", "#F14CC1", "#A3A3A3", "#FFC400", "#00D7FF"].freeze,
|
20
|
+
"bright6" => ["#023EFF", "#1AC938", "#E8000B",
|
21
|
+
"#8B2BE2", "#FFC400", "#00D7FF"].freeze,
|
22
|
+
"dark" => ["#001C7F", "#B1400D", "#12711C", "#8C0800", "#591E71",
|
23
|
+
"#592F0D", "#A23582", "#3C3C3C", "#B8850A", "#006374"].freeze,
|
24
|
+
"dark6" => ["#001C7F", "#12711C", "#8C0800",
|
25
|
+
"#591E71", "#B8850A", "#006374"].freeze,
|
26
|
+
"colorblind" => ["#0173B2", "#DE8F05", "#029E73", "#D55E00", "#CC78BC",
|
27
|
+
"#CA9161", "#FBAFE4", "#949494", "#ECE133", "#56B4E9"].freeze,
|
28
|
+
"colorblind6" => ["#0173B2", "#029E73", "#D55E00",
|
29
|
+
"#CC78BC", "#ECE133", "#56B4E9"].freeze
|
30
|
+
}.freeze
|
31
|
+
|
32
|
+
MPL_QUAL_PALS = {
|
33
|
+
"tab10" => 10,
|
34
|
+
"tab20" => 20,
|
35
|
+
"tab20b" => 20,
|
36
|
+
"tab20c" => 20,
|
37
|
+
"Set1" => 9,
|
38
|
+
"Set2" => 8,
|
39
|
+
"Set3" => 12,
|
40
|
+
"Accent" => 8,
|
41
|
+
"Paired" => 12,
|
42
|
+
"Pastel1" => 9,
|
43
|
+
"Pastel2" => 8,
|
44
|
+
"Dark2" => 8,
|
45
|
+
}.freeze
|
46
|
+
|
47
|
+
QUAL_PALETTE_SIZES = MPL_QUAL_PALS.dup
|
48
|
+
SEABORN_PALETTES.each do |k, v|
|
49
|
+
QUAL_PALETTE_SIZES[k] = v.length
|
50
|
+
end
|
51
|
+
QUAL_PALETTE_SIZES.freeze
|
52
|
+
|
53
|
+
def self.seaborn_colors(name)
|
54
|
+
SEABORN_PALETTES[name].map do |hex_string|
|
55
|
+
Colors::RGB.parse(hex_string)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# Get a set of evenly spaced colors in HSL hue space.
|
60
|
+
#
|
61
|
+
# @param n_colors [Integer]
|
62
|
+
# The number of colors in the palette
|
63
|
+
# @param h [Numeric]
|
64
|
+
# The hue value of the first color in degree
|
65
|
+
# @param s [Numeric]
|
66
|
+
# The saturation value of the first color (between 0 and 1)
|
67
|
+
# @param l [Numeric]
|
68
|
+
# The lightness value of the first color (between 0 and 1)
|
69
|
+
#
|
70
|
+
# @return [Array<Colors::HSL>]
|
71
|
+
# The array of colors
|
72
|
+
def self.hsl_colors(n_colors=6, h: 3.6r, s: 0.65r, l: 0.6r)
|
73
|
+
hues = Numo::DFloat.linspace(0, 1, n_colors + 1)[0...-1]
|
74
|
+
hues.inplace + (h/360r).to_f
|
75
|
+
hues.inplace % 1
|
76
|
+
hues.inplace - Numo::Int32.cast(hues)
|
77
|
+
(0...n_colors).map {|i| Colors::HSL.new(hues[i]*360r, s, l) }
|
78
|
+
end
|
79
|
+
|
80
|
+
# Get a set of evenly spaced colors in HUSL hue space.
|
81
|
+
#
|
82
|
+
# @param n_colors [Integer]
|
83
|
+
# The number of colors in the palette
|
84
|
+
# @param h [Numeric]
|
85
|
+
# The hue value of the first color in degree
|
86
|
+
# @param s [Numeric]
|
87
|
+
# The saturation value of the first color (between 0 and 1)
|
88
|
+
# @param l [Numeric]
|
89
|
+
# The lightness value of the first color (between 0 and 1)
|
90
|
+
#
|
91
|
+
# @return [Array<Colors::HSL>]
|
92
|
+
# The array of colors
|
93
|
+
def self.husl_colors(n_colors=6, h: 3.6r, s: 0.9r, l: 0.65r)
|
94
|
+
hues = Numo::DFloat.linspace(0, 1, n_colors + 1)[0...-1]
|
95
|
+
hues.inplace + (h/360r).to_f
|
96
|
+
hues.inplace % 1
|
97
|
+
hues.inplace * 359
|
98
|
+
(0...n_colors).map {|i| Colors::HUSL.new(hues[i], s, l) }
|
99
|
+
end
|
100
|
+
|
101
|
+
def self.cubehelix_colors(n_colors, start=0, rot=0.4r, gamma=1.0r, hue=0.8r,
|
102
|
+
light=0.85r, dark=0.15r, reverse=false, as_cmap: false)
|
103
|
+
raise NotImplementedError,
|
104
|
+
"Cubehelix palette has not been implemented"
|
105
|
+
end
|
106
|
+
|
107
|
+
def self.matplotlib_colors(name, n_colors=6)
|
108
|
+
raise NotImplementedError,
|
109
|
+
"Matplotlib's colormap emulation has not been implemented"
|
110
|
+
end
|
111
|
+
|
112
|
+
# Return a list of colors defining a color palette
|
113
|
+
#
|
114
|
+
# @param palette [nil, String, Palette]
|
115
|
+
# Name of palette or nil to return current palette.
|
116
|
+
# If a Palette is given, input colors are used but
|
117
|
+
# possibly cycled and desaturated.
|
118
|
+
# @param n_colors [Integer, nil]
|
119
|
+
# Number of colors in the palette.
|
120
|
+
# If `nil`, the default will depend on how `palette` is specified.
|
121
|
+
# Named palettes default to 6 colors, but grabbing the current palette
|
122
|
+
# or passing in a list of colors will not change the number of colors
|
123
|
+
# unless this is specified. Asking for more colors than exist in the
|
124
|
+
# palette cause it to cycle.
|
125
|
+
# @param desaturate_factor [Float, nil]
|
126
|
+
# Propotion to desaturate each color by.
|
127
|
+
#
|
128
|
+
# @return [Palette]
|
129
|
+
# Color palette. Behaves like a list.
|
130
|
+
def initialize(palette=nil, n_colors=nil, desaturate_factor: nil)
|
131
|
+
case
|
132
|
+
when palette.nil?
|
133
|
+
@name = nil
|
134
|
+
palette = Colors::ColorDate::DEFAULT_COLOR_CYCLE
|
135
|
+
n_colors ||= palette.length
|
136
|
+
else
|
137
|
+
palette = normalize_palette_name(palette)
|
138
|
+
case palette
|
139
|
+
when String
|
140
|
+
@name = palette
|
141
|
+
# Use all colors in a qualitative palette or 6 of another kind
|
142
|
+
n_colors ||= QUAL_PALETTE_SIZES.fetch(palette, 6)
|
143
|
+
case @name
|
144
|
+
when SEABORN_PALETTES.method(:has_key?)
|
145
|
+
palette = self.class.seaborn_colors(@name)
|
146
|
+
when "hls", "HLS", "hsl", "HSL"
|
147
|
+
palette = self.class.hsl_colors(n_colors)
|
148
|
+
when "husl", "HUSL"
|
149
|
+
palette = self.class.husl_colors(n_colors)
|
150
|
+
when /\Ach:/
|
151
|
+
# Cubehelix palette with params specified in string
|
152
|
+
args, kwargs = parse_cubehelix_args(palette)
|
153
|
+
palette = self.class.cubehelix_colors(n_colors, *args, **kwargs)
|
154
|
+
else
|
155
|
+
begin
|
156
|
+
palette = self.class.matplotlib_colors(palette, n_colors)
|
157
|
+
rescue ArgumentError
|
158
|
+
raise ArgumentError,
|
159
|
+
"#{palette} is not a valid palette name"
|
160
|
+
end
|
161
|
+
end
|
162
|
+
else
|
163
|
+
n_colors ||= palette.length
|
164
|
+
end
|
165
|
+
end
|
166
|
+
if desaturate_factor
|
167
|
+
palette = palette.map {|c| Colors.desaturate(c, desaturate_factor) }
|
168
|
+
end
|
169
|
+
|
170
|
+
# Always return as many colors as we asked for
|
171
|
+
@colors = palette.cycle.take(n_colors).freeze
|
172
|
+
@desaturate_factor = desaturate_factor
|
173
|
+
end
|
174
|
+
|
175
|
+
attr_reader :name, :colors, :desaturate_factor
|
176
|
+
|
177
|
+
def n_colors
|
178
|
+
@colors.length
|
179
|
+
end
|
180
|
+
|
181
|
+
# Two palettes are equal if they have the same colors, even if they have
|
182
|
+
# the different names and different desaturate factors.
|
183
|
+
def ==(other)
|
184
|
+
case other
|
185
|
+
when Palette
|
186
|
+
colors == other.colors
|
187
|
+
else
|
188
|
+
super
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
def [](i)
|
193
|
+
@palette[i % n_colors]
|
194
|
+
end
|
195
|
+
|
196
|
+
def to_ary
|
197
|
+
@palette.dup
|
198
|
+
end
|
199
|
+
|
200
|
+
private def normalize_palette_name(palette)
|
201
|
+
case palette
|
202
|
+
when String
|
203
|
+
palette
|
204
|
+
when Symbol
|
205
|
+
palette.to_s
|
206
|
+
else
|
207
|
+
palette.to_str
|
208
|
+
end
|
209
|
+
rescue NoMethodError, TypeError
|
210
|
+
palette
|
211
|
+
end
|
212
|
+
|
213
|
+
class << self
|
214
|
+
attr_reader :default
|
215
|
+
|
216
|
+
def default=(args)
|
217
|
+
@default = case args
|
218
|
+
when Palette
|
219
|
+
args
|
220
|
+
when Array
|
221
|
+
case args[0]
|
222
|
+
when Array
|
223
|
+
Palette.new(*args)
|
224
|
+
else
|
225
|
+
Palette.new(args)
|
226
|
+
end
|
227
|
+
else
|
228
|
+
Palette.new(args)
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
self.default = Palette.new("deep").freeze
|
234
|
+
end
|
235
|
+
end
|