charty 0.2.0 → 0.2.1
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 +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
|