|
| 1 | +//! Discrete (qualitative) color sequences from Plotly Express. |
| 2 | +//! |
| 3 | +//! These mirror [`plotly.colors.qualitative`] in Plotly.py |
| 4 | +//! (<https://plotly.com/python/discrete-color/#color-sequences-in-plotly-express>) |
| 5 | +//! and can be used wherever a list of CSS colors is accepted, e.g. marker |
| 6 | +//! colors or `layout.colorway`. |
| 7 | +//! |
| 8 | +//! Colors are stored as uppercase `#RRGGBB` hex strings. Plotly.py stores some |
| 9 | +//! sequences as `rgb(...)` triplets; the RGB channel values are identical. |
| 10 | +//! |
| 11 | +//! Each sequence has a reversed counterpart (`*_R`), equivalent to the Python |
| 12 | +//! `*_r` lists (e.g. `PLOTLY_R` ↔ `qualitative.Plotly_r`). |
| 13 | +//! |
| 14 | +//! # Example |
| 15 | +//! ```rust |
| 16 | +//! use plotly::qualitative; |
| 17 | +//! |
| 18 | +//! let mut colors = Vec::new(); |
| 19 | +//! for i in 0..12 { |
| 20 | +//! // Cycles when there are more series than colors |
| 21 | +//! colors.push(qualitative::PLOTLY.get(i).to_string()); |
| 22 | +//! } |
| 23 | +//! assert_eq!(colors[0], "#636EFA"); |
| 24 | +//! assert_eq!(colors[10], "#636EFA"); |
| 25 | +//! ``` |
| 26 | +
|
| 27 | +/// A fixed sequence of CSS colors with modular indexing. |
| 28 | +/// |
| 29 | +/// Inspired by `plotly.express` color sequences (`color_discrete_sequence`). |
| 30 | +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] |
| 31 | +pub struct ColorSequence { |
| 32 | + colors: &'static [&'static str], |
| 33 | + reversed: bool, |
| 34 | +} |
| 35 | + |
| 36 | +impl ColorSequence { |
| 37 | + /// Create a color sequence from a static slice of CSS colors. |
| 38 | + pub const fn new(colors: &'static [&'static str]) -> Self { |
| 39 | + Self { |
| 40 | + colors, |
| 41 | + reversed: false, |
| 42 | + } |
| 43 | + } |
| 44 | + |
| 45 | + /// Number of colors in the sequence (independent of reversal). |
| 46 | + pub const fn len(&self) -> usize { |
| 47 | + self.colors.len() |
| 48 | + } |
| 49 | + |
| 50 | + /// Returns `true` if the sequence contains no colors. |
| 51 | + pub const fn is_empty(&self) -> bool { |
| 52 | + self.colors.is_empty() |
| 53 | + } |
| 54 | + |
| 55 | + /// Whether this sequence yields colors in reverse declaration order. |
| 56 | + pub const fn is_reversed(&self) -> bool { |
| 57 | + self.reversed |
| 58 | + } |
| 59 | + |
| 60 | + /// Return a sequence with the same colors yielded in reverse order. |
| 61 | + pub const fn reversed(&self) -> Self { |
| 62 | + Self { |
| 63 | + colors: self.colors, |
| 64 | + reversed: !self.reversed, |
| 65 | + } |
| 66 | + } |
| 67 | + |
| 68 | + /// Get the color at `index`, wrapping around with modulo arithmetic. |
| 69 | + /// |
| 70 | + /// Indexing follows display order: for a reversed sequence, index `0` is |
| 71 | + /// the last declared color. |
| 72 | + /// |
| 73 | + /// # Panics |
| 74 | + /// |
| 75 | + /// Panics if the sequence is empty. |
| 76 | + pub fn get(&self, index: usize) -> &'static str { |
| 77 | + let n = self.colors.len(); |
| 78 | + debug_assert!(n > 0, "ColorSequence::get on empty sequence"); |
| 79 | + let i = index % n; |
| 80 | + if self.reversed { |
| 81 | + self.colors[n - 1 - i] |
| 82 | + } else { |
| 83 | + self.colors[i] |
| 84 | + } |
| 85 | + } |
| 86 | + |
| 87 | + /// Iterate colors in display order. |
| 88 | + pub fn iter(&self) -> impl Iterator<Item = &'static str> + use<'_> { |
| 89 | + let n = self.colors.len(); |
| 90 | + (0..n).map(move |i| self.get(i)) |
| 91 | + } |
| 92 | + |
| 93 | + /// Collect colors in display order into a `Vec` (convenient for APIs |
| 94 | + /// expecting `Vec<impl Color>` / marker color lists). |
| 95 | + pub fn as_vec(&self) -> Vec<&'static str> { |
| 96 | + self.iter().collect() |
| 97 | + } |
| 98 | +} |
| 99 | + |
| 100 | +impl IntoIterator for ColorSequence { |
| 101 | + type Item = &'static str; |
| 102 | + type IntoIter = std::vec::IntoIter<&'static str>; |
| 103 | + |
| 104 | + fn into_iter(self) -> Self::IntoIter { |
| 105 | + self.as_vec().into_iter() |
| 106 | + } |
| 107 | +} |
| 108 | + |
| 109 | +impl IntoIterator for &ColorSequence { |
| 110 | + type Item = &'static str; |
| 111 | + type IntoIter = std::vec::IntoIter<&'static str>; |
| 112 | + |
| 113 | + fn into_iter(self) -> Self::IntoIter { |
| 114 | + self.as_vec().into_iter() |
| 115 | + } |
| 116 | +} |
| 117 | + |
| 118 | +const PLOTLY_COLORS: [&str; 10] = [ |
| 119 | + "#636EFA", "#EF553B", "#00CC96", "#AB63FA", "#FFA15A", "#19D3F3", "#FF6692", "#B6E880", |
| 120 | + "#FF97FF", "#FECB52", |
| 121 | +]; |
| 122 | +const D3_COLORS: [&str; 10] = [ |
| 123 | + "#1F77B4", "#FF7F0E", "#2CA02C", "#D62728", "#9467BD", "#8C564B", "#E377C2", "#7F7F7F", |
| 124 | + "#BCBD22", "#17BECF", |
| 125 | +]; |
| 126 | +const G10_COLORS: [&str; 10] = [ |
| 127 | + "#3366CC", "#DC3912", "#FF9900", "#109618", "#990099", "#0099C6", "#DD4477", "#66AA00", |
| 128 | + "#B82E2E", "#316395", |
| 129 | +]; |
| 130 | +const T10_COLORS: [&str; 10] = [ |
| 131 | + "#4C78A8", "#F58518", "#E45756", "#72B7B2", "#54A24B", "#EECA3B", "#B279A2", "#FF9DA6", |
| 132 | + "#9D755D", "#BAB0AC", |
| 133 | +]; |
| 134 | +const ALPHABET_COLORS: [&str; 26] = [ |
| 135 | + "#AA0DFE", "#3283FE", "#85660D", "#782AB6", "#565656", "#1C8356", "#16FF32", "#F7E1A0", |
| 136 | + "#E2E2E2", "#1CBE4F", "#C4451C", "#DEA0FD", "#FE00FA", "#325A9B", "#FEAF16", "#F8A19F", |
| 137 | + "#90AD1C", "#F6222E", "#1CFFCE", "#2ED9FF", "#B10DA1", "#C075A6", "#FC1CBF", "#B00068", |
| 138 | + "#FBE426", "#FA0087", |
| 139 | +]; |
| 140 | +const DARK24_COLORS: [&str; 24] = [ |
| 141 | + "#2E91E5", "#E15F99", "#1CA71C", "#FB0D0D", "#DA16FF", "#222A2A", "#B68100", "#750D86", |
| 142 | + "#EB663B", "#511CFB", "#00A08B", "#FB00D1", "#FC0080", "#B2828D", "#6C7C32", "#778AAE", |
| 143 | + "#862A16", "#A777F1", "#620042", "#1616A7", "#DA60CA", "#6C4516", "#0D2A63", "#AF0038", |
| 144 | +]; |
| 145 | +const LIGHT24_COLORS: [&str; 24] = [ |
| 146 | + "#FD3216", "#00FE35", "#6A76FC", "#FED4C4", "#FE00CE", "#0DF9FF", "#F6F926", "#FF9616", |
| 147 | + "#479B55", "#EEA6FB", "#DC587D", "#D626FF", "#6E899C", "#00B5F7", "#B68E00", "#C9FBE5", |
| 148 | + "#FF0092", "#22FFA7", "#E3EE9E", "#86CE00", "#BC7196", "#7E7DCD", "#FC6955", "#E48F72", |
| 149 | +]; |
| 150 | +const SET1_COLORS: [&str; 9] = [ |
| 151 | + "#E41A1C", "#377EB8", "#4DAF4A", "#984EA3", "#FF7F00", "#FFFF33", "#A65628", "#F781BF", |
| 152 | + "#999999", |
| 153 | +]; |
| 154 | +const PASTEL1_COLORS: [&str; 9] = [ |
| 155 | + "#FBB4AE", "#B3CDE3", "#CCEBC5", "#DECBE4", "#FED9A6", "#FFFFCC", "#E5D8BD", "#FDDAEC", |
| 156 | + "#F2F2F2", |
| 157 | +]; |
| 158 | +const DARK2_COLORS: [&str; 8] = [ |
| 159 | + "#1B9E77", "#D95F02", "#7570B3", "#E7298A", "#66A61E", "#E6AB02", "#A6761D", "#666666", |
| 160 | +]; |
| 161 | +const SET2_COLORS: [&str; 8] = [ |
| 162 | + "#66C2A5", "#FC8D62", "#8DA0CB", "#E78AC3", "#A6D854", "#FFD92F", "#E5C494", "#B3B3B3", |
| 163 | +]; |
| 164 | +const PASTEL2_COLORS: [&str; 8] = [ |
| 165 | + "#B3E2CD", "#FDCDAC", "#CBD5E8", "#F4CAE4", "#E6F5C9", "#FFF2AE", "#F1E2CC", "#CCCCCC", |
| 166 | +]; |
| 167 | +const SET3_COLORS: [&str; 12] = [ |
| 168 | + "#8DD3C7", "#FFFFB3", "#BEBADA", "#FB8072", "#80B1D3", "#FDB462", "#B3DE69", "#FCCDE5", |
| 169 | + "#D9D9D9", "#BC80BD", "#CCEBC5", "#FFED6F", |
| 170 | +]; |
| 171 | +const ANTIQUE_COLORS: [&str; 11] = [ |
| 172 | + "#855C75", "#D9AF6B", "#AF6458", "#736F4C", "#526A83", "#625377", "#68855C", "#9C9C5E", |
| 173 | + "#A06177", "#8C785D", "#7C7C7C", |
| 174 | +]; |
| 175 | +const BOLD_COLORS: [&str; 11] = [ |
| 176 | + "#7F3C8D", "#11A579", "#3969AC", "#F2B701", "#E73F74", "#80BA5A", "#E68310", "#008695", |
| 177 | + "#CF1C90", "#F97B72", "#A5AA99", |
| 178 | +]; |
| 179 | +const PASTEL_COLORS: [&str; 11] = [ |
| 180 | + "#66C5CC", "#F6CF71", "#F89C74", "#DCB0F2", "#87C55F", "#9EB9F3", "#FE88B1", "#C9DB74", |
| 181 | + "#8BE0A4", "#B497E7", "#B3B3B3", |
| 182 | +]; |
| 183 | +const PRISM_COLORS: [&str; 11] = [ |
| 184 | + "#5F4690", "#1D6996", "#38A6A5", "#0F8554", "#73AF48", "#EDAD08", "#E17C05", "#CC503E", |
| 185 | + "#94346E", "#6F4070", "#666666", |
| 186 | +]; |
| 187 | +const SAFE_COLORS: [&str; 11] = [ |
| 188 | + "#88CCEE", "#CC6677", "#DDCC77", "#117733", "#332288", "#AA4499", "#44AA99", "#999933", |
| 189 | + "#882255", "#661100", "#888888", |
| 190 | +]; |
| 191 | +const VIVID_COLORS: [&str; 11] = [ |
| 192 | + "#E58606", "#5D69B1", "#52BCA3", "#99C945", "#CC61B0", "#24796C", "#DAA51B", "#2F8AC4", |
| 193 | + "#764E9F", "#ED645A", "#A5AA99", |
| 194 | +]; |
| 195 | + |
| 196 | +/// `Plotly` color sequence (10 colors). |
| 197 | +pub const PLOTLY: ColorSequence = ColorSequence::new(&PLOTLY_COLORS); |
| 198 | +/// Reversed `Plotly` (Python `plotly.colors.qualitative.Plotly_r`). |
| 199 | +pub const PLOTLY_R: ColorSequence = PLOTLY.reversed(); |
| 200 | +/// `D3` color sequence (10 colors). |
| 201 | +pub const D3: ColorSequence = ColorSequence::new(&D3_COLORS); |
| 202 | +/// Reversed `D3` (Python `plotly.colors.qualitative.D3_r`). |
| 203 | +pub const D3_R: ColorSequence = D3.reversed(); |
| 204 | +/// `G10` color sequence (10 colors). |
| 205 | +pub const G10: ColorSequence = ColorSequence::new(&G10_COLORS); |
| 206 | +/// Reversed `G10` (Python `plotly.colors.qualitative.G10_r`). |
| 207 | +pub const G10_R: ColorSequence = G10.reversed(); |
| 208 | +/// `T10` color sequence (10 colors). |
| 209 | +pub const T10: ColorSequence = ColorSequence::new(&T10_COLORS); |
| 210 | +/// Reversed `T10` (Python `plotly.colors.qualitative.T10_r`). |
| 211 | +pub const T10_R: ColorSequence = T10.reversed(); |
| 212 | +/// `Alphabet` color sequence (26 colors). |
| 213 | +pub const ALPHABET: ColorSequence = ColorSequence::new(&ALPHABET_COLORS); |
| 214 | +/// Reversed `Alphabet` (Python `plotly.colors.qualitative.Alphabet_r`). |
| 215 | +pub const ALPHABET_R: ColorSequence = ALPHABET.reversed(); |
| 216 | +/// `Dark24` color sequence (24 colors). |
| 217 | +pub const DARK24: ColorSequence = ColorSequence::new(&DARK24_COLORS); |
| 218 | +/// Reversed `Dark24` (Python `plotly.colors.qualitative.Dark24_r`). |
| 219 | +pub const DARK24_R: ColorSequence = DARK24.reversed(); |
| 220 | +/// `Light24` color sequence (24 colors). |
| 221 | +pub const LIGHT24: ColorSequence = ColorSequence::new(&LIGHT24_COLORS); |
| 222 | +/// Reversed `Light24` (Python `plotly.colors.qualitative.Light24_r`). |
| 223 | +pub const LIGHT24_R: ColorSequence = LIGHT24.reversed(); |
| 224 | +/// `Set1` color sequence (9 colors). |
| 225 | +pub const SET1: ColorSequence = ColorSequence::new(&SET1_COLORS); |
| 226 | +/// Reversed `Set1` (Python `plotly.colors.qualitative.Set1_r`). |
| 227 | +pub const SET1_R: ColorSequence = SET1.reversed(); |
| 228 | +/// `Pastel1` color sequence (9 colors). |
| 229 | +pub const PASTEL1: ColorSequence = ColorSequence::new(&PASTEL1_COLORS); |
| 230 | +/// Reversed `Pastel1` (Python `plotly.colors.qualitative.Pastel1_r`). |
| 231 | +pub const PASTEL1_R: ColorSequence = PASTEL1.reversed(); |
| 232 | +/// `Dark2` color sequence (8 colors). |
| 233 | +pub const DARK2: ColorSequence = ColorSequence::new(&DARK2_COLORS); |
| 234 | +/// Reversed `Dark2` (Python `plotly.colors.qualitative.Dark2_r`). |
| 235 | +pub const DARK2_R: ColorSequence = DARK2.reversed(); |
| 236 | +/// `Set2` color sequence (8 colors). |
| 237 | +pub const SET2: ColorSequence = ColorSequence::new(&SET2_COLORS); |
| 238 | +/// Reversed `Set2` (Python `plotly.colors.qualitative.Set2_r`). |
| 239 | +pub const SET2_R: ColorSequence = SET2.reversed(); |
| 240 | +/// `Pastel2` color sequence (8 colors). |
| 241 | +pub const PASTEL2: ColorSequence = ColorSequence::new(&PASTEL2_COLORS); |
| 242 | +/// Reversed `Pastel2` (Python `plotly.colors.qualitative.Pastel2_r`). |
| 243 | +pub const PASTEL2_R: ColorSequence = PASTEL2.reversed(); |
| 244 | +/// `Set3` color sequence (12 colors). |
| 245 | +pub const SET3: ColorSequence = ColorSequence::new(&SET3_COLORS); |
| 246 | +/// Reversed `Set3` (Python `plotly.colors.qualitative.Set3_r`). |
| 247 | +pub const SET3_R: ColorSequence = SET3.reversed(); |
| 248 | +/// `Antique` color sequence (11 colors). |
| 249 | +pub const ANTIQUE: ColorSequence = ColorSequence::new(&ANTIQUE_COLORS); |
| 250 | +/// Reversed `Antique` (Python `plotly.colors.qualitative.Antique_r`). |
| 251 | +pub const ANTIQUE_R: ColorSequence = ANTIQUE.reversed(); |
| 252 | +/// `Bold` color sequence (11 colors). |
| 253 | +pub const BOLD: ColorSequence = ColorSequence::new(&BOLD_COLORS); |
| 254 | +/// Reversed `Bold` (Python `plotly.colors.qualitative.Bold_r`). |
| 255 | +pub const BOLD_R: ColorSequence = BOLD.reversed(); |
| 256 | +/// `Pastel` color sequence (11 colors). |
| 257 | +pub const PASTEL: ColorSequence = ColorSequence::new(&PASTEL_COLORS); |
| 258 | +/// Reversed `Pastel` (Python `plotly.colors.qualitative.Pastel_r`). |
| 259 | +pub const PASTEL_R: ColorSequence = PASTEL.reversed(); |
| 260 | +/// `Prism` color sequence (11 colors). |
| 261 | +pub const PRISM: ColorSequence = ColorSequence::new(&PRISM_COLORS); |
| 262 | +/// Reversed `Prism` (Python `plotly.colors.qualitative.Prism_r`). |
| 263 | +pub const PRISM_R: ColorSequence = PRISM.reversed(); |
| 264 | +/// `Safe` color sequence (11 colors). |
| 265 | +pub const SAFE: ColorSequence = ColorSequence::new(&SAFE_COLORS); |
| 266 | +/// Reversed `Safe` (Python `plotly.colors.qualitative.Safe_r`). |
| 267 | +pub const SAFE_R: ColorSequence = SAFE.reversed(); |
| 268 | +/// `Vivid` color sequence (11 colors). |
| 269 | +pub const VIVID: ColorSequence = ColorSequence::new(&VIVID_COLORS); |
| 270 | +/// Reversed `Vivid` (Python `plotly.colors.qualitative.Vivid_r`). |
| 271 | +pub const VIVID_R: ColorSequence = VIVID.reversed(); |
| 272 | + |
| 273 | +/// All qualitative sequences as `(python_name, sequence)` pairs, in Plotly.py |
| 274 | +/// module order. |
| 275 | +pub const ALL: &[(&str, ColorSequence)] = &[ |
| 276 | + ("Plotly", PLOTLY), |
| 277 | + ("D3", D3), |
| 278 | + ("G10", G10), |
| 279 | + ("T10", T10), |
| 280 | + ("Alphabet", ALPHABET), |
| 281 | + ("Dark24", DARK24), |
| 282 | + ("Light24", LIGHT24), |
| 283 | + ("Set1", SET1), |
| 284 | + ("Pastel1", PASTEL1), |
| 285 | + ("Dark2", DARK2), |
| 286 | + ("Set2", SET2), |
| 287 | + ("Pastel2", PASTEL2), |
| 288 | + ("Set3", SET3), |
| 289 | + ("Antique", ANTIQUE), |
| 290 | + ("Bold", BOLD), |
| 291 | + ("Pastel", PASTEL), |
| 292 | + ("Prism", PRISM), |
| 293 | + ("Safe", SAFE), |
| 294 | + ("Vivid", VIVID), |
| 295 | +]; |
| 296 | + |
| 297 | +#[cfg(test)] |
| 298 | +mod tests { |
| 299 | + use super::*; |
| 300 | + |
| 301 | + #[test] |
| 302 | + fn lengths_match_python() { |
| 303 | + assert_eq!(PLOTLY.len(), 10); |
| 304 | + assert_eq!(D3.len(), 10); |
| 305 | + assert_eq!(G10.len(), 10); |
| 306 | + assert_eq!(T10.len(), 10); |
| 307 | + assert_eq!(ALPHABET.len(), 26); |
| 308 | + assert_eq!(DARK24.len(), 24); |
| 309 | + assert_eq!(LIGHT24.len(), 24); |
| 310 | + assert_eq!(SET1.len(), 9); |
| 311 | + assert_eq!(PASTEL1.len(), 9); |
| 312 | + assert_eq!(DARK2.len(), 8); |
| 313 | + assert_eq!(SET2.len(), 8); |
| 314 | + assert_eq!(PASTEL2.len(), 8); |
| 315 | + assert_eq!(SET3.len(), 12); |
| 316 | + assert_eq!(ANTIQUE.len(), 11); |
| 317 | + assert_eq!(BOLD.len(), 11); |
| 318 | + assert_eq!(PASTEL.len(), 11); |
| 319 | + assert_eq!(PRISM.len(), 11); |
| 320 | + assert_eq!(SAFE.len(), 11); |
| 321 | + assert_eq!(VIVID.len(), 11); |
| 322 | + } |
| 323 | + |
| 324 | + #[test] |
| 325 | + fn first_and_last_colors() { |
| 326 | + assert_eq!(PLOTLY.get(0), "#636EFA"); |
| 327 | + assert_eq!(PLOTLY.get(9), "#FECB52"); |
| 328 | + assert_eq!(D3.get(0), "#1F77B4"); |
| 329 | + assert_eq!(ALPHABET.get(0), "#AA0DFE"); |
| 330 | + assert_eq!(ALPHABET.get(25), "#FA0087"); |
| 331 | + // plotly.py carto.Safe[9] is rgb(102, 17, 0) |
| 332 | + assert_eq!(SAFE.get(9), "#661100"); |
| 333 | + } |
| 334 | + |
| 335 | + #[test] |
| 336 | + fn modular_indexing() { |
| 337 | + assert_eq!(PLOTLY.get(10), PLOTLY.get(0)); |
| 338 | + assert_eq!(PLOTLY.get(23), PLOTLY.get(3)); |
| 339 | + assert_eq!(PLOTLY_R.get(10), PLOTLY_R.get(0)); |
| 340 | + } |
| 341 | + |
| 342 | + #[test] |
| 343 | + fn reversed_order() { |
| 344 | + assert_eq!(PLOTLY_R.len(), PLOTLY.len()); |
| 345 | + assert_eq!(PLOTLY_R.get(0), PLOTLY.get(PLOTLY.len() - 1)); |
| 346 | + assert_eq!(PLOTLY_R.get(1), PLOTLY.get(PLOTLY.len() - 2)); |
| 347 | + assert_eq!(PLOTLY_R.get(9), PLOTLY.get(0)); |
| 348 | + assert!(PLOTLY_R.is_reversed()); |
| 349 | + assert!(!PLOTLY.is_reversed()); |
| 350 | + // Double reverse is identity |
| 351 | + assert_eq!(PLOTLY.reversed().reversed(), PLOTLY); |
| 352 | + } |
| 353 | + |
| 354 | + #[test] |
| 355 | + fn all_has_19_forward_entries() { |
| 356 | + assert_eq!(ALL.len(), 19); |
| 357 | + let names: Vec<&str> = ALL.iter().map(|(n, _)| *n).collect(); |
| 358 | + assert_eq!( |
| 359 | + names, |
| 360 | + vec![ |
| 361 | + "Plotly", "D3", "G10", "T10", "Alphabet", "Dark24", "Light24", "Set1", "Pastel1", |
| 362 | + "Dark2", "Set2", "Pastel2", "Set3", "Antique", "Bold", "Pastel", "Prism", "Safe", |
| 363 | + "Vivid", |
| 364 | + ] |
| 365 | + ); |
| 366 | + } |
| 367 | + |
| 368 | + #[test] |
| 369 | + fn iter_collect_display_order() { |
| 370 | + let forward: Vec<&str> = PLOTLY.iter().collect(); |
| 371 | + let back: Vec<&str> = PLOTLY_R.iter().collect(); |
| 372 | + assert_eq!(forward.len(), 10); |
| 373 | + assert_eq!(back, forward.iter().copied().rev().collect::<Vec<_>>()); |
| 374 | + } |
| 375 | + |
| 376 | + #[test] |
| 377 | + fn empty_sequence_get_panics() { |
| 378 | + let empty = ColorSequence::new(&[]); |
| 379 | + assert!(empty.is_empty()); |
| 380 | + assert_eq!(empty.len(), 0); |
| 381 | + let result = std::panic::catch_unwind(|| empty.get(0)); |
| 382 | + assert!(result.is_err()); |
| 383 | + } |
| 384 | +} |
0 commit comments