Coverage for src/colorspace/palettes.py: 95%

884 statements  

« prev     ^ index     » next       coverage.py v7.6.4, created at 2024-10-29 15:11 +0000

1 

2 

3class palette: 

4 """Custom Color Palette 

5 

6 Allows for the construction of custom (named) color palettes with a fixed 

7 set of colors based on hex color inputs (or named matplotlib colors). 

8 

9 Args: 

10 colors (str, list, colorspace.colorlib.colorobject, LinearSegmentedColormap): 

11 One or multiple colors which will make up the custom palette, or a 

12 `matplotlib.colors.LinearSegmentedColormap`. 

13 name (str): Name of this custom palette. Defaults to `"user_palette"`. 

14 Used for object representation/visualization. 

15 n (int): int (`>1`), number of colors drawn from an `hclpalette` object. 

16 Only taken into account if the object provided on `colors` inherits 

17 from `colorspace.palettes.hclpalette`. 

18 

19 Returns: 

20 An object of class :py:class:`colorspace.palettes.palette`. 

21 

22 Example: 

23 

24 >>> from colorspace.palettes import palette 

25 >>> colors = ["#070707", "#690056", "#C30E62", "#ED8353", "#FDF5EB"] 

26 >>> custom_pal = palette(colors, "test palette") 

27 >>> custom_pal 

28 >>> 

29 >>> #: Creating custom palettes based on different input 

30 >>> # types (str, list, colorobject) 

31 >>> from colorspace.colorlib import hexcols 

32 >>> from colorspace import palette 

33 >>> hexcols = hexcols(colors) 

34 >>>  

35 >>> # Creating a series of custom palette objects 

36 >>> pal1 = palette("#ff0033") # unnamed 

37 >>> pal2 = palette("#ff0033", name = "Custom Palette") 

38 >>> pal3 = palette(colors, name = "Custom Palette #3") 

39 >>> pal4 = palette(hexcols, name = "Custom Palette #4") 

40 >>> print(pal1) 

41 >>> #: 

42 >>> print(pal2) 

43 >>> #: 

44 >>> print(pal3) 

45 >>> #: 

46 >>> print(pal4) 

47 >>> 

48 >>> #: Palette Swatch Plot 

49 >>> from colorspace import swatchplot 

50 >>> swatchplot([pal3, pal4], figsize = (5.5, 2.0)); 

51 

52 Raises: 

53 TypeError: If `n` is not int. 

54 ValueError: If `n` is `< 2`. 

55 TypeError: If `name` is neither `str` nor `None`. 

56 """ 

57 

58 def __init__(self, colors, name = None, n = 7): 

59 

60 # Sanity check for input 

61 from colorspace.colorlib import colorobject 

62 from colorspace.palettes import hclpalette 

63 from colorspace import check_hex_colors, palette 

64 

65 if not isinstance(n, int): 

66 raise TypeError("argument `n` must be int") 

67 elif n <= 1: 

68 raise ValueError("argument `n` must be > 1") 

69 

70 # Trying to load matplotlib to allow 'colors' to be a 

71 # LinearSegmentedColormap 

72 try: 

73 from matplotlib.colors import LinearSegmentedColormap 

74 matplotlib_loaded = True 

75 except: 

76 matplotlib_loaded = False 

77 

78 # This is a palette object? Well ... 

79 if isinstance(colors, hclpalette): 

80 colors = colors.colors(n) 

81 # Already a palette object? Simply extract the colors from it. 

82 elif isinstance(colors, palette): 

83 colors = colors.colors() 

84 # If the input inherits from colorspace.colorlib.colorobject we 

85 # draw the colors as a hex list. 

86 elif isinstance(colors, colorobject): 

87 colors = colors.colors() 

88 # Matplotlib loaded and 'colors' is a LinearSegmentedColormap 

89 elif matplotlib_loaded and isinstance(colors, LinearSegmentedColormap): 

90 colors = self._LinearSegmentedColormap_to_colors(colors, n) 

91 

92 # Now check if all our colors are valid hex colors 

93 self._colors = check_hex_colors(colors) 

94 

95 if not isinstance(name, (type(None), str)): 

96 raise TypeError("argument `name` must be None or a str") 

97 

98 self._name = name 

99 

100 

101 def _LinearSegmentedColormap_to_colors(self, x, n): 

102 from matplotlib.colors import LinearSegmentedColormap 

103 from numpy import linspace 

104 from colorspace.colorlib import sRGB 

105 

106 assert isinstance(x, LinearSegmentedColormap) 

107 assert isinstance(n, int) and n > 1 

108 

109 # Evaluate at 

110 at = linspace(0.0, 1.0, n) 

111 # Get sRGB Coordinates 

112 rgb = x(at).transpose() 

113 

114 # Create sRGB colorobject, return hex color list 

115 return sRGB(R = rgb[0], G = rgb[1], B = rgb[2]).colors() 

116 

117 

118 def __len__(self): 

119 """Number of Colors 

120 

121 Returns: 

122 int: Number of colors. 

123 """ 

124 return len(self._colors) 

125 

126 def __repr__(self): 

127 name = self.name() 

128 str = "Palette Name: {:s}\n".format("None" if name is None else name) + \ 

129 " Type: Custom palette\n" + \ 

130 " Number of colors: {:d}\n".format(len(self.colors())) 

131 return str 

132 

133 def rename(self, name): 

134 """Rename Custom Palette 

135 

136 Allows to set, remplace, or remove the name of a palette. 

137 

138 Args: 

139 name (None, str): new name for the palette. 

140 

141 Raises: 

142 ValueError: If input 'name' is not of type str. 

143 

144 Examples: 

145 

146 >>> from colorspace import palette 

147 >>> #: Starting from an unnamed palette 

148 >>> pal = palette(["#11C638", "#E2E2E2", "#EF9708"]) 

149 >>> pal.name() # Returns None 

150 >>> 

151 >>> #: Naming the palette 

152 >>> pal.rename("Custom palette") 

153 >>> pal.name() 

154 >>> 

155 >>> #: Rename 

156 >>> pal.rename("Modified palette name") 

157 >>> pal.name() 

158 >>> 

159 >>> #: Unname (replace current name with None) 

160 >>> pal.rename(None) 

161 >>> pal.name() # Returns None 

162  

163 """ 

164 if not isinstance(name, (type(None), str)): 

165 raise ValueError("argument `name` must be None or a str") 

166 self._name = name 

167 

168 def name(self): 

169 """Get Palette Name 

170 

171 Returns: 

172 Returns `None` if the palette is unnamed, else 

173 the name of the palette as `str`. 

174 

175 Examples: 

176 >>> from colorspace import palette 

177 >>> # Unnamed palette 

178 >>> pal = palette(["#11C638", "#E2E2E2", "#EF9708"]) 

179 >>> pal.name() 

180 >>> #: 

181 >>> type(pal.name()) 

182 >>> #: Named palette 

183 >>> pal = palette(["#11C638", "#E2E2E2", "#EF9708"], 

184 >>> name = "My Custom Palette") 

185 >>> pal.name() 

186 """ 

187 return self._name 

188 

189 def colors(self, *args, **kwargs): 

190 """Get Palette Colors 

191 

192 Returns the colors of the current palette as a list 

193 of hex colors (`str`). 

194 

195 Args: 

196 *args: Ignored. 

197 **kwargs: Ignored. 

198 

199 Returns: 

200 list: List of all colors of the palette. 

201 

202 Examples: 

203 >>> from colorspace import palette 

204 >>> pal = palette(["#11C638", "#E2E2E2", "#EF9708"], 

205 >>> name = "My Custom Palette") 

206 >>> pal.colors() 

207 """ 

208 return self._colors 

209 

210 def swatchplot(self, **kwargs): 

211 """Palette Swatch Plot 

212 

213 Interfacing the main :py:func:`swatchplot <colorspace.swatchplot.swatchplot>` 

214 function. Plotting the spectrum of the current color palette. 

215 

216 Args: 

217 **kwargs: forwarded to :py:func:`swatchplot <colorspace.swatchplot.swatchplot>`. 

218 Note that `show_names` will always be set to `False`. 

219 

220 Return: 

221 Returns what :py:func:`colorspace.swatchplot.swatchplot` returns. 

222 

223 Example: 

224 

225 >>> from colorspace import palette 

226 >>> pal = palette(["#FCFFC9", "#E8C167", "#D67500", "#913640", "#1D0B14"], 

227 >>> name = "Custom Palette") 

228 >>> pal.swatchplot() 

229 >>> pal.swatchplot(figsize = (5, 1)) 

230 """ 

231 

232 from .swatchplot import swatchplot 

233 if "show_names" in kwargs.keys(): 

234 del kwargs["show_names"] 

235 return swatchplot(pals = self.colors(), show_names = False, **kwargs) 

236 

237 def specplot(self, *args, **kwargs): 

238 """Color Spectrum Plot 

239 

240 Interfacing the :py:func:`colorspace.specplot.specplot` function. 

241 Plotting the spectrum of the current color palette. 

242 

243 Args: 

244 *args: Forwarded to :py:func:`colorspace.specplot.specplot`. 

245 **kwargs: Forwarded to :py:func:`colorspace.specplot.specplot`. 

246 

247 Return: 

248 Returns what :py:func:`colorspace.specplot.specplot` returns. 

249 

250 Example: 

251 

252 >>> from colorspace import palette, diverging_hcl 

253 >>> # Default diverging HCL palette 

254 >>> pal = palette(diverging_hcl().colors(7)) 

255 >>> pal.specplot() 

256 >>> pal.specplot(rgb = False) 

257 """ 

258 

259 from .specplot import specplot 

260 return specplot(self.colors(), *args, **kwargs) 

261 

262 

263 def hclplot(self, **kwargs): 

264 """Palette Plot in HCL Space 

265 

266 Internally calls :py:func:`hclplot <colorspace.hclplot.hclplot>`, 

267 additional arguments to this main function can be forwarded via the 

268 `**kwargs` argument. 

269 

270 Args: 

271 **kwargs: Additional named arguments forwarded to 

272 :py:func:`hclplot <colorspace.hclplot.hclplot>`. 

273 

274 Return: 

275 Returns what :py:func:`colorspace.hclplot.hclplot` returns. 

276 

277 Example: 

278 

279 >>> from colorspace import palette, diverging_hcl 

280 >>> pal = palette(diverging_hcl().colors(7)) 

281 >>> pal.hclplot() 

282 """ 

283 

284 from .hclplot import hclplot 

285 return hclplot(x = self.colors(), **kwargs) 

286 

287 

288 def cmap(self, continuous = True): 

289 """Create Matplotlib Compatible Color Map 

290 

291 Converts the current palette into a 

292 `matplotlib.colors.LinearSegmentedColormap` color map based on the 

293 colors provided creating this palette object. If `continuous = True` 

294 a series of `256` unique colors will be created using linear 

295 interpolation in the standard RGB color space. If `continuous = False` 

296 the resulting color map is solely based on the number of colors of 

297 the palette which yields a non-continuous color map with step-functions 

298 in R, G, and B (see Example). 

299 

300 Args: 

301 continuous (bool): If `True` (default) the resulting colormap 

302 will contain 256 colors, linearely interpolated in between 

303 the colors of the palette. If `False`, only the `N` colors 

304 of the palette are used (see Examples). 

305 

306 Return: 

307 matplotlib.colors.LinearSegmentedColormap: Colormap to be used with 

308 matplotlib. 

309 

310 Example: 

311 

312 >>> from colorspace import diverging_hcl, palette, specplot 

313 >>> pal = diverging_hcl() 

314 >>> pal = palette(pal(5), name = "Diverging Palette with 5 Colors") 

315 >>> 

316 >>> # Continuous colormap 

317 >>> cmap1 = pal.cmap(continuous = True) 

318 >>> cmap1.N # Internal number of colors 

319 >>> 

320 >>> #: Non-continuous version of the colormap 

321 >>> cmap2 = pal.cmap(continuous = False) 

322 >>> cmap2.N # Internal number of colors 

323 >>> 

324 >>> #: Using helper function for demonstration 

325 >>> specplot(cmap1, rgb = True, figsize = (8, 6)); 

326 >>> #: 

327 >>> specplot(cmap2, rgb = True, figsize = (8, 6)); 

328 

329 Raises: 

330 TypeError: If `continuous` is not bool 

331 """ 

332 

333 from matplotlib.colors import LinearSegmentedColormap 

334 

335 if not isinstance(continuous, bool): 

336 raise TypeError("argument `continuous` must be bool") 

337 

338 # Create colormap 

339 n = 256 if continuous else len(self.colors()) 

340 cmap = LinearSegmentedColormap.from_list(self.name(), self.colors(), n) 

341 return cmap 

342 

343 

344 

345# ------------------------------------------------------------------- 

346# ------------------------------------------------------------------- 

347class defaultpalette: 

348 """Pre-defined HCL Color Palettes 

349 

350 Object for handling the pre-defined HCL-based color palettes, not intended 

351 to be used by the users. 

352 

353 Args: 

354 type (str): Type of palette. 

355 method (str): Name of the method which has to be called to retrieve colors 

356 (e.g., :py:class:`colorspace.palettes.diverging_hcl`). 

357 name (str): Name of the color palette. 

358 settings (dict): Dictionary containing the parameter settings. 

359 

360 Returns: 

361 Object of class `colorspace.palettes.defaultpalette`. 

362 """ 

363 

364 def __init__(self, type, method, name, settings): 

365 

366 self._type_ = type 

367 self._name_ = name 

368 self._method_ = method 

369 self._settings_ = settings 

370 

371 # Default representation of defaultpalette objects. 

372 def __repr__(self): 

373 """Standard Representation 

374 

375 Prints the current settings on stdout. 

376 Development method.""" 

377 

378 res = [] 

379 res.append("Palette Name: {:s}".format(self.name())) 

380 res.append(" Type: {:s}".format(self.type())) 

381 res.append(" Inspired by: {:s}".format(self.get("desc"))) 

382 #for key,val in self._settings_.items(): 

383 keys = list(self.get_settings().keys()) 

384 keys.sort() 

385 for key in keys: 

386 if key in ["desc"]: continue 

387 val = self.get(key) 

388 if isinstance(val, bool): val = " True" if val else "False" 

389 elif isinstance(val, int): val = "{:5d}".format(val) 

390 elif isinstance(val, float): val = "{:5.1f}".format(val) 

391 elif callable(val): val = "{:s}".format(str(val)) 

392 res.append(" {:10s} {:s}".format(key,val)) 

393 

394 return "\n".join(res) 

395 

396 def __call__(self, n = 11): 

397 """Get Colors 

398 

399 Wrapper function for :py:func:`colors`. 

400 

401 Args: 

402 n (int): Number of colors, defaults to 7. 

403 

404 Returns: 

405 list: List of hex colors. 

406 """ 

407 return self.colors(n) 

408 

409 def method(self): 

410 """Get Construction Method 

411 

412 Returns method used to create this palette. 

413 

414 Returns: 

415 Returns the method (`str`, name of the function to be called 

416 to create the palette) of the palette. 

417 """ 

418 return self._method_ 

419 

420 def type(self): 

421 """Get Palette Type 

422 

423 Get type of the color palette. 

424 

425 Returns: 

426 Returns the type (`str`) of the palette. 

427 """ 

428 return self._type_ 

429 

430 def name(self): 

431 """Get Palette Name 

432 

433 Get name of color palette. 

434 

435 Returns: 

436 str: Returns the name of the palette. 

437 """ 

438 return self._name_ 

439 

440 def rename(self, name): 

441 """Rename Palette 

442 

443 Allows to rename the palette. 

444 

445 Args: 

446 name (str): New palette name. 

447 """ 

448 self._name_ = name 

449 

450 def get(self, what): 

451 """Get Specific Palette Settings 

452 

453 Allows to load the settings of the palette for the different parameters 

454 (e.g., `h1`, `h2`, ...). Returns `None` if the parameter does not 

455 exist. Another method (:py:func:`set`) allows to set the parameters. 

456 

457 Args: 

458 what (str): Name of the parameter which should be extracted and 

459 returned from the settings of this color palette. 

460 

461 Returns: 

462 Returns `None` if the parameter `what` cannot be found, 

463 else the value of the parameter `what` is returned. 

464 """ 

465 if what in self._settings_.keys(): 

466 return self._settings_[what] 

467 else: 

468 return None 

469 

470 

471 def set(self, lambda_allowed = False, **kwargs): 

472 """Set Specific Palette Settings 

473 

474 Allows to set/overwrite color palette parameters (e.g., `h1`, `h2`, 

475 ...). Another method (:py:func:`get`) allows to retrieve the 

476 parameters. 

477 

478 Args: 

479 lambda_allowed (bool): Defaults to `False`, for qualitative palettes this 

480 is set `True` for `h2`. 

481 **kwargs: A set of named arguments (`key = value` pairs) where the key 

482 defines the parameter which should be overruled, value the 

483 corresponding value. Allowed value types are bool, int, and float. 

484 """ 

485 if not isinstance(lambda_allowed, bool): 

486 ValueError("argument `lambda_allowed` must be bool") 

487 

488 for key,val in kwargs.items(): 

489 # Float or integer? Default case; passing forward 

490 if isinstance(val, (float, int)): 

491 pass 

492 # If key == h2 and val is callable, we do allow this (for qualitative palettes) 

493 # if lambda_allowed is set True; passing foward as well 

494 elif key == "h2" and lambda_allowed and callable(val): 

495 pass 

496 # Else check current type specification and append 

497 # if possible (and convert to the new type). 

498 elif not isinstance(val, (int, float, bool)): 

499 raise ValueError(f"argument `{key}` to {self.__class__.__name__}" + \ 

500 f" is of type {type(val)}; only bool, int, and float allowed.") 

501 elif isinstance(val, bool): 

502 val = 1 if val else 0 

503 else: 

504 raise Exception(f"whoops, no rule yet to handle {key} = {val}") 

505 

506 # Not yet a parameter in our dictionary? Add as float 

507 if not key in self._settings_.keys(): 

508 self._settings_[key] = float(val) 

509 # If already existing we convert the new value into the existing type. 

510 elif isinstance(self._settings_[key], int): 

511 self._settings_[key] = int(val) 

512 # If setitng exists and is float, int, or a lambda function: replace 

513 # If val is itself a callable function, take it as is. Else convert to float. 

514 elif isinstance(self._settings_[key], (int, float)) or callable(self._settings_[key]): 

515 self._settings_[key] = val if callable(val) else float(val) 

516 else: 

517 raise Exception(f"whoops, some code needed here in {self.__class__.__name__}.set") 

518 

519 

520 def get_settings(self): 

521 """Get All Palette Settings 

522 

523 Allows to get the current settings of the palette object. 

524 To retrieve single parameters use :py:func:`get`. 

525 

526 Returns: 

527 Returns a `dict` object with all parameter specification of this 

528 palette. 

529 """ 

530 return self._settings_ 

531 

532 def colors(self, n = 11): 

533 """Get Colors 

534 

535 Load a set of `n` colors from this palette. This method evaluates 

536 the `method` argument to generate a set of hex colors which will be 

537 returned. Please note that it is possible that none-values will be 

538 returned if the fixup-setting is set to `False` (see 

539 :py:class:`colorlib.hexcols`). 

540 

541 Args: 

542 n (int): Number of colors to be returned, defaults to 11. 

543 

544 Returns: 

545 list: Returns a list of str with `n` colors from the palette. 

546 """ 

547 

548 # Dynamically load color function 

549 mod = __import__("colorspace") 

550 cfun = getattr(mod, self._method_) 

551 

552 # Calling color method with arguments of this object.  

553 from copy import copy 

554 args = copy(self.get_settings()) 

555 

556 pal = cfun(**args) 

557 return pal.colors(n, fixup = True) 

558 

559 

560# ------------------------------------------------------------------- 

561# ------------------------------------------------------------------- 

562class hclpalettes: 

563 """Prepare Pre-defined HCL Palettes 

564 

565 Prepares the pre-specified hclpalettes. Reads the config files and creates 

566 a set of `defaultpalette` objects. 

567 

568 See also: :py:func:`divergingx_palettes <colorspace.hcl_palettes.divergingx_palettes>`. 

569 

570 Args: 

571 files (None, str list): If `None` (default) the default color palette 

572 configuration from within the package will be loaded. A path to a custom 

573 config file (str) or a list of paths can be provided to read custom 

574 palettes. 

575 files_regex (None, str): Additional regular expression to filter files. 

576 Only used if `files = None`. 

577 

578 Return: 

579 hclpalettes: Collection of predefined hcl color palettes. 

580 

581 Examples: 

582 

583 >>> from colorspace import hclpalettes 

584 >>> hclpals = hclpalettes() 

585 >>> hclpals 

586 >>> #: Palette swatch plots with 5 colors each 

587 >>> hclpals.plot(n = 5); 

588 >>> #: Palette swatch plots with 11 colors each 

589 >>> hclpals.plot(n = 11); 

590 """ 

591 def __init__(self, files = None, files_regex = None): 

592 

593 from os.path import dirname, join, isfile 

594 

595 if not isinstance(files, (type(None), str, list)): 

596 raise TypeError("argument `files` must either be None, str, or list of str") 

597 if isinstance(files, str): files = [files] 

598 if isinstance(files, list): 

599 for file in files: 

600 if not isinstance(file, str): 

601 raise TypeError("not all elements in `files` are of type str") 

602 

603 if files is None: 

604 import glob 

605 resource_package = dirname(__file__) 

606 tmp = glob.glob(join(resource_package, "palconfig", "*.conf")) 

607 # Ensure files_regex is of appropriate type 

608 if not isinstance(files_regex, (str, type(None))): 

609 raise TypeError("argument `filex_regex` must be None or str") 

610 if files_regex: 

611 from re import match 

612 files = [] 

613 for f in tmp: 

614 if match(files_regex, f): files.append(f) 

615 else: 

616 files = tmp 

617 

618 # Input 'files' specified: 

619 if not len(files) > 0: 

620 raise ValueError(f"no palette config files provided ({self.__class__.__name__})") 

621 for file in files: 

622 if not isfile(file): 

623 raise FileNotFoundError(f"file \"{file}\" does not exist") 

624 

625 

626 # Else trying to load palettes and append thenm to _palettes_ 

627 self._palettes_ = {} 

628 for file in files: 

629 [palette_type, pals] = self._load_palette_config_(file) 

630 if pals: self._palettes_[palette_type] = pals # append 

631 

632 # A poor attempt to order the palettes somehow 

633 x = list(self._palettes_.keys()) 

634 x.sort(reverse = True) 

635 tmp = {} 

636 

637 for rec in x: tmp[rec] = self._palettes_[rec] 

638 self._palettes_ = tmp 

639 

640 

641 def __repr__(self): 

642 """Standard Representation 

643 

644 Standard representation of the object.""" 

645 res = ["HCL palettes"] 

646 

647 for type_ in self.get_palette_types(): 

648 res.append("") # Blank line 

649 res.append("Type: {:s}".format(type_)) 

650 nchar = 0 

651 for pal in self.get_palettes(type_): 

652 # Initialize new line 

653 if nchar == 0: 

654 tmp = "Names: {:s}".format(pal.name()) 

655 nchar = len(tmp) 

656 elif (len(pal.name()) + nchar) > 60: 

657 res.append(tmp) 

658 tmp = " {:s}".format(pal.name()) 

659 nchar = len(tmp) 

660 # Append 

661 else: 

662 tmp += ", {:s}".format(pal.name()) 

663 nchar += len(pal.name()) + 2 

664 res.append(tmp) 

665 

666 return "\n".join(res) 

667 

668 

669 def get_palette_types(self): 

670 """Get Palette Types 

671 

672 Get all palette types. 

673 

674 Returns: 

675 list: Returns a `list` of str with the names of all palette types 

676 or groups. 

677 """ 

678 

679 return list(self._palettes_.keys()) 

680 

681 def get_palettes(self, type_ = None, exact = False): 

682 """Get Type-Specific Palettes 

683 

684 Get all palettes of a specific type. 

685 

686 Args: 

687 type_ (None, str): (Partial) Name of the palettes which should be returned. 

688 String matching is used; partial matches are allowed. 

689 If set to `None` (default) all palettes will be returned. Names 

690 have to match but are not case sensitive, defaults to None. 

691 exact (bool): If `False` (default) partial matching is used. If `True`, 

692 `type_` must be an exact match (case sensitive). 

693 

694 Examples: 

695 >>> # Initialize hclpalettes object 

696 >>> from colorspace import hclpalettes 

697 >>> hclpals = hclpalettes() 

698 >>> 

699 >>> # Get all Diverging palettes 

700 >>> pals1 = hclpals.get_palettes("Diverging") 

701 >>> len(pals1) 

702 >>> 

703 >>> #: Get 'Advanced: Diverging' palettes, also includes 

704 >>> # 'Advanced: DivergingX' (partial match). 

705 >>> pals2 = hclpals.get_palettes("Advanced: Diverging") 

706 >>> len(pals2) 

707 >>> 

708 >>> #: Only get 'Advanced: Diverging' (exact match) 

709 >>> pals3 = hclpals.get_palettes("Advanced: Diverging", exact = True) 

710 >>> len(pals3) 

711 >>> #: 

712 >>> pals3 

713 

714 Returns: 

715 Returns a `list` containing `defaultpalette` objects objects. 

716 

717 Raises: 

718 TypeError: If `type_` is not str or None. 

719 TypeError: If `exact` is not bool. 

720 ValueError: If no matching palette is found. 

721 """ 

722 if not isinstance(type_, (str, type(None))): 

723 raise TypeError("argument `type_` must be str or None") 

724 if not isinstance(exact, bool): 

725 raise TypeError("argument `exact` must be bool.") 

726 

727 # Return all available palettes 

728 if not type_: 

729 res = [] 

730 for key,pals in self._palettes_.items(): res += pals 

731 

732 # Else find matching palettes (given type_). Either exact matches or 

733 # partial matches depending on `exact`. 

734 else: 

735 from re import compile, IGNORECASE, escape 

736 if not exact: 

737 pattern = compile(f".*?{escape(type_)}.*?", IGNORECASE) 

738 else: 

739 pattern = compile(f"^{escape(type_)}$") 

740 

741 # Searching trough available palettes 

742 res = [] 

743 for t in self._palettes_.keys(): 

744 if pattern.match(t): 

745 res += self._palettes_[t] 

746 

747 # No palettes found? Raise ValueError 

748 if len(res) == 0: 

749 raise ValueError(f"no palettes for type \"{type_}\" ({'exact' if exact else 'partial'} match)") 

750 

751 # Else return list with palettes 

752 return res 

753 

754 def get_palette(self, name): 

755 """Get Palette by Name 

756 

757 Get a palette with a specific name. 

758 

759 Args: 

760 name (str): Name of the color palette which should be returned. Not 

761 case sensitive; blanks are ignored (removed). 

762 

763 Returns: 

764 Returns an object of class `defaultpalette` if a palette with 

765 the name as specified can be found. Else an error will be dropped. 

766 """ 

767 

768 # Try to find the palette with the name 'name' 

769 take_pal = None 

770 for type_,pals in self._palettes_.items(): 

771 # Looping over palettes 

772 for pal in pals: 

773 if pal.name().upper().replace(" ", "") == name.upper().replace(" ", ""): 

774 take_pal = pal 

775 break; 

776 # If already found: break outer loop 

777 if take_pal: break; 

778 

779 # If none: not found 

780 if take_pal is None: 

781 raise ValueError(f"palette \"{name}\" not found") 

782 

783 # Else return list with palettes 

784 return take_pal 

785 

786 

787 # Helper method to load the palette config files. 

788 def _load_palette_config_(self, file): 

789 

790 import re 

791 import sys 

792 from configparser import ConfigParser 

793 

794 CNF = ConfigParser() 

795 CNF.read(file) 

796 

797 # Reading type (or name) 

798 try: 

799 palette_type = CNF.get("main", "type") 

800 palette_method = CNF.get("main", "method") 

801 except Exception as e: 

802 raise Exception(f"misspecification in palconfig file = \"{file}\": {str(e)}") 

803 

804 # The dictionary which will be returned. 

805 pals = [] 

806 

807 # Looping over all sections looking for palette specifications. 

808 for sec in CNF.sections(): 

809 mtch = re.match("^palette\\s+(.*)$", sec) 

810 if not mtch: continue 

811 

812 # Extracting palette name from section name 

813 name = mtch.group(1).strip() 

814 

815 # Loading all available setting elements. 

816 # "desc": interpreted as character 

817 # "p1/p1": interpreted as float 

818 # "fixup": interpreted as bool 

819 # rest: interpreted as int 

820 settings = {} 

821 for key,val in CNF.items(sec): 

822 key = key.lower() 

823 if key in ["desc"]: 

824 settings[key] = val 

825 elif key in ["fixup"]: 

826 settings[key] = True if int(val) else False 

827 elif key in ["p1","p2", "p3", "p4"]: 

828 settings[key] = float(val) 

829 elif key in ["h1", "h2"]: 

830 if re.match("^-?[0-9\\.]+$", val): 

831 settings[key] = int(val) 

832 else: 

833 # Try to evaluate this as a lambda function. 

834 try: 

835 val = eval(val) 

836 if not callable(val): raise Exception 

837 except: 

838 raise ValueError(f"element '{key}' for palette '{sec}' neither an int nor proper lambda function") 

839 # Append lambda function to the settings 

840 settings[key] = val 

841 

842 else: 

843 settings[key] = int(val) 

844 

845 pals.append(defaultpalette(palette_type, palette_method, name, settings)) 

846 

847 # Return dictionary with palettes 

848 if len(pals) == 0: 

849 return [None, None] 

850 else: 

851 return [palette_type, pals] 

852 

853 

854 def length(self): 

855 """Get Number of Palettes 

856 

857 Get length of palette. 

858 

859 Returns: 

860 int: Integer, number of palettes in the object. 

861 """ 

862 

863 npals = 0 

864 for type_ in self.get_palette_types(): 

865 for pal in self.get_palettes(type_): npals += 1 

866 

867 return npals 

868 

869 

870 def plot(self, n = 5): 

871 """Palette Swatch Plot 

872 

873 Interfacing the main :py:func:`swatchplot <colorspace.swatchplot.swatchplot>` 

874 function. Plotting the spectrum of the current color palette. 

875 

876 Args: 

877 n (int): Number of colors, defaults to 7. 

878 """ 

879 if not isinstance(n, int): raise TypeError("argument `n` must be int") 

880 if not n > 0: raise ValueError("argument `n` must be positive") 

881 

882 from .swatchplot import swatchplot 

883 swatchplot(self, n = n) 

884 

885 

886# ------------------------------------------------------------------- 

887# ------------------------------------------------------------------- 

888class hclpalette: 

889 """HCL Palette Superclass 

890 

891 Hy, I am the base class. Is extended by the different HCL based color 

892 palettes such as the classes :py:class:`diverging_hcl`, :py:class:`qualitative_hcl`, 

893 :py:class:`rainbow_hcl`, :py:class:`sequential_hcl`, and maybe even more in the future. 

894 """ 

895 

896 # Default call: return n hex colors 

897 def __call__(self, *args, **kwargs): 

898 """__call__(*args, **kwargs) 

899 

900 Call interface, calls objects `colors(...)` method. 

901 

902 Args: 

903 *args: Unnamd arguments forwarded to the color method. 

904 **kwargs: Named arguments forwarded to the color method. 

905 

906 Returns: 

907 See colors method. 

908 """ 

909 return self.colors(*args, **kwargs) 

910 

911 def specplot(self, n = 180, *args, **kwargs): 

912 """Color Spectrum Plot 

913 

914 Interfacing the :py:func:`colorspace.specplot.specplot` function. 

915 Plotting the spectrum of the current color palette. 

916 

917 Args: 

918 n (int): Number of colors, defaults to 180. 

919 *args: Forwarded to :py:func:`colorspace.specplot.specplot`. 

920 **kwargs: Forwarded to :py:func:`colorspace.specplot.specplot`. 

921 

922 Return: 

923 Returns what :py:func:`colorspace.specplot.specplot` returns. 

924 

925 Example: 

926 

927 >>> # Default diverging HCL palette 

928 >>> from colorspace import diverging_hcl 

929 >>> pal = diverging_hcl() 

930 >>> pal.specplot() 

931 >>> #: 

932 >>> pal.specplot(rgb = True) 

933 >>> 

934 >>> #: Default sequential HCL palette 

935 >>> from colorspace import sequential_hcl 

936 >>> pal = sequential_hcl() 

937 >>> pal.specplot(figsize = (8, 4)) 

938 >>> 

939 >>> #: Default qualitative HCL palette 

940 >>> from colorspace import qualitative_hcl 

941 >>> pal = qualitative_hcl() 

942 >>> pal.specplot(figsize = (8, 4), hcl = False, rgb = True) 

943 """ 

944 

945 from .specplot import specplot 

946 return specplot(self.colors(n), *args, **kwargs) 

947 

948 def swatchplot(self, n = 7, **kwargs): 

949 """Palette Swatch Plot 

950 

951 Interfacing the main :py:func:`swatchplot <colorspace.swatchplot.swatchplot>` 

952 function. Plotting the spectrum of the current color palette. 

953 

954 Args: 

955 n (int): Number of colors, defaults to 7. 

956 **kwargs: forwarded to :py:func:`swatchplot <colorspace.swatchplot.swatchplot>`. 

957 

958 Return: 

959 Returns what :py:func:`colorspace.swatchplot.swatchplot` returns. 

960 

961 Example: 

962 

963 >>> # Exemplarily for diverging_hcl, works for 

964 >>> # all other HCL palettes as well. 

965 >>> from colorspace import diverging_hcl 

966 >>> pal = diverging_hcl() 

967 >>> pal.swatchplot(figsize = (8, 2)) 

968 >>> #: Handing over a series of additional arguments 

969 >>> # forwarded to swatchplot() 

970 >>> pal.swatchplot(n = 21, figsize = (8, 2), 

971 >>> show_names = False, cvd = "deutan") 

972 >>> #: 

973 >>> pal.swatchplot(n = 21, figsize = (8, 2), 

974 >>> show_names = False, 

975 >>> cvd = ["protan", "deutan", "tritan", "desaturate"]) 

976 """ 

977 

978 from .swatchplot import swatchplot 

979 return swatchplot(self.colors(n), **kwargs) 

980 

981 

982 def hclplot(self, n = 7, **kwargs): 

983 """Palette Plot in HCL Space 

984 

985 Internally calls :py:func:`hclplot <colorspace.hclplot.hclplot>`, 

986 additional arguments to this main function can be forwarded via the 

987 `**kwargs` argument. 

988 

989 Args: 

990 n (int): Number of colors, defaults to 7. 

991 **kwargs: Additional named arguments forwarded to 

992 :py:func:`hclplot <colorspace.hclplot.hclplot>`. 

993 

994 Return: 

995 Returns what :py:func:`colorspace.hclplot.hclplot` returns. 

996 

997 Example: 

998 

999 >>> from colorspace import diverging_hcl 

1000 >>> pal = diverging_hcl() 

1001 >>> pal.hclplot() 

1002 >>> pal.hclplot(n = 11) 

1003 """ 

1004 

1005 from .hclplot import hclplot 

1006 return hclplot(x = self.colors(n), **kwargs) 

1007 

1008 def name(self): 

1009 """Get Palette Name 

1010 

1011 Get name (generic) of color palette. 

1012 

1013 Returns: 

1014 str: Returns the name of the palette. 

1015 

1016 Examples: 

1017 >>> from colorspace import * 

1018 >>> pal1 = diverging_hcl() 

1019 >>> pal1.name() 

1020 >>> #: 

1021 >>> pal2 = sequential_hcl("ag_Sunset") 

1022 >>> pal2.name() 

1023 >>> #: 

1024 >>> pal3 = heat_hcl() 

1025 >>> pal3.name() 

1026 >>> #: 

1027 >>> pal4 = sequential_hcl("Rocket") 

1028 >>> pal4.name() 

1029 """ 

1030 return self._name 

1031 

1032 def get(self, key): 

1033 """Get Specific Palette Setting 

1034 

1035 Returns one specific item of the palette settings, 

1036 e.g., the current value for `h1` or `l2`. 

1037 If not existing a `None` will be returned. 

1038 

1039 Args: 

1040 key (str): Name of the setting to be returned. 

1041 

1042 Returns: 

1043 None if `key` does ont exist, else the current value will be 

1044 returned. 

1045 

1046 Example: 

1047 

1048 >>> # Exemplarily for rainbow_hcl (works for the 

1049 >>> # other HCL palettes as well) 

1050 >>> from colorspace import rainbow_hcl 

1051 >>> a = rainbow_hcl() 

1052 >>> a.get("h1") 

1053 >>> #: 

1054 >>> a.get("c1") 

1055 >>> #: 

1056 >>> a.get("l1") 

1057 >>> #: 

1058 >>> a.get("not_defined") 

1059 """ 

1060 if not key in self.settings.keys(): 

1061 return None 

1062 return self.settings[key] 

1063 

1064 def show_settings(self): 

1065 """Show Palette Settings 

1066 

1067 Shows the current settings (table like print to stdout). Should more be 

1068 seen as a development method than a very useful thing. 

1069 

1070 Example: 

1071 

1072 >>> from colorspace.palettes import rainbow_hcl 

1073 >>> a = rainbow_hcl(10) 

1074 >>> a.show_settings() 

1075 """ 

1076 

1077 def get(key): 

1078 val = self.get(key) 

1079 if val is None: 

1080 return f"{key:7s} ---" 

1081 elif callable(val): 

1082 return f"{key:7s} <lambda>" 

1083 elif isinstance(val, bool): 

1084 return f"{key:7s} {str(val):>8s}" 

1085 elif isinstance(val, int): 

1086 return f"{key:7s} {val:8d}" 

1087 else: 

1088 return f"{key:7s} {val:8.1f}" 

1089 

1090 from .palettes import divergingx_hcl 

1091 

1092 if not isinstance(self, divergingx_hcl): 

1093 keys = ["h1", "h2", "c1", "cmax", "c2", "l1", "l2", "p1", "p2", "fixup"] 

1094 else: 

1095 keys = ["h1", "h2", "h3", "c1", "cmax1", "c2", "cmax2", "c3", 

1096 "l1", "l2", "l3", "p1", "p2", "p3", "p4", "fixup"] 

1097 

1098 print(f"Class: {self.__class__.__name__}") 

1099 for k in keys: print(get(k)) 

1100 

1101 

1102 # Better input handling 

1103 def _checkinput_(self, dtype, length_min = None, length_max = None, 

1104 recycle = False, nansallowed = False, **kwargs): 

1105 """Used to check/convert/extend input arguments to the palette functions. 

1106 

1107 Args: 

1108 dtype (object): E.g. int or float, the type in which the inputs 

1109 should be converted. 

1110 length_min (None, int): Optional. Minimum length of the input data. 

1111 If not fulfilled and `recycle` is set to True it expands the 

1112 input to `length_min`. Defaults to None, see also `length_max`. 

1113 lenth_max (None, int): Optional. Maximum length of the input data. 

1114 If longer, the script will stop. If not set (default is `None`) 

1115 only the `length_min` will be checked. 

1116 recycle (bool): if set to `True` the user inputs will be recycled 

1117 to match the expected number of inputs, defaults to False. 

1118 nansallowed (bool): if False an error will be raised if the final 

1119 arguments contain `numpy.nan` values. Else `numpy.nan`s are 

1120 passed trough and will be returned, defaults to False. 

1121 **kwargs: List of named arguments, the ones to be checked. If only 

1122 one is given the function returns the values of this specific 

1123 input. If multiple arguments are handed over a dict will be 

1124 returned with the names corresponding to the user input. 

1125 

1126 Returns: 

1127 If `kwargs` is of length one the values of this specific variable 

1128 will be returned. If multiple `kwargs` arguments are set a dict is 

1129 returned. Note that `None` will simply stay `None`. The function 

1130 raises errors if the user inputs do not match the required 

1131 specifications. 

1132 """ 

1133 

1134 # Support function 

1135 def fun(key, value, dtype, length_min, length_max, recycle, nansallowed): 

1136 

1137 from numpy import vstack, ndarray, asarray, isnan, nan, any, atleast_1d 

1138 

1139 if not nansallowed and any(isnan(value)): 

1140 raise ValueError(f"nan's not allowed in `{key}`") 

1141 elif isinstance(value, ndarray) and len(value) < length_min: 

1142 raise ValueError(f"argument `{key}` too short (< {length_min})") 

1143 

1144 # If None 

1145 if value == None: return value 

1146 

1147 # Converting the data 

1148 try: 

1149 value = asarray([value], dtype = dtype).flatten() 

1150 except Exception as e: 

1151 raise ValueError(f"incorrect input on argument `{key}`: {str(e)}") 

1152 

1153 # Vector of length 0? 

1154 if len(value) == 0: 

1155 raise ValueError(f"argument `{key}` of length 0") 

1156 

1157 # Not enough input values, check if we are allowed to 

1158 # recycle. 

1159 if length_min and len(value) < length_min: 

1160 # Input was too short: check if we are allowed to 

1161 # recycle the value or not. 

1162 if not recycle: 

1163 raise ValueError(f"wrong length of input \"{key}\", expected min {length_min} elements, " + \ 

1164 f"got {len(value)} when calling {self.__class__.__name__}") 

1165 else: 

1166 value = vstack([value] * length_min).flatten()[0:length_min] 

1167 elif length_min and not length_max and len(value) > length_min: 

1168 value = value[0:length_min] 

1169 

1170 # Check if the input exceeds length_max if set 

1171 if length_max and len(value) > length_max: 

1172 raise ValueError(f"wrong length of input \"{key}\", expected max {length_max} elements, " + \ 

1173 f"got {len(value)} when calling {self.__class__.__name__}") 

1174 # Cropping data if too much elements are given. 

1175 elif length_max and len(value) > length_max: 

1176 value = value[0:length_max] 

1177 

1178 # Checking nan's 

1179 if not nansallowed and any(isnan(value)): 

1180 raise ValueError(f"arguments for \"{key}\" to function calling {self.__class__.__name__}" + \ 

1181 "contain nan values: not allowed") 

1182 

1183 # Return single value if length is set to 1. 

1184 if len(value) == 1: value = value[0] 

1185 

1186 return atleast_1d(value) # Return 1d array 

1187 

1188 # Looping over all kwargs 

1189 for key,value in kwargs.items(): 

1190 if value is None: raise ValueError(f"argument `{key}` cannot be None") 

1191 kwargs[key] = fun(key, value, dtype, length_min, length_max, recycle, nansallowed) 

1192 

1193 # If only one kwarg was given: return values, else return dict. 

1194 if len(kwargs) == 1: 

1195 return kwargs[list(kwargs.keys())[0]] 

1196 else: 

1197 return kwargs 

1198 

1199 

1200 # Return matplotlib.colors.LinearSegmentedColormap 

1201 def cmap(self, n = 256, name = "custom_hcl_cmap"): 

1202 """Create Matplotlib Compatible Color Map 

1203 

1204 Allows to retrieve a matplotlib LinearSegmentedColormap color map. 

1205 Clasically LinearSegmentedColormaps allow to retrieve a set of `N` 

1206 colors from a set of `n` colors where `N >> n`. The matplotlib 

1207 simply linearely interpolates between all `n` colors to extend 

1208 the number of colors to `N`. 

1209 

1210 In case of `hclpalette` objects this is not necessary as 

1211 `hclpalette` objects allow to retrieve `N` colors directly 

1212 along well-specified Hue-Chroma-Luminance paths. Thus, this method 

1213 returns a matplotlib color map with `n = N` colors. The linear  

1214 interpolation between the colors (as typically done by 

1215 LinearSegmentedColormap) is not necessary. However, for convenience 

1216 cmaps have been implemented such that you can easily use hcl based 

1217 palettes in your existing workflow. 

1218 

1219 Args: 

1220 n (int): Number of colors the cmap should be based on; default is `n = 256` 

1221 name (str): Name of the custom color map. Default is `custom_hcl_cmap` 

1222 

1223 Example: 

1224 

1225 >>> # Create LinearSegmentedColormap from diverging_hcl() palette. 

1226 >>> # By default, 256 distinct colors are used across the palette. 

1227 >>> from colorspace import diverging_hcl, specplot 

1228 >>> pal = diverging_hcl() 

1229 >>> cmap1 = pal.cmap() 

1230 >>> cmap1.N 

1231 >>> 

1232 >>> #: Same as above, but only using 5 distinct colors. 

1233 >>> cmap2 = pal.cmap(n = 5) 

1234 >>> cmap2.N 

1235 >>> 

1236 >>> #: Plotting HCL and sRGB spectrum for both cmaps 

1237 >>> specplot(cmap1, rgb = True, figsize = (8, 6)); 

1238 >>> #: 

1239 >>> specplot(cmap2, rgb = True, figsize = (8, 6)); 

1240 

1241 Returns: 

1242 Returns a `LinearSegmentedColormap` (cmap) to be used 

1243 with the matplotlib library. 

1244 

1245 Raises: 

1246 TypeError: If `n` is not int 

1247 ValueError: If `n` is lower than 2 

1248 """ 

1249 import matplotlib 

1250 from matplotlib.colors import LinearSegmentedColormap 

1251 from numpy import linspace, round, fmin, fmax 

1252 

1253 if not isinstance(n, int): 

1254 raise TypeError("argument `n` must be int") 

1255 elif n < 2: 

1256 raise ValueError("argument `n` must be >= 2") 

1257 

1258 cols = self.colors(n) 

1259 return LinearSegmentedColormap.from_list(name, cols, n) 

1260 

1261 

1262 def _set_rev(self, rev): 

1263 """Helper function: Store 'rev' argument 

1264 

1265 Args: 

1266 rev (bool): Should the palette be reversed? 

1267 

1268 Raises: 

1269 TypeError: If argument `rev` is not bool. 

1270 """ 

1271 if not isinstance(rev, bool): 

1272 raise TypeError("argument `rev` must e bool") 

1273 self._rev = rev # Just store it 

1274 

1275 def _get_alpha_array(self, alpha, n): 

1276 """Get numpy.ndarray for alpha values 

1277 

1278 The .color() method allowes to specify an additonal alpha 

1279 channel, a value between 0. (fully opaque) to 1. (fully transparent) 

1280 which can be provided in different ways. 

1281 

1282 Args: 

1283 alpha (None, float, list, or numpy.ndarray): Can be `None` 

1284 (default), a single float, a list, or a numpy array. If a list or 

1285 array is provided it must be of length 1 or of length `n` and be 

1286 convertible to float, providing values between `0.0` (full opacity) 

1287 and `1.0` (full transparency) 

1288 n (int): Number of colors (must be > 0). 

1289 Raises: 

1290 TypeError: If `n` is not int. 

1291 ValueError: If `n` is not > 0. 

1292 TypeError: If `alpha` is not among the allowed types. 

1293 ValueError: If `alpha` is a numpy.array of length > 1 but not of length n. 

1294 

1295 Return: 

1296 None, numpy.ndarray: Returns None (if input alpha is None) or a numpy 

1297 numeric numpy array of length 'n'. 

1298 """ 

1299 import numpy as np 

1300 

1301 if not isinstance(n, int): 

1302 raise TypeError("argument `n` must be int") 

1303 elif n <= 0: 

1304 raise ValueError("argument `n` (int) must larger than 0") 

1305 

1306 # checking alpha 

1307 if not isinstance(alpha, (type(None), float, list, np.ndarray)): 

1308 raise TypeError("argument `alpha` not among the allowed types") 

1309 elif isinstance(alpha, (list, np.ndarray)) and len(alpha) == 0: 

1310 raise ValueError("argument `alpha` is of length 0 (not allowed)") 

1311 

1312 # If alpha is None, we can return immediately 

1313 if alpha is None: return None 

1314 

1315 # If alpha is a numpy array of length > 0, but not equal to n, error. 

1316 if isinstance(alpha, np.ndarray) and len(alpha) > 1 and not len(alpha) == n: 

1317 raise ValueError("if `alpha` is a numpy array, it must be of the same length as `n`") 

1318 elif isinstance(alpha, list) and not len(alpha) == n: 

1319 raise ValueError("if `alpha` is a list, it must be of the same length as `n`") 

1320 elif isinstance(alpha, float): 

1321 alpha = np.repeat(alpha, n) 

1322 elif len(alpha) == 1: 

1323 try: 

1324 alpha = np.repeat(alpha[0], n) 

1325 alpha = alpha.dtype("float") 

1326 except Exception as e: 

1327 raise Exception(f"problems converting alpha: {e}") 

1328 else: 

1329 try: 

1330 alpha = np.asarray(alpha, dtype = "float") 

1331 except Exception as e: 

1332 raise Exception(f"problems converting alpha: {e}") 

1333 

1334 # Now we know we have a float array 

1335 if np.any(alpha < 0) or np.any(alpha > 1): 

1336 raise ValueError("values `alpha` must be in the range of [0.0, 1.0]") 

1337 

1338 # Returning numpy.ndarray 

1339 return alpha 

1340 

1341 def _chroma_trajectory(self, i, p1, c1, c2, cmax): 

1342 """Helper function: Calculate linear or triangle trajectory for chroma dimension. 

1343 

1344 Args: 

1345 i (numpy array; float): Position across the palette, a sequence 

1346 of values between 1 and 0. For diverging palettes this function 

1347 is called twice, once for 1 to 0.5, and once for <0.5 t0 0. 

1348 p1 (float): Power parameter p1. 

1349 c1 (float): Chroma value of the left end of the color palette. 

1350 c2 (float): Chroma value of the right end of the color palette. 

1351 cmax (float, None, np.nan): Max choma value. 

1352 

1353 Returns: 

1354 numpy array: Linear trajectory for the chroma color dimension. 

1355 """ 

1356 from numpy import isnan 

1357 

1358 def _linear_trajectory(i, c1, c2): 

1359 return c2 - (c2 - c1) * i 

1360 

1361 def _triangle_trajectory(i, j, c1, c2, cmax): 

1362 from numpy import where, abs, linspace 

1363 res = where(i <= j, 

1364 c2 - (c2 - cmax) * i / j, 

1365 cmax - (cmax - c1) * abs((i - j) / (1 - j))) 

1366 #print(res) 

1367 return res 

1368 

1369 if cmax is None or isnan(cmax): 

1370 C = _linear_trajectory(i**p1, c1, c2) 

1371 else: 

1372 # Calculate the position of the triangle point 

1373 j = 1. / (1. + abs(cmax - c1) / abs(cmax - c2)) 

1374 if not j is None and (j <= 0. or j >= 1.): j = None 

1375 

1376 if j is None: C = _linear_trajectory(i**p1, c1, c2) 

1377 else: C = _triangle_trajectory(i**p1, j, c1, c2, cmax) 

1378 

1379 return C 

1380 

1381 

1382 def _get_seqhcl(self, i, ha, hb, ca, cb, la, lb, pa, pb, cmax): 

1383 """Get Sequential Palette Colors 

1384 

1385 Get 'one side' of a sequential palette. This is also used 

1386 by `divergingx_hcl` which is, per construction, nothing else 

1387 than a combination of two flexible sequential palettes meeting 

1388 in the center. 

1389 

1390 Args: 

1391 i (numpy.ndarray): Sequence of floats in `[0, 1]` where to draw the 

1392 values from. Typically `linspace(1, 0, n)`, but for some palettes 

1393 (diverging palettes with an even number of colors) they do not meet at 0. 

1394 ha (float): Hue on end "A". 

1395 hb (float): Hue on end "B". 

1396 ca (float): Chroma on end "A". 

1397 cb (float): Chroma on end "B". 

1398 la (float): Luminance on end "A". 

1399 lb (float): Luminance on end "B". 

1400 pa (loat): Power parameter 1. 

1401 pb (loat): Power parameter 2. 

1402 cmax (float, None, np.nan): Max chroma. 

1403 

1404 Return: 

1405 list: List of `H`, `C`, and `L` coordinates. 

1406 """ 

1407 from numpy import power 

1408 

1409 # Hue and Luminance 

1410 H = hb - (hb - ha) * i 

1411 L = lb - (lb - la) * power(i, pb) 

1412 

1413 # Calculate the trajectory for the chroma dimension 

1414 C = self._chroma_trajectory(i, pa, ca, cb, cmax) 

1415 

1416 return [H, C, L] 

1417 

1418 

1419# ------------------------------------------------------------------- 

1420# ------------------------------------------------------------------- 

1421class qualitative_hcl(hclpalette): 

1422 """Qualitative HCL Color Palettes 

1423 

1424 The HCL (hue-chroma-luminance) color model is a perceptual color model 

1425 obtained by using polar coordinates in CIELUV space 

1426 (i.e., :py:class:`polarLUV <colorspace.colorlib.polarLUV>`), 

1427 where steps of equal size correspond to approximately equal perceptual 

1428 changes in color. By taking polar coordinates the resulting three 

1429 dimensions capture the three perceptual axes very well: hue is the type of 

1430 color, chroma the colorfulness compared to the corresponding gray, and 

1431 luminance the brightness. This makes it relatively easy to create balanced 

1432 palettes through trajectories in this HCL space. In contrast, in the more 

1433 commonly-used ‘HSV’ (hue-saturation-value) model (a simple transformation 

1434 of ‘RGB’), the three axes are confounded so that luminance changes along 

1435 with the hue leading to very unbalanced palettes. 

1436 

1437 `qualitative_hcl` distinguishes the underlying categories by a 

1438 sequence of hues while keeping both chroma and luminance constant to give 

1439 each color in the resulting palette the same perceptual weight. Thus, `h` 

1440 should be a pair of hues (or equivalently `h1` and `h2` can be used) with 

1441 the starting and ending hue of the palette. Then, an equidistant sequence 

1442 between these hues is employed, by default spanning the full color wheel 

1443 (i.e, the full 360 degrees). Chroma `c` (or equivalently `c1`) and 

1444 luminance `l` (or equivalently `l1`) are constants. If `h` is str it will 

1445 overwrite the `palette` argument. In this case, pre-specified palette 

1446 settings will be loaded but are allowed to be overwritten by the user. At 

1447 any time the user can overwrite any of the settings. If `h` is str it will 

1448 overwrite the `palette` argument. In this case, pre-specified palette 

1449 settings will be loaded but are allowed to be overwritten by the user. At 

1450 any time the user can overwrite any of the settings. 

1451 

1452 By default, `qualitative_hcl` returns an object of class `hclpalette` which 

1453 allows to draw a number of colors (`n`) uniformly distributed around the 

1454 circle (`[0, 360 * (n - 1) / n]`) controlled via the `h` (Hue) argument. As 

1455 the number of colors is not yet defined, the upper hue limit (`h[1]`, `h2`) 

1456 is defined via lambda function. 

1457 

1458 See also: :py:class:`sequential_hcl`, :py:class:`diverging_hcl`, 

1459 :py:class:`divergingx_hcl`, :py:class:`rainbow_hcl`, :py:class:`heat_hcl`, 

1460 :py:class:`terrain_hcl`, :py:class:`diverging_hsv`, and 

1461 :py:class:`rainbow`. 

1462 

1463 Args: 

1464 h (list, str): Hue values defining the 'color' or name of pre-defined 

1465 palette (`str`) or a list of two numeric values (float/int) defining 

1466 the hue on the two ends of the palette. If str, it acts as the 

1467 input argument `palette`. 

1468 Elements in list can also be lambda functions with one single input 

1469 argument `n` (number of colors; see default value). 

1470 c (int, float): Chroma value (colorfullness), a single numeric value. 

1471 l (int, float): luminance value (lightness), a single numeric value. 

1472 fixup (bool): Only used when converting the HCL colors to hex. Should RGB 

1473 values outside the defined RGB color space be corrected? 

1474 palette (None, str): Can be used to load a default diverging color 

1475 qpalette specification. If the palette does not exist an exception will be 

1476 raised. Else the settings of the palette as defined will be used to create 

1477 qthe color palette. 

1478 rev (bool): Should the color map be reversed? Default `False`. 

1479 **kwargs: Additional named arguments to overwrite the palette settings. 

1480 Allowed: `h1`, `h2`, `c1`, `l1`. 

1481 

1482 Returns: 

1483 qualitative_hcl: Initialize new object. Raises exceptions if the parameters are 

1484 misspecified. Note that the object is callable, the default object call can 

1485 be used to return hex colors (identical to the `.colors()` method), see 

1486 examples. 

1487 

1488 Example: 

1489 

1490 >>> from colorspace import qualitative_hcl 

1491 >>> a = qualitative_hcl() 

1492 >>> a.colors(10) 

1493 >>> #: 

1494 >>> b = qualitative_hcl("Warm") 

1495 >>> b.colors(10) 

1496 >>> #: 

1497 >>> b.swatchplot(show_names = False, figsize = (5.5, 0.5)); 

1498 >>> #: The standard call of the object also returns hex colors 

1499 >>> qualitative_hcl("Warm")(10) 

1500 >>> 

1501 >>> #: Example where `h` is a list of two lambda functions 

1502 >>> from colorspace import hexcols 

1503 >>> pal = qualitative_hcl([lambda n: 100. * (n - 1) / n,  

1504 >>> lambda n, h1: 300. * (n - 1) / n + h1], c = 30) 

1505 >>> cols = hexcols(pal.colors(5)) 

1506 >>> cols 

1507 >>> #: 

1508 >>> cols.to("HCL") 

1509 >>> cols 

1510 

1511 

1512 Raises: 

1513 TypeError: If `h` is neither str nor list of length 2. 

1514 TypeError: If `h` is list of length 2, the elements must be int, float, or 

1515 lambda functions. 

1516 ValueError: If `c` and/or `l` contain unexpected values. 

1517 ValueError: If `h` is str or `palette` is set, but a pre-defined palette 

1518 with this name does not exist. 

1519 """ 

1520 

1521 _allowed_parameters = ["h1", "h2", "c1", "l1"] 

1522 _name = "Qualitative" 

1523 

1524 def __init__(self, h = [0, lambda n: 360. * (n - 1.) / n], c = 80, l = 60, 

1525 fixup = True, palette = None, rev = False, **kwargs): 

1526 

1527 self._set_rev(rev) 

1528 if not isinstance(fixup, bool): raise TypeError("argument `fixup` must be bool") 

1529 if not isinstance(palette, (str, type(None))): 

1530 raise TypeError("argument `palette` must be None or str") 

1531 

1532 # If a str is given on "h": exchange with "palette". 

1533 if isinstance(h, str): 

1534 palette = h 

1535 h = [0, lambda n: 360. * (n - 1.) / n] # Temporary override 

1536 # Else it must be list of length 2 

1537 elif not isinstance(h, list) or not len(h) == 2: 

1538 raise ValueError("argument `h` must be str or list of length 2") 

1539 # Check list elements for allowed types 

1540 else: 

1541 for rec in h: 

1542 if callable(rec): pass 

1543 elif isinstance(rec, (float, int)): pass 

1544 else: 

1545 raise TypeError("unexpected type in list on argument `h`") 

1546 

1547 # _checkinput_ parameters (in the correct order): 

1548 # - dtype, length_min, length_max, recycle, nansallowed, **kwargs 

1549 try: 

1550 c = self._checkinput_(int, 1, 1, False, c = c) 

1551 l = self._checkinput_(int, 1, 1, False, l = l) 

1552 except Exception as e: 

1553 raise ValueError(str(e)) 

1554 

1555 # If user selected a named palette: load palette settings 

1556 if isinstance(palette, str): 

1557 from numpy import where 

1558 pals = hclpalettes().get_palettes("Qualitative") 

1559 idx = where([x.name().upper().replace(" ", "") == palette.upper().replace(" ", "") for x in pals])[0] 

1560 if len(idx) == 0: 

1561 raise ValueError(f"palette {palette} is not a valid qualitative palette. " + \ 

1562 f"Choose one of: {', '.join([x.name() for x in pals])}") 

1563 pal = pals[idx[0]] 

1564 

1565 # Allow to overrule few things 

1566 for key,value in kwargs.items(): 

1567 if key in self._allowed_parameters: pal.set(**{key: value}, lambda_allowed = True) 

1568 

1569 # Extending h2 if h1 = h2 (h2 None) 

1570 if pal.get("h2") == None or pal.get("h1") == pal.get("h2"): 

1571 pal.set(h2 = pal.get("h1") + 360) 

1572 if pal.get("h2") > 360: 

1573 pal.set(h1 = pal.get("h1") - 360) 

1574 pal.set(h2 = pal.get("h2") - 360) 

1575 

1576 # Getting settings 

1577 settings = pal.get_settings() 

1578 else: 

1579 # User settings 

1580 settings = {} 

1581 settings["h1"] = h[0] 

1582 settings["h2"] = h[1] 

1583 settings["c1"] = c[0] # qualitative palette, constant 

1584 settings["l1"] = l[0] # qualitative palette, constant 

1585 settings["fixup"] = fixup 

1586 settings["rev"] = rev 

1587 

1588 # Allow to overule few things 

1589 for key,value in kwargs.items(): 

1590 if key in ["h1", "h2", "c1", "l1"]: settings[key] = value 

1591 

1592 # If keyword arguments are set: 

1593 # overwrite the settings if possible. 

1594 if kwargs: 

1595 for key,val in kwargs.items(): 

1596 if not key in self._allowed_parameters + ["desc", "gui"]: 

1597 raise ValueError(f"argument `{key}` not allowed for {type(self).__name__}") 

1598 settings[key] = val 

1599 

1600 # Save settings 

1601 self.settings = settings 

1602 

1603 

1604 def colors(self, n = 11, fixup = None, alpha = None, **kwargs): 

1605 """Get Colors 

1606 

1607 Returns the colors of the current color palette. 

1608 

1609 Args: 

1610 n (int): Number of colors which should be returned, defaults to 11. 

1611 fixup (None, bool): should sRGB colors be corrected if they lie outside 

1612 the defined color space? If `None` the `fixup` parameter from the 

1613 object will be used. Can be set to `True` or `False` to explicitly 

1614 control the fixup here. 

1615 alpha (None, float, list, or numpy.ndarray): Allows to add an transparency 

1616 (alpha channel) to the colors. Can be a single float, a list, or a 

1617 numpy array. If a list or array is provided it must be of length 1 or 

1618 of length `n` and be convertible to float, providing values 

1619 between `0.0` (full opacity) and `1.0` (full transparency) 

1620 **kwargs: Currently allows for `rev = True` to reverse the colors. 

1621 

1622 Returns: 

1623 list: Returns a list of str with `n` colors from the 

1624 color palette. 

1625 

1626 Examples: 

1627 >>> from colorspace import qualitative_hcl, rainbow_hcl 

1628 >>> qualitative_hcl("Dark 3").colors() 

1629 >>> #: Five colors with constant alpha of 0.3 

1630 >>> qualitative_hcl().colors(5, alpha = 0.3) 

1631 >>> #: Three colors with variying alpha 

1632 >>> qualitative_hcl().colors(3, alpha = [0.2, 0.8, 0.3]) 

1633 >>> 

1634 >>> #: Same for rainbow_hcl which is a special 

1635 >>> # version of the qualitative HCL color palette 

1636 >>> rainbow_hcl().colors(4) 

1637 

1638 """ 

1639 

1640 from numpy import repeat, linspace, asarray 

1641 from numpy import vstack, transpose 

1642 from .colorlib import HCL 

1643 

1644 alpha = self._get_alpha_array(alpha, n) 

1645 fixup = fixup if isinstance(fixup, bool) else self.settings["fixup"] 

1646 

1647 # If either h1 or h2 is a lambda function: evaluate now. 

1648 h1 = self.get("h1")(n) if callable(self.get("h1")) else self.get("h1") 

1649 if callable(self.get("h2")): 

1650 fn = self.get("h2") 

1651 # Distinguish between lambda functions with one argument (n) or two (n + h1) 

1652 h1 = self.get("h1")(n) if callable(self.get("h1")) else self.get("h1") 

1653 h2 = fn(n) if fn.__code__.co_argcount == 1 else fn(n, h1) 

1654 else: 

1655 h2 = self.get("h2") 

1656 

1657 # Calculate the coordinates for our HCL color(s) 

1658 L = repeat(self.get("l1"), n) 

1659 C = repeat(self.get("c1"), n) 

1660 H = linspace(h1, h2, n) 

1661 

1662 # Create new HCL color object 

1663 HCL = HCL(H, C, L, alpha) 

1664 

1665 # Reversing colors 

1666 rev = self._rev 

1667 if "rev" in kwargs.keys(): rev = kwargs["rev"] 

1668 

1669 # Return hex colors 

1670 return HCL.colors(fixup = fixup, rev = rev) 

1671 

1672 

1673# ------------------------------------------------------------------- 

1674# The rainbow class extends the qualitative_hcl class. 

1675# ------------------------------------------------------------------- 

1676class rainbow_hcl(qualitative_hcl): 

1677 """HCL Based Rainbow Palette 

1678 

1679 `rainbow_hcl` computes a rainbow of colors via :py:class:`qualitative_hcl` 

1680 defined by different hues given a single value of each chroma and 

1681 luminance. It corresponds to `rainbow` which computes a rainbow in 

1682 HSV space. 

1683 

1684 See also: :py:class:`qualitative_hcl`, :py:class:`sequential_hcl`, 

1685 :py:class:`diverging_hcl`, :py:class:`divergingx_hcl`, 

1686 :py:class:`heat_hcl`, :py:class:`terrain_hcl`, :py:class:`diverging_hsv`, 

1687 and :py:class:`rainbow`. 

1688 

1689 Args: 

1690 c (float, int): Chroma (colorfullness) of the color map `[0-100+]`. 

1691 l (float, int): Luminance (lightness) of the color map `[0-100]`. 

1692 start (float, int, lambda): Hue at which the rainbow should start or lambda function 

1693 with one argument. Defaults to 0. 

1694 end (float, int, lambda): Hue (int) at which the rainbow should end or lambda function 

1695 with one argument. By default a lambda function evaluated when 

1696 drawing colors (`360 * (n - 1) / n`). 

1697 fixup (bool): Only used when converting the HCL colors to hex. Should 

1698 RGB values outside the defined RGB color space be corrected? 

1699 rev (bool): Should the color map be reversed? Default `False`. 

1700 *args: Currently unused. 

1701 **kwargs: Additional named arguments to overwrite the palette settings. 

1702 Allowed: `h1`, `h2`, `c1`, `l1`, `l2`, `p1`. 

1703 

1704 Returns: 

1705 Initialize new object, no return. Raises a set of errors if the parameters 

1706 are misspecified. Note that the object is callable, the default object call 

1707 can be used to return hex colors (identical to the `.colors()` method), 

1708 see examples. 

1709 

1710 Example: 

1711 

1712 >>> from colorspace import rainbow_hcl 

1713 >>> pal = rainbow_hcl() 

1714 >>> pal.colors(10) 

1715 >>> #: 

1716 >>> pal.swatchplot(show_names = False, figsize = (5.5, 0.5)); 

1717 >>> #: The standard call of the object also returns hex colors. Thus, 

1718 >>> # you can make your code slimmer by calling 

1719 >>> rainbow_hcl()(10) 

1720 >>> 

1721 >>> #: Testing lambda function for both, start and end 

1722 >>> pal = rainbow_hcl(start = lambda n: (n - 1) / n, 

1723 >>> end = lambda n: 360 - (n - 1) / n) 

1724 >>> pal.swatchplot(n = 5, show_names = False, figsize = (5.5, 0.5)) 

1725 >>> #: 

1726 >>> pal.swatchplot(n = 10, show_names = False, figsize = (5.5, 0.5)) 

1727 """ 

1728 

1729 _allowed_parameters = ["h1", "h2", "c1", "l1"] 

1730 _name = "Rainbow HCL" 

1731 

1732 def __init__(self, c = 50, l = 70, start = 0, end = lambda n: 360 * (n - 1) / n, 

1733 fixup = True, rev = False, *args, **kwargs): 

1734 

1735 self._set_rev(rev) 

1736 if not isinstance(fixup, bool): raise TypeError("argument `fixup` must be bool") 

1737 

1738 # _checkinput_ parameters (in the correct order): 

1739 # dtype, length = None, recycle = False, nansallowed = False, **kwargs 

1740 try: 

1741 c = self._checkinput_(int, 1, 1, False, c = c) 

1742 l = self._checkinput_(int, 1, 1, False, l = l) 

1743 except Exception as e: 

1744 raise ValueError(str(e)) 

1745 

1746 # Checking start and end. If int, use _checkinput_, if callable make 

1747 # sure it is a lambda function with one single input argument. 

1748 if isinstance(start, (float, int)): 

1749 start = self._checkinput_(int, 1, False, False, start = start) 

1750 elif callable(start): 

1751 if not start.__code__.co_argcount == 1: 

1752 raise Exception("if `start` is a lambda function it must have only one argument") 

1753 else: 

1754 raise TypeError("argument `start` must be int or lambda function") 

1755 

1756 if isinstance(end, int): 

1757 end = self._checkinput_(int, 1, False, False, end = end) 

1758 elif callable(end): 

1759 if not end.__code__.co_argcount == 1: 

1760 raise Exception("if `end` is a lambda function it must have only one argument") 

1761 else: 

1762 raise TypeError("argument `end` must be int or lambda function") 

1763 

1764 # Save settins 

1765 self.settings = {"h1": start if callable(start) else int(start[0]), 

1766 "h2": end if callable(end) else int(end[0]), 

1767 "c1": int(c[0]), 

1768 "l1": int(l[0]), 

1769 "fixup": bool(fixup)} 

1770 

1771 # If keyword arguments are set: 

1772 # overwrite the settings if possible. 

1773 if kwargs: 

1774 for key,val in kwargs.items(): 

1775 if not key in self._allowed_parameters + ["desc", "gui"]: 

1776 raise ValueError(f"argument `{key}` not allowed for {type(self).__name__}") 

1777 self.settings[key] = val 

1778 

1779 self.settings["rev"] = self._rev 

1780 

1781# ------------------------------------------------------------------- 

1782# ------------------------------------------------------------------- 

1783class diverging_hcl(hclpalette): 

1784 """Diverging HCL Color Palettes 

1785 

1786 The HCL (hue-chroma-luminance) color model is a perceptual color model 

1787 obtained by using polar coordinates in CIELUV space 

1788 (i.e., :py:class:`polarLUV <colorspace.colorlib.polarLUV>`), 

1789 where steps of equal size correspond to approximately equal perceptual 

1790 changes in color. By taking polar coordinates the resulting three 

1791 dimensions capture the three perceptual axes very well: hue is the type of 

1792 color, chroma the colorfulness compared to the corresponding gray, and 

1793 luminance the brightness. This makes it relatively easy to create balanced 

1794 palettes through trajectories in this HCL space. In contrast, in the more 

1795 commonly-used ‘HSV’ (hue-saturation-value) model (a simple transformation 

1796 of ‘RGB’), the three axes are confounded so that luminance changes along 

1797 with the hue leading to very unbalanced palettes. 

1798 

1799 `diverging_hcl` codes the underlying numeric values by a 

1800 triangular luminance sequence with different hues in the left and 

1801 in the right arm of the palette. Thus, it can be seen as a 

1802 combination of two sequential palettes with some restrictions: (a) 

1803 a single hue is used for each arm of the palette, (b) chroma and 

1804 luminance trajectory are balanced between the two arms, (c) the 

1805 neutral central value has zero chroma. To specify such a palette a 

1806 vector of two hues `h` (or equivalently `h1` and `h2`), either a 

1807 single chroma value `c` (or `c1`) or a vector of two chroma values 

1808 `c` (or `c1` and `cmax`), a vector of two luminances `l` (or `l1` 

1809 and `l2`), and power parameter(s) `power` (or `p1` and `p2`) are 

1810 used. For more flexible diverging palettes without the 

1811 restrictrictions above (and consequently more parameters) 

1812 `divergingx_hcl` is available. For backward compatibility, 

1813 `diverge_hcl` is a copy of `diverging_hcl`. 

1814 

1815 If `h` is str it will overwrite the `palette` argument. In this case, 

1816 pre-specified palette settings will be loaded but are allowed to be 

1817 overwritten by the user. At any time the user can overwrite any of the 

1818 settings. By default, `diverging_hcl` returns an object of class 

1819 `hclpalette` identical to the pre-defined `"Blue-Red"` palette. 

1820 

1821 See also: :py:class:`qualitative_hcl`, :py:class:`sequential_hcl`, 

1822 :py:class:`divergingx_hcl`, :py:class:`rainbow_hcl`, :py:class:`heat_hcl`, 

1823 :py:class:`terrain_hcl`, :py:class:`diverging_hsv`, and 

1824 :py:class:`rainbow`. 

1825 

1826 Args: 

1827 h (list, float, int): Hue values (color), diverging color palettes should 

1828 have different hues for both ends of the palette. If only one value is 

1829 present it will be recycled ending up in a diverging color palette with the 

1830 same colors on both ends. If more than two values are provided the first 

1831 two will be used while the rest is ignored. If input `h` is a str 

1832 this argument acts like the `palette` argument (see `palette` input 

1833 parameter). 

1834 c (float, int, list): Chroma value (colorfullness), a single numeric value. If two 

1835 values are provided the first will be taken as `c1`, the second as `cmax`. 

1836 l (float, int, list): luminance values (lightness). The first value is for 

1837 the two ends of the color palette, the second one for the neutral center 

1838 point. If only one value is given this value will be recycled. 

1839 power (float, int, list): Power parameter for non-linear behaviour of the color 

1840 palette. 

1841 fixup (bool): Only used when converting the HCL colors to hex. Should RGB 

1842 values outside the defined RGB color space be corrected? 

1843 palette (str): Can be used to load a default diverging color palette 

1844 specification. If the palette does not exist an exception will be raised. 

1845 Else the settings of the palette as defined will be used to create the 

1846 color palette. 

1847 rev (bool): Should the color map be reversed. 

1848 *args: Currently unused. 

1849 **kwargs: Additional named arguments to overwrite the palette settings. 

1850 Allowed: `h1`, `h2`, `c1`, `cmax`, `l1`, `l2`, `p1`, `p2`. 

1851 

1852 Returns: 

1853 Initialize new object, no return. Raises a set of errors if the parameters 

1854 are misspecified. Note that the object is callable, the default object call 

1855 can be used to return hex colors (identical to the `.colors()` method), 

1856 see examples. 

1857 

1858 Example: 

1859 

1860 >>> from colorspace import diverging_hcl 

1861 >>> a = diverging_hcl() 

1862 >>> a.colors(10) 

1863 >>> #: Different color palette by name 

1864 >>> b = diverging_hcl("Tropic") 

1865 >>> b.colors(10) 

1866 >>> #: 

1867 >>> b.swatchplot(show_names = False, figsize = (5.5, 0.5)); 

1868 >>> #: The standard call of the object also returns hex colors 

1869 >>> diverging_hcl("Tropic")(10) 

1870 """ 

1871 

1872 _allowed_parameters = ["h1", "h2", "c1", "cmax", "l1", "l2", "p1", "p2"] 

1873 _name = "Diverging HCL" 

1874 

1875 def __init__(self, h = [260, 0], c = 80, l = [30, 90], 

1876 power = 1.5, fixup = True, palette = None, rev = False, 

1877 *args, **kwargs): 

1878 

1879 self._set_rev(rev) 

1880 if not isinstance(fixup, bool): raise TypeError("argument `fixup` must be bool") 

1881 if not isinstance(palette, (str, type(None))): 

1882 raise TypeError("argument `palette` must be None or str") 

1883 

1884 if isinstance(h, str): 

1885 palette = h 

1886 h = -999 # Temporarliy setting to dummy value 

1887 if isinstance(power, int) or isinstance(power, float): 

1888 power = [power] 

1889 

1890 # _checkinput_ parameters (in the correct order): 

1891 # - dtype, length_min, length_max, recycle, nansallowed, **kwargs 

1892 try: 

1893 h = self._checkinput_(int, 2, 2, True, False, h = h) 

1894 c = self._checkinput_(int, 1, 2, False, False, c = c) 

1895 l = self._checkinput_(int, 2, 2, True, False, l = l) 

1896 power = self._checkinput_(float, 1, 2, True, False, power = power) 

1897 except Exception as e: 

1898 raise ValueError(str(e)) 

1899 

1900 # If user selected a named palette: load palette settings 

1901 if isinstance(palette, str): 

1902 from numpy import where 

1903 pals = hclpalettes().get_palettes("Diverging") 

1904 idx = where([x.name().upper().replace(" ", "") == \ 

1905 palette.upper().replace(" ", "") for x in pals])[0] 

1906 if len(idx) == 0: 

1907 raise ValueError(f"palette {palette} is not a valid diverging palette. " + \ 

1908 f"Choose one of: {', '.join([x.name() for x in pals])}") 

1909 pal = pals[idx[0]] 

1910 

1911 # Allow to overule few things 

1912 for key,value in kwargs.items(): 

1913 if key in self._allowed_parameters: pal.set(**{key: value}) 

1914 

1915 # Extending h2 if h1 == h2 or h2 is None 

1916 if pal.get("h2") == None or pal.get("h1") == pal.get("h2"): 

1917 pal.set(h2 = pal.get("h1") + 360) 

1918 if pal.get("h2") > 360: 

1919 pal.set(h1 = pal.get("h1") - 360) 

1920 pal.set(h2 = pal.get("h2") - 360) 

1921 

1922 # Getting settings 

1923 settings = pal.get_settings() 

1924 else: 

1925 settings = {} 

1926 

1927 from numpy import ndarray 

1928 

1929 # User settings 

1930 settings["h1"] = h[0] 

1931 settings["h2"] = h[1] 

1932 settings["c1"] = float(c[0]) 

1933 if len(c) == 2: 

1934 settings["cmax"] = float(c[1]) 

1935 settings["l1"] = l[0] 

1936 settings["l2"] = l[1] 

1937 settings["p1"] = float(power[0]) 

1938 if len(power) == 2: 

1939 settings["p2"] = float(power[1]) 

1940 settings["fixup"] = fixup 

1941 settings["rev"] = rev 

1942 

1943 # If keyword arguments are set: 

1944 # overwrite the settings if possible. 

1945 if kwargs: 

1946 for key,val in kwargs.items(): 

1947 if not key in self._allowed_parameters + ["desc", "gui"]: 

1948 raise ValueError(f"argument `{key}` not allowed for {type(self).__name__}") 

1949 settings[key] = val 

1950 

1951 # Save settings 

1952 self.settings = settings 

1953 

1954 

1955 # Return hex colors 

1956 def colors(self, n = 11, fixup = None, alpha = None, **kwargs): 

1957 """Get Colors 

1958 

1959 Returns the colors of the current color palette. 

1960 

1961 Args: 

1962 n (int): Number of colors which should be returned. 

1963 fixup (None, bool): Should sRGB colors be corrected if they lie 

1964 outside the defined color space? If `None` the `fixup` 

1965 parameter from the object will be used. Can be set to `True` or 

1966 `False` to explicitly control the fixup here. 

1967 alpha (None, float, list, or numpy.ndarray): Allows to add an transparency 

1968 (alpha channel) to the colors. Can be a single float, a list, or a 

1969 numpy array. If a list or array is provided it must be of length 1 or 

1970 of length `n` and be convertible to float, providing values 

1971 between `0.0` (full opacity) and `1.0` (full transparency) 

1972 **kwargs: Currently allows for `rev = True` to reverse the colors. 

1973 

1974 Returns: 

1975 list: Returns a list of str with `n` colors from the 

1976 color palette. 

1977 

1978 

1979 Examples: 

1980 >>> from colorspace import diverging_hcl, hexcols 

1981 >>> diverging_hcl().colors() 

1982 >>> #: Different diverging palette 

1983 >>> cols = diverging_hcl("Green-Orange").colors(5) 

1984 >>> cols 

1985 >>> #: 

1986 >>> hexcols(cols) 

1987 >>> #: Five colors with constant alpha of 0.3 

1988 >>> diverging_hcl().colors(5, alpha = 0.3) 

1989 >>> #: Three colors with variying alpha 

1990 >>> diverging_hcl().colors(3, alpha = [0.2, 0.8, 0.3]) 

1991 

1992 """ 

1993 

1994 from numpy import abs, ceil, linspace, power, repeat, arange, fmax, delete 

1995 from numpy import asarray, ndarray, ndenumerate, concatenate, flip 

1996 from numpy import vstack, transpose, repeat 

1997 from .colorlib import HCL 

1998 

1999 alpha = self._get_alpha_array(alpha, n) 

2000 fixup = fixup if isinstance(fixup, bool) else self.settings["fixup"] 

2001 

2002 # Calculate H/C/L 

2003 p1 = self.get("p1") 

2004 p2 = p1 if self.get("p2") is None else self.get("p2") 

2005 c1 = self.get("c1") 

2006 c2 = 0 if self.get("c2") is None else self.get("c2") 

2007 cmax = None if not self.get("cmax") else self.get("cmax") 

2008 l1 = self.get("l1") 

2009 l2 = l1 if self.get("l2") is None else self.get("l2") 

2010 h1 = self.get("h1") 

2011 h2 = h1 if self.get("h2") is None else self.get("h2") 

2012 

2013 # If n == 1 we do as we have 3 colors, but then only return the middle one 

2014 tmp_n = n if n > 1 else 3 

2015 

2016 # Calculate H/C/L 

2017 rval = linspace(1., -1., tmp_n) 

2018 

2019 L = l2 - (l2 - l1) * power(abs(rval), p2) 

2020 H = ndarray(tmp_n, dtype = "float") 

2021 for i,val in ndenumerate(rval): H[i] = h1 if val > 0 else h2 

2022 

2023 # Calculate the trajectory for the chroma dimension 

2024 i = fmax(0, arange(1., -1e-10, -2. / (tmp_n - 1.))) 

2025 C = self._chroma_trajectory(i, p1, c1, c2, cmax) 

2026 C = fmax(0., concatenate((C, flip(C)))) 

2027 

2028 # Non-even number of colors? We need to remove one. 

2029 if tmp_n % 2 == 1: C = delete(C, int(ceil(tmp_n / 2.))) 

2030 

2031 # Create new HCL color object 

2032 HCL = HCL(H, C, L, alpha) 

2033 

2034 # Reversing colors 

2035 rev = self._rev 

2036 if "rev" in kwargs.keys(): rev = kwargs["rev"] 

2037 

2038 # Return hex colors 

2039 cols = HCL.colors(fixup = fixup, rev = rev) 

2040 return [cols[1]] if n == 1 else cols 

2041 

2042 

2043 

2044 

2045# ------------------------------------------------------------------- 

2046# ------------------------------------------------------------------- 

2047class divergingx_hcl(hclpalette): 

2048 """Diverging X HCL Color Palettes 

2049 

2050 More flexible version of the `diverging_hcl` class. A diverging X 

2051 palette basically consists of two multi-hue sequential palettes. 

2052 

2053 The `divergingx_hcl` function simply calls :py:class:`sequential_hcl` twice 

2054 with a prespecified set of hue, chroma, and luminance parameters. This is 

2055 similar to :py:class:`diverging_hcl` but allows for more flexibility: 

2056 :py:class:`diverging_hcl` employs two _single-hue_ sequential palettes, 

2057 always uses zero chroma for the neutral/central color, and restricts the 

2058 chroma/luminance path to be the same in both "arms" of the palette. In 

2059 contrast, `divergingx_hcl` relaxes this to two full _multi-hue_ palettes 

2060 that can thus go through a non-gray neutral color (typically light yellow). 

2061 Consequently, the chroma/luminance paths can be rather unbalanced between 

2062 the two arms. 

2063 

2064 With this additional flexibility various diverging palettes 

2065 suggested by <https://ColorBrewer2.org/> and CARTO 

2066 (<https://carto.com/carto-colors/>), can be emulated along with 

2067 the Zissou 1 palette from 'wesanderson', Cividis from 'viridis', 

2068 and Roma from 'scico'. 

2069 

2070 * Available CARTO palettes: ArmyRose, Earth, Fall, Geyser, TealRose, 

2071 Temps, and Tropic (available in :py:class:`diverging_hcl`). 

2072 * Available ColorBrewer.org palettes: PuOr, RdBu, RdGy, PiYG, PRGn, 

2073 BrBG, RdYlBu, RdYlGn, Spectral. 

2074 

2075 If `h` is str it will overwrite the `palette` argument. In this case, 

2076 pre-specified palette settings will be loaded but are allowed to be 

2077 overwritten by the user. At any time the user can overwrite any of 

2078 the settings. 

2079 

2080 See also: :py:class:`qualitative_hcl`, :py:class:`sequential_hcl`, 

2081 :py:class:`diverging_hcl`, :py:class:`rainbow_hcl`, :py:class:`heat_hcl`, 

2082 :py:class:`terrain_hcl`, :py:class:`diverging_hsv`, and 

2083 :py:class:`rainbow`. 

2084 

2085 Args: 

2086 h (list): Hue values (color), list of three numerics. Divergingx color 

2087 palettes should have different hues for both ends and the center of the 

2088 palette. For this class three values must be provided. If input `h` is 

2089 a str this argument acts like the `palette` argument (see `palette` 

2090 input parameter). 

2091 c (list): Chroma value (colorfullness), list of floats. In case two 

2092 values are provided the firt is taken as `c1` and `c3` while the second 

2093 one is used for `c2` (center value). When three values are provided 

2094 they are used as `c1`, `c2`, and `c3` (see also `cmax`). 

2095 l (list): Luminance values (lightness), list of float/int. In case two 

2096 values are provided the firt is taken as `l1` and `l3` while the second 

2097 one is used for `l2` (center value). When three are provided 

2098 they are used as `l1`, `l2`, and `l3` respectively. 

2099 power (list): Power parameters for non-linear behaviour of the color 

2100 palette, list of floats. 

2101 If two values are provided `power[0]` will be used for `p1` and `p4` 

2102 while `power[1]` is used for `p2` and `p3` (symmetric). A list of length 

2103 four allows to specify `p1`, `p2`, `p3`, and `p4` individually. List 

2104 of length three acts like a list of length two, the last element is ignored. 

2105 cmax (None, float, int, list, numpy.ndarray): Maximum chroma for 

2106 triangular trajectory. Unused if set `Non`. If one value is provided it 

2107 is used for `cmax1`, if two values are provided they are used as 

2108 `cmax1` and `cmax2`, respectively. 

2109 fixup (bool): Only used when converting the HCL colors to hex. Should RGB 

2110 values outside the defined RGB color space be corrected? 

2111 palette (str): Can be used to load a default diverging color palette 

2112 specification. If the palette does not exist an exception will be raised. 

2113 Else the settings of the palette as defined will be used to create the 

2114 color palette. 

2115 rev (bool): Should the color map be reversed. 

2116 *args: Currently unused. 

2117 **kwargs: Additional named arguments to overwrite the palette settings. 

2118 Allowed: `h1`, `h2`, `h3`, `c1`, `cmax1`, `c2`, `cmax2` `c3`, 

2119 `l1`, `l2`, `l3`, `p1`, `p2`, `p3`, `p4`. 

2120 

2121 Returns: 

2122 Initialize new object, no return. Raises a set of errors if the parameters 

2123 are misspecified. Note that the object is callable, the default object call 

2124 can be used to return hex colors (identical to the `.colors()` method), 

2125 see examples. 

2126 

2127 Example: 

2128 

2129 >>> from colorspace import divergingx_hcl 

2130 >>> pal1 = divergingx_hcl() 

2131 >>> pal1.colors(5) 

2132 >>> #: 

2133 >>> pal1.swatchplot(show_names = False, figsize = (5.5, 0.5)); 

2134 >>> 

2135 >>> #: Different color palette by name 

2136 >>> pal2 = divergingx_hcl("ArmyRose") 

2137 >>> pal2.colors(7) 

2138 >>> #: 

2139 >>> pal2.swatchplot(show_names = False, figsize = (5.5, 0.5)); 

2140 >>> 

2141 >>> #: The standard call of the object also returns hex colors 

2142 >>> divergingx_hcl("Fall")(3) 

2143 >>> 

2144 >>> #: Manual palette with user settings. The following diverginx 

2145 >>> # palette goes from h = 180 (left) to h = 100 (center) and h = 20 (right). 

2146 >>> # Croma is c = 30 (left), c = 5 (center), and c = 30 (right). 

2147 >>> # In addition, both 'arms' have a maximum chroma of cmax = 70 

2148 >>> # in the center of each of the two arms. 

2149 >>> pal3 = divergingx_hcl(h = [180, 100, 20],  

2150 >>> c = [30, 5, 30], 

2151 >>> cmax = [70, 70])  

2152 >>> pal3.specplot(); 

2153 >>> #: Drawing 5 colors from the custom palette. 

2154 >>> pal3(3) 

2155 >>> 

2156 >>> #: Available default palettes (divergingx_hcl palettes) 

2157 >>> from colorspace import divergingx_hcl, swatchplot, palette 

2158 >>> 

2159 >>> carto = ["ArmyRose", "Earth", "Fall", 

2160 >>> "Geyser", "TealRose", "Temps"]  

2161 >>> brewer = ["PuOr", "RdBu", "RdGy", "PiYG", "PRGn", 

2162 >>> "BrBG", "RdYlBu", "RdYlGn", "Spectral"] 

2163 >>> others = ["Zissou 1", "Cividis", "Roma"] 

2164 >>> 

2165 >>> # Create named palettes for swatchplot 

2166 >>> col_carto = [palette(divergingx_hcl(x)(11), name = x) for x in carto] 

2167 >>> col_brewer = [palette(divergingx_hcl(x)(11), name = x) for x in carto] 

2168 >>> col_others = [palette(divergingx_hcl(x)(11), name = x) for x in others] 

2169 >>> 

2170 >>> # Visualize available divergingx palettes 

2171 >>> swatchplot({"Carto": col_carto, 

2172 >>> "Brewer": col_brewer, 

2173 >>> "Others": col_others}, 

2174 >>> figsize = (5.5, 6)); 

2175 >>> 

2176 >>> #: Checking settings of a specific palette 

2177 >>> pal4 = divergingx_hcl("PRGn") 

2178 >>> pal4.show_settings() 

2179 

2180 Raises: 

2181 TypeError: If `fixup` is not bool. 

2182 TypeError: If `palette` is not `None` or str. 

2183 TypeError: If `cmax` not `Non`, float, int, list, numpy.ndarray. 

2184 ValueError: If `cmax` is list of length `<1` or `>2`. 

2185 ValueError: If `h`, `c`, `l`, `power`, `cmax` contain unexpected types or values. 

2186 ValueError: If `palette` is string, but palette with this name cannot be found. 

2187 Exception: If `h3` is not specified. 

2188 ValueError: If `**kwargs` are provides which are not among the allowed ones. 

2189 """ 

2190 

2191 _allowed_parameters = ["h1", "h2", "h3", 

2192 "c1", "cmax1", "c2", "cmax2", "c3", 

2193 "l1", "l2", "l3", "p1", "p2", "p3", "p4"] 

2194 _name = "DivergingX HCL" 

2195 

2196 def __init__(self, h = [192, 77, 21], c = [40, 35, 100], l = [50, 95, 50], \ 

2197 power = [1.0, 1.0, 1.2, 1.0], cmax = 20, \ 

2198 fixup = True, palette = None, rev = False, *args, **kwargs): 

2199 

2200 import numpy as np 

2201 

2202 self._set_rev(rev) 

2203 if not isinstance(fixup, bool): raise TypeError("argument `fixup` must be bool") 

2204 if not isinstance(palette, (str, type(None))): 

2205 raise TypeError("argument `palette` must be None or str") 

2206 

2207 if isinstance(h, str): 

2208 palette = h 

2209 h = [-999.] * 3 # Temporarliy setting to dummy value 

2210 if isinstance(power, int) or isinstance(power, float): 

2211 power = [power] 

2212 

2213 # Handling cmax 

2214 if not isinstance(cmax, (type(None), float, int, list, np.ndarray)): 

2215 raise TypeError("argument `cmax` must be None, float, int, or list") 

2216 elif isinstance(cmax, (list, np.ndarray)) and (len(cmax) < 1 or len(cmax) > 2): 

2217 raise ValueError("if `cmax` is a list or numpy.ndarray it must be of lengt 1 or 2") 

2218 # If cmax is a numpy array: Reduce to list 

2219 if isinstance(cmax, np.ndarray): cmax = list(cmax) 

2220 

2221 if isinstance(cmax, list) and len(cmax) == 1: 

2222 cmax = cmax + [None] 

2223 elif cmax is None: 

2224 cmax = [None, None] # Both set to None 

2225 elif isinstance(cmax, (float, int)): 

2226 cmax = [cmax, None] # Use 'cmax' as 'cmax1' 

2227 

2228 # _checkinput_ parameters (in the correct order): 

2229 # dtype, length = None, recycle = False, nansallowed = False, **kwargs 

2230 try: 

2231 h = self._checkinput_(int, 3, 3, False, False, h = h) 

2232 c = self._checkinput_(int, 2, 3, False, False, c = c) 

2233 l = self._checkinput_(int, 2, 3, False, False, l = l) 

2234 power = self._checkinput_(float, 2, 4, False, False, power = power) 

2235 cmax = self._checkinput_(float, 2, 2, False, True, cmax = cmax) 

2236 except Exception as e: 

2237 raise ValueError(str(e)) 

2238 

2239 if len(c) == 2: c = [c[0], c[1], c[0]] 

2240 if len(l) == 2: l = [l[0], l[1], l[0]] 

2241 if len(power) < 4: power = [power[0], power[1], power[1], power[0]] 

2242 

2243 # If user selected a named palette: load palette settings 

2244 if isinstance(palette, str): 

2245 from .hcl_palettes import divergingx_palettes 

2246 from numpy import where 

2247 pals = divergingx_palettes().get_palettes("Divergingx") 

2248 idx = where([x.name().upper().replace(" ", "") == palette.upper().replace(" ", "") for x in pals])[0] 

2249 if len(idx) == 0: 

2250 raise ValueError(f"palette {palette} is not a valid divergingx palette. " + \ 

2251 f"Choose one of: {', '.join([x.name() for x in pals])}") 

2252 pal = pals[idx[0]] 

2253 del pals, idx 

2254 

2255 # Allow to overule few things 

2256 for key,value in kwargs.items(): 

2257 if key in self._allowed_parameters: 

2258 pal.set(**{key: value}) 

2259 

2260 # Extending h2 if h1 = h2 (h2 None) 

2261 def isNone(x): return isinstance(x, type(None)) 

2262 if isNone(pal.get("p1")): pal.set(p1 = 1.0) 

2263 # Second coordinate 

2264 if isNone(pal.get("h2")): pal.set(h2 = pal.get("h1")) 

2265 if isNone(pal.get("c2")): pal.set(c2 = 0.0) 

2266 if isNone(pal.get("l2")): pal.set(l2 = pal.get("l1")) 

2267 if isNone(pal.get("p2")): pal.set(p2 = pal.get("p1")) 

2268 ## third coordinate 

2269 if isNone(pal.get("h3")): 

2270 raise Exception("third hue coordinate (h3) must be specified") 

2271 if isNone(pal.get("c3")): pal.set(c3 = pal.get("c1")) 

2272 if isNone(pal.get("l3")): pal.set(l3 = pal.get("l1")) 

2273 if isNone(pal.get("p3")): pal.set(p3 = pal.get("p1")) 

2274 if isNone(pal.get("p4")): pal.set(p4 = pal.get("p2")) 

2275 

2276 # Getting settings 

2277 settings = pal.get_settings() 

2278 else: 

2279 settings = {} 

2280 

2281 from numpy import ndarray 

2282 

2283 # User settings 

2284 for i in range(3): settings["h{:d}".format(i + 1)] = h[i] 

2285 for i in range(3): settings["c{:d}".format(i + 1)] = c[i] 

2286 for i in range(3): settings["l{:d}".format(i + 1)] = l[i] 

2287 for i in range(4): settings["p{:d}".format(i + 1)] = power[i] 

2288 for i in range(2): settings["cmax{:d}".format(i + 1)] = cmax[i] 

2289 settings["fixup"] = fixup 

2290 settings["rev"] = rev 

2291 

2292 # If keyword arguments are set: 

2293 # overwrite the settings if possible. 

2294 if kwargs: 

2295 for key,val in kwargs.items(): 

2296 if not key in self._allowed_parameters + ["desc", "gui"]: 

2297 raise ValueError(f"argument `{key}` not allowed for {type(self).__name__}") 

2298 settings[key] = val 

2299 

2300 # Save settings 

2301 self.settings = settings 

2302 

2303 

2304 # Return hex colors 

2305 def colors(self, n = 11, fixup = None, alpha = None, **kwargs): 

2306 """Get Colors 

2307 

2308 Returns the colors of the current color palette. 

2309 

2310 Args: 

2311 n (int): Number of colors which should be returned. 

2312 fixup (None, bool): Should sRGB colors be corrected if they lie 

2313 outside the defined color space? If `None` the `fixup` 

2314 parameter from the object will be used. Can be set to `True` or 

2315 `False` to explicitly control the fixup here. 

2316 alpha (None, float, list, or numpy.ndarray): Allows to add an transparency 

2317 (alpha channel) to the colors. Can be a single float, a list, or a 

2318 numpy array. If a list or array is provided it must be of length 1 or 

2319 of length `n` and be convertible to float, providing values 

2320 between `0.0` (full opacity) and `1.0` (full transparency) 

2321 **kwargs: Currently allows for `rev = True` to reverse the colors. 

2322 

2323 

2324 Returns: 

2325 list: Returns a list of str with `n` colors from the 

2326 color palette. 

2327 

2328 """ 

2329 

2330 import numpy as np 

2331 from .colorlib import HCL 

2332 

2333 alpha = self._get_alpha_array(alpha, n) 

2334 fixup = fixup if isinstance(fixup, bool) else self.settings["fixup"] 

2335 

2336 # If n == 1 we do as we have 3 colors, but then only return the middle one 

2337 tmp_n = n if n > 1 else 3 

2338 

2339 # Calculate where to evaluate the trajectories 

2340 # n2 is half the number of colors (n on either side of the palette) 

2341 n2 = int(np.ceil(tmp_n / 2)) 

2342 rval = np.linspace(1., 0., n2) if n % 2 == 1 else np.linspace(1., 1. / (2 * n2 - 1), n2) 

2343 

2344 # Calculate H/C/L coordinates for both sides (called 'a' and 'b' not to get 

2345 # confused with the numbering of the parameters). 

2346 Ha, Ca, La = self._get_seqhcl(rval, ha = self.get("h1"), hb = self.get("h2"), 

2347 ca = self.get("c1"), cb = self.get("c2"), 

2348 la = self.get("l1"), lb = self.get("l2"), 

2349 pa = self.get("p1"), pb = self.get("p2"), 

2350 cmax = self.get("cmax1")) 

2351 Hb, Cb, Lb = self._get_seqhcl(rval, ha = self.get("h3"), hb = self.get("h2"), 

2352 ca = self.get("c3"), cb = self.get("c2"), 

2353 la = self.get("l3"), lb = self.get("l2"), 

2354 pa = self.get("p3"), pb = self.get("p4"), 

2355 cmax = self.get("cmax2")) 

2356 

2357 # In case the user requested an odd number of colors we need to 

2358 # cut away one of one of the two sides (remove it from 'b'). 

2359 if tmp_n % 2 == 1: 

2360 Hb = Hb[:-1] 

2361 Cb = Cb[:-1] 

2362 Lb = Lb[:-1] 

2363 

2364 # Concatenate the two sides 

2365 H = np.concatenate((Ha, Hb[::-1])) 

2366 C = np.concatenate((Ca, Cb[::-1])) 

2367 L = np.concatenate((La, Lb[::-1])) 

2368 

2369 # Create new HCL color object 

2370 HCL = HCL(H, C, L, alpha) 

2371 

2372 # Reversing colors 

2373 rev = self._rev 

2374 if "rev" in kwargs.keys(): rev = kwargs["rev"] 

2375 

2376 # Return hex colors 

2377 cols = HCL.colors(fixup = fixup, rev = rev) 

2378 return [cols[1]] if n == 1 else cols 

2379 

2380 

2381 

2382 

2383# ------------------------------------------------------------------- 

2384# ------------------------------------------------------------------- 

2385class sequential_hcl(hclpalette): 

2386 """Sequential HCL Color Palettes 

2387 

2388 The HCL (hue-chroma-luminance) color model is a perceptual color model 

2389 obtained by using polar coordinates in CIELUV space 

2390 (i.e., :py:class:`polarLUV <colorspace.colorlib.polarLUV>`), 

2391 where steps of equal size correspond to approximately equal perceptual 

2392 changes in color. By taking polar coordinates the resulting three 

2393 dimensions capture the three perceptual axes very well: hue is the type of 

2394 color, chroma the colorfulness compared to the corresponding gray, and 

2395 luminance the brightness. This makes it relatively easy to create balanced 

2396 palettes through trajectories in this HCL space. In contrast, in the more 

2397 commonly-used ‘HSV’ (hue-saturation-value) model (a simple transformation 

2398 of ‘RGB’), the three axes are confounded so that luminance changes along 

2399 with the hue leading to very unbalanced palettes. 

2400 

2401 `qualitative_hcl` distinguishes the underlying categories by a sequence of 

2402 hues while keeping both chroma and luminance constant to give each color in 

2403 the resulting palette the same perceptual weight. Thus, `h` should be a 

2404 pair of hues (or equivalently `h1` and `h2` can be used) with the starting 

2405 and ending hue of the palette. Then, an equidistant sequence between these 

2406 hues is employed, by default spanning the full color wheel (i.e, the full 

2407 360 degrees). Chroma `c` (or equivalently `c1`) and luminance `l` (or 

2408 equivalently `l1`) are constants. If `h` is str it will overwrite the 

2409 `palette` argument. In this case, pre-specified palette settings will be 

2410 loaded but are allowed to be overwritten by the user. At any time the user 

2411 can overwrite any of the settings. 

2412 

2413 By default, `sequential_hcl` returns an object of class `hclpalette` 

2414 identical to the pre-defined `"Blues 2"` palette. 

2415 

2416 `h1` and `h2` both allow for lambda functions to create uniformly distributed 

2417 hues around the (full) circle (360 degrees). 

2418 

2419 * `h1`: can be a lambda function with one single argument `n` (number of colors). 

2420 * `h2`: can be a lambda function with one or two arguments. If only one, `n` 

2421 (number of colors) will be handed over when evaluated. If two, the first 

2422 one is expected to be `n` (number of colors), as second argument the 

2423 value `h1` will be used. 

2424 

2425 See also: :py:class:`qualitative_hcl`, :py:class:`diverging_hcl`, 

2426 :py:class:`divergingx_hcl`, :py:class:`rainbow_hcl`, :py:class:`heat_hcl`, 

2427 :py:class:`terrain_hcl`, :py:class:`diverging_hsv`, and 

2428 :py:class:`rainbow`. 

2429 

2430 Args: 

2431 h (float, int, list, str): Hue values (color). If only one value is given the value 

2432 is recycled which yields a single-hue sequential color palette. If 

2433 input `h` is a str this argument acts like the `palette` argument 

2434 (see `palette` input parameter). 

2435 c (float, int, list): Chroma values (colorfullness), int or float 

2436 (linear to zero), list of two numerics (linear in interval), = 

2437 or three numerics (advanced; `[c1, cmax, c2]`). 

2438 l (float, int, list): Luminance values (luminance). If float or int, 

2439 the element will be recycled, or a list of two numerics 

2440 (defining `[l1, l2]`). 

2441 power (float, int, list): Power parameter for non-linear behaviour 

2442 of the color palette. Single float or int, or a list of numerics 

2443 (defining `[p1, p2]`). 

2444 fixup (bool): Only used when converting the HCL colors to hex. Should 

2445 RGB values outside the defined RGB color space be corrected? 

2446 palette (str): Can be used to load a default diverging color palette 

2447 specification. If the palette does not exist an exception will be 

2448 raised. Else the settings of the palette as defined will be used to 

2449 create the color palette. 

2450 rev (bool): Should the color map be reversed. 

2451 *args: Currently unused. 

2452 **kwargs: Additional named arguments to overwrite the palette settings. 

2453 Allowed: `h1`, `h2`, `c1`, `c2`, `cmax`, `l1`, `l2`, `p1`, `p2`. 

2454 

2455 Returns: 

2456 Initialize new object, no return. Raises a set of errors if the parameters 

2457 are misspecified. Note that the object is callable, the default object call 

2458 can be used to return hex colors (identical to the `.colors()` method), 

2459 see examples. 

2460 

2461 Example: 

2462 

2463 >>> from colorspace import sequential_hcl 

2464 >>> a = sequential_hcl() 

2465 >>> a.colors(10) 

2466 >>> #: Different color palette by name 

2467 >>> b = sequential_hcl("Peach") 

2468 >>> b.colors(10) 

2469 >>> #: 

2470 >>> b.swatchplot(show_names = False, figsize = (5.5, 0.5)); 

2471 >>> #: The standard call of the object also returns hex colors 

2472 >>> sequential_hcl("Peach")(10) 

2473 """ 

2474 

2475 # Allowed to overwrite via **kwargs 

2476 _allowed_parameters = ["h1", "h2", "c1", "c2", "cmax", "l1", "l2", "p1", "p2"] 

2477 _name = "Sequential HCL" 

2478 

2479 def __init__(self, h = 260, c = 80, l = [30, 90], 

2480 power = 1.5, fixup = True, palette = None, rev = False, 

2481 *args, **kwargs): 

2482 

2483 self._set_rev(rev) 

2484 if not isinstance(fixup, bool): raise TypeError("argument `fixup` must be bool") 

2485 if not isinstance(palette, (str, type(None))): 

2486 raise TypeError("argument `palette` must be None or str") 

2487 

2488 # If input "h" is a str: exchange with "palette" 

2489 if isinstance(h, str): 

2490 palette = h 

2491 h = -999 # Temporarliy setting to dummy value 

2492 

2493 # _checkinput_ parameters (in the correct order): 

2494 # dtype, length_min = None, length_max = None, 

2495 # recycle = False, nansallowed = False, **kwargs 

2496 try: 

2497 h = self._checkinput_(int, 1, 2, True, False, h = h) 

2498 c = self._checkinput_(int, 1, 3, True, False, c = c) 

2499 l = self._checkinput_(int, 2, 2, True, False, l = l) 

2500 power = self._checkinput_(float, 2, 2, True, False, power = power) 

2501 except Exception as e: 

2502 raise ValueError(str(e)) 

2503 

2504 # If user selected a named palette: load palette settings 

2505 if isinstance(palette, str): 

2506 from numpy import where 

2507 pals = hclpalettes().get_palettes("Sequential") 

2508 idx = where([x.name().upper().replace(" ", "") == palette.upper().replace(" ", "") for x in pals])[0] 

2509 if len(idx) == 0: 

2510 raise ValueError(f"palette {palette} is not a valid sequential palette. " + \ 

2511 f"Choose one of: {', '.join([x.name() for x in pals])}") 

2512 pal = pals[idx[0]] 

2513 

2514 def isNone(x): return isinstance(x, type(None)) 

2515 

2516 if isNone(pal.get("h2")): pal.set(h2 = pal.get("h1")) 

2517 

2518 # Allow to overule few things 

2519 for key,value in kwargs.items(): 

2520 if key in self._allowed_parameters: pal.set(**{key: value}) 

2521 

2522 # Getting settings 

2523 settings = pal.get_settings() 

2524 else: 

2525 # User settings 

2526 settings = {} 

2527 settings["h1"] = h[0] 

2528 settings["h2"] = None if len(h) == 1 else h[1] 

2529 if len(c) == 3: 

2530 settings["c1"] = c[0] 

2531 settings["cmax"] = c[1] 

2532 settings["c2"] = c[2] 

2533 elif len(c) == 2: 

2534 settings["c1"] = c[0] 

2535 settings["c2"] = c[1] 

2536 else: 

2537 settings["c1"] = c[0] 

2538 settings["c2"] = 0 

2539 settings["l1"] = l[0] 

2540 settings["l2"] = l[1] 

2541 settings["p1"] = power[0] 

2542 settings["p2"] = power[1] 

2543 settings["fixup"] = fixup 

2544 settings["rev"] = rev 

2545 

2546 # If keyword arguments are set: 

2547 # overwrite the settings if possible. 

2548 if kwargs: 

2549 for key,val in kwargs.items(): 

2550 if not key in self._allowed_parameters + ["desc", "gui"]: 

2551 raise ValueError(f"argument `{key}` not allowed for {type(self).__name__}") 

2552 settings[key] = val 

2553 

2554 if isinstance(settings["h2"], type(None)): settings["h2"] = settings["h1"] 

2555 

2556 # Save settings 

2557 self.settings = settings 

2558 

2559 

2560 # Return hex colors 

2561 def colors(self, n = 11, fixup = None, alpha = None, **kwargs): 

2562 """Get Colors 

2563 

2564 Returns the colors of the current color palette. 

2565 

2566 Args: 

2567 n (int): Number of colors which should be returned. 

2568 fixup (None, bool): Should sRGB colors be corrected if they lie 

2569 outside the defined color space? If `None` the `fixup` 

2570 parameter from the object will be used. Can be set to `True` or 

2571 `False` to explicitly control the fixup here. 

2572 alpha (None, float, list, or numpy.ndarray): Allows to add an transparency 

2573 (alpha channel) to the colors. Can be a single float, a list, or a 

2574 numpy array. If a list or array is provided it must be of length 1 or 

2575 of length `n` and be convertible to float, providing values 

2576 between `0.0` (full opacity) and `1.0` (full transparency) 

2577 **kwargs: Currently allows for `rev = True` to reverse the colors. 

2578 

2579 Returns: 

2580 list: Returns a list of str with `n` colors from the 

2581 color palette. 

2582 

2583 Examples: 

2584 >>> from colorspace import sequential_hcl, hexcols 

2585 >>> sequential_hcl().colors() 

2586 >>> #: Different sequential palette 

2587 >>> cols = sequential_hcl("Rocket").colors(5) 

2588 >>> cols 

2589 >>> #: 

2590 >>> hexcols(cols) 

2591 >>> #: Five colors with constant alpha of 0.3 

2592 >>> sequential_hcl().colors(5, alpha = 0.3) 

2593 >>> #: Three colors with variying alpha 

2594 >>> sequential_hcl().colors(3, alpha = [0.2, 0.8, 0.3]) 

2595 

2596 """ 

2597 

2598 from numpy import linspace 

2599 from .colorlib import HCL 

2600 

2601 alpha = self._get_alpha_array(alpha, n) 

2602 fixup = fixup if isinstance(fixup, bool) else self.settings["fixup"] 

2603 

2604 # Calculate H/C/L 

2605 p1 = self.get("p1") 

2606 p2 = p1 if self.get("p2") is None else self.get("p2") 

2607 c1 = self.get("c1") 

2608 c2 = 0 if self.get("c2") is None else self.get("c2") 

2609 cmax = None if not self.get("cmax") else self.get("cmax") 

2610 l1 = self.get("l1") 

2611 l2 = l1 if self.get("l2") is None else self.get("l2") 

2612 h1 = self.get("h1") 

2613 h2 = h1 if self.get("h2") is None else self.get("h2") 

2614 

2615 # Get colors and create new HCL color object 

2616 [H, C, L] = self._get_seqhcl(linspace(1., 0., n), h1, h2, c1, c2, l1, l2, p1, p2, cmax) 

2617 HCL = HCL(H, C, L, alpha) 

2618 

2619 # Reversing colors 

2620 rev = self._rev 

2621 if "rev" in kwargs.keys(): rev = kwargs["rev"] 

2622 

2623 # Return hex colors 

2624 return HCL.colors(fixup = fixup, rev = rev) 

2625 

2626 

2627# ------------------------------------------------------------------- 

2628# The rainbow class extends the qualitative_hcl class. 

2629# ------------------------------------------------------------------- 

2630class heat_hcl(sequential_hcl): 

2631 """HCL Based Heat Color Palette 

2632 

2633 `heat_hcl` is an implementation of the base _R_ 'heat.colors' palette but 

2634 constructed in HCL space based on a call to :py:class:`sequential_hcl`.  

2635 

2636 See also: :py:class:`qualitative_hcl`, :py:class:`sequential_hcl`, 

2637 :py:class:`diverging_hcl`, :py:class:`divergingx_hcl`, 

2638 :py:class:`rainbow_hcl`, :py:class:`terrain_hcl`, 

2639 :py:class:`diverging_hsv`, and :py:class:`rainbow`. 

2640 

2641 Args: 

2642 h (list of int): Hue parameters (h1/h2). 

2643 c (list of int): Chroma parameters (c1/c2). 

2644 l (int): Luminance parameters (l1/l2). 

2645 power (list of float): Power parameters (p1/p2). 

2646 fixup (bool): Only used when converting the HCL colors to hex. Should 

2647 RGB values outside the defined RGB color space be corrected? 

2648 rev (bool): Should the color map be reversed. 

2649 *args: Currently unused. 

2650 **kwargs: Additional arguments to overwrite the h/c/l settings. 

2651 Allowed: `h1`, `h2`, `c1`, `c2`, `l1`, `l2`, `p1`, `p2`. 

2652 

2653 Returns: 

2654 Initialize new object, no return. Raises a set of errors if the parameters 

2655 are misspecified. Note that the object is callable, the default object call 

2656 can be used to return hex colors (identical to the `.colors()` method), 

2657 see examples. 

2658 

2659 Example: 

2660 

2661 >>> from colorspace.palettes import heat_hcl 

2662 >>> pal = heat_hcl() 

2663 >>> pal.colors(3) 

2664 >>> #: 

2665 >>> pal.swatchplot(show_names = False, figsize = (5.5, 0.5)); 

2666 >>> #: The standard call of the object also returns hex colors 

2667 >>> heat_hcl()(10) 

2668 """ 

2669 

2670 

2671 _allowed_parameters = ["h1", "h2", "c1", "c2", "l1", "l2", "p1", "p2"] 

2672 _name = "Heat HCL" 

2673 

2674 def __init__(self, h = [0, 90], c = [100, 30], l = [50, 90], power = [1./5., 1.], 

2675 fixup = True, rev = False, *args, **kwargs): 

2676 

2677 self._set_rev(rev) 

2678 if not isinstance(fixup, bool): raise TypeError("argument `fixup` must be bool") 

2679 

2680 # _checkinput_ parameters (in the correct order): 

2681 # - dtype, length_min, length_max, recycle, nansallowed, **kwargs 

2682 try: 

2683 h = self._checkinput_(int, 2, 2, False, h = h) 

2684 c = self._checkinput_(int, 2, 2, False, c = c) 

2685 l = self._checkinput_(int, 2, 2, False, l = l) 

2686 power = self._checkinput_(float, 2, 2, False, power = power) 

2687 except Exception as e: 

2688 raise ValueError(str(e)) 

2689 

2690 # Save settins 

2691 self.settings = {"h1": int(h[0]), "h2": int(h[1]), 

2692 "c1": int(c[0]), "c2": int(c[1]), 

2693 "l1": int(l[0]), "l2": int(l[1]), 

2694 "p1": power[0], "p2": power[1], 

2695 "fixup": bool(fixup)} 

2696 

2697 # If keyword arguments are set: 

2698 # overwrite the settings if possible. 

2699 if kwargs: 

2700 for key,val in kwargs.items(): 

2701 if not key in self._allowed_parameters + ["desc", "gui"]: 

2702 raise ValueError(f"argument `{key}` not allowed for {type(self).__name__}") 

2703 self.settings[key] = val 

2704 

2705 self.settings["rev"] = self._rev 

2706 

2707 

2708# ------------------------------------------------------------------- 

2709# The rainbow class extends the qualitative_hcl class. 

2710# ------------------------------------------------------------------- 

2711class terrain_hcl(sequential_hcl): 

2712 """HCL Based Terrain Color Palette 

2713 

2714 `terrain_hcl` is an implementation of the base _R_ 'terrain.colors' palette but 

2715 constructed in HCL space based on a call to :py:class:`sequential_hcl`.  

2716 

2717 See also: :py:class:`qualitative_hcl`, :py:class:`sequential_hcl`, 

2718 :py:class:`diverging_hcl`, :py:class:`divergingx_hcl`, 

2719 :py:class:`rainbow_hcl`, :py:class:`heat_hcl`, :py:class:`diverging_hsv`, 

2720 and :py:class:`rainbow`. 

2721 

2722 Args: 

2723 h (list of int): Hue parameters (h1/h2). 

2724 c (list of int): Chroma parameters (c1/c2). 

2725 l (int): Luminance parameters (l1/l2). 

2726 power (list of float): Power parameters (p1/p2). 

2727 fixup (bool): Only used when converting the HCL colors to hex. Should 

2728 RGB values outside the defined RGB color space be corrected? 

2729 rev (bool): Should the color map be reversed. 

2730 *args: unused. 

2731 **kwargs: Additional arguments to overwrite the h/c/l settings. 

2732 Allowed: `h1`, `h2`, `c1`, `c2`, `l1`, `l2`, `p1`, `p2`. 

2733 

2734 Returns: 

2735 Initialize new object, no return. Raises a set of errors if the parameters 

2736 are misspecified. Note that the object is callable, the default object call 

2737 can be used to return hex colors (identical to the `.colors()` method), 

2738 see examples. 

2739 

2740 Example: 

2741 

2742 >>> from colorspace import terrain_hcl 

2743 >>> pal = terrain_hcl() 

2744 >>> pal.colors(10) 

2745 >>> #: 

2746 >>> pal.swatchplot(show_names = False, figsize = (5.5, 0.5)); 

2747 >>> #: The standard call of the object also returns hex colors 

2748 >>> terrain_hcl()(10) 

2749 """ 

2750 

2751 _allowed_parameters = ["h1", "h2", "c1", "c2", "l1", "l2", "p1", "p2"] 

2752 _name = "Terrain HCL" 

2753 

2754 def __init__(self, h = [130, 0], c = [80, 0], l = [60, 95], power = [1./10., 1.], 

2755 fixup = True, rev = False, *args, **kwargs): 

2756 

2757 self._set_rev(rev) 

2758 if not isinstance(fixup, bool): raise TypeError("argument `fixup` must be bool") 

2759 

2760 # _checkinput_ parameters (in the correct order): 

2761 # dtype, length = None, recycle = False, nansallowed = False, **kwargs 

2762 try: 

2763 h = self._checkinput_(int, 2, 2, False, h = h) 

2764 c = self._checkinput_(int, 2, 2, False, c = c) 

2765 l = self._checkinput_(int, 2, 2, False, l = l) 

2766 power = self._checkinput_(float, 2, 2, False, power = power) 

2767 except Exception as e: 

2768 raise ValueError(str(e)) 

2769 

2770 # Save settins 

2771 self.settings = {"h1": int(h[0]), "h2": int(h[1]), 

2772 "c1": int(c[0]), "c2": int(c[1]), 

2773 "l1": int(l[0]), "l2": int(l[1]), 

2774 "p1": power[0], "p2": power[1], 

2775 "fixup": bool(fixup)} 

2776 

2777 # If keyword arguments are set: 

2778 # overwrite the settings if possible. 

2779 if kwargs: 

2780 for key,val in kwargs.items(): 

2781 if not key in self._allowed_parameters + ["desc", "gui"]: 

2782 raise ValueError(f"argument `{key}` not allowed for {type(self).__name__}") 

2783 self.settings[key] = val 

2784 

2785 self.settings["rev"] = self._rev 

2786 

2787 

2788class diverging_hsv(hclpalette): 

2789 """Diverging HSV Color Palettes 

2790 

2791 `diverging_hsv` provides an HSV-based version of :py:class:`diverging_hcl`. 

2792 Its purpose is mainly didactic to show that HSV-based diverging palettes 

2793 are less appealing, more difficult to read and more flashy than HCL-based 

2794 diverging palettes. 

2795 

2796 See also: :py:class:`qualitative_hcl`, :py:class:`sequential_hcl`, 

2797 :py:class:`diverging_hcl`, :py:class:`divergingx_hcl`, 

2798 :py:class:`rainbow_hcl`, :py:class:`heat_hcl`, :py:class:`terrain_hcl`, and 

2799 :py:class:`rainbow`. 

2800 

2801 Args: 

2802 h (list of numerics): Hue values, diverging color palettes should have 

2803 different hues for both ends of the palette. If only one value is present 

2804 it will be recycled ending up in a diverging color palette with the same 

2805 colors on both ends. If more than two values are provided the first two 

2806 will be used while the rest is ignored. If input `h` is a str this 

2807 argument acts like the `palette` argument (see `palette` input 

2808 parameter). 

2809 s (float, int): Saturation value for the two ends of the palette. 

2810 v (float, int): Value (the HSV value) of the colors. 

2811 power (numeric): Power parameter for non-linear behaviour of the color 

2812 palette. 

2813 fixup (bool): Only used when converting the HCL colors to hex. Should 

2814 RGB values outside the defined RGB color space be corrected? 

2815 rev (bool): Should the color map be reversed. 

2816 *args: Unused. 

2817 **kwargs: Additional arguments to overwrite the h/c/l settings. 

2818 Allowed: `h1`, `h2`, `s`, `v`. 

2819 

2820 Returns: 

2821 Initialize new object, no return. Raises a set of errors if the parameters 

2822 are misspecified. Note that the object is callable, the default object call 

2823 can be used to return hex colors (identical to the `.colors()` method), 

2824 see examples. 

2825 

2826 Example: 

2827 

2828 >>> from colorspace import diverging_hsv 

2829 >>> pal = diverging_hsv() 

2830 >>> pal.colors(10) 

2831 >>> #: 

2832 >>> pal.swatchplot(show_names = False, figsize = (5.5, 0.5)); 

2833 >>> #: The standard call of the object also returns hex colors 

2834 >>> diverging_hsv()(10) 

2835 >>> #: Manually modified palette from 'cyan' to 'orange' 

2836 >>> diverging_hsv(h = [180, 30]).swatchplot( 

2837 >>> n = 7, show_names = False, figsize = (5.5, 0.5)) 

2838 >>> #: Additionally, lower saturation on the two ends 

2839 >>> diverging_hsv(h = [180, 30], s = 0.4).swatchplot( 

2840 >>> n = 7, show_names = False, figsize = (5.5, 0.5)) 

2841 >>> #: Lowering the value 

2842 >>> diverging_hsv(h = [180, 30], s = 0.4, v = 0.75).swatchplot( 

2843 >>> n = 7, show_names = False, figsize = (5.5, 0.5)) 

2844 """ 

2845 

2846 _allowed_parameters = ["h1", "h2", "s", "v"] 

2847 _name = "Diverging HSV" 

2848 

2849 def __init__(self, h = [240, 0], s = 1., v = 1., power = 1., 

2850 fixup = True, rev = False, *args, **kwargs): 

2851 

2852 self._set_rev(rev) 

2853 if not isinstance(fixup, bool): raise TypeError("argument `fixup` must be bool") 

2854 

2855 # Doing all the sanity checks. 

2856 if not isinstance(s, (float, int)): 

2857 raise TypeError("argument 's' must be float or int") 

2858 s = float(s) 

2859 if s < 0. or s > 1.: raise ValueError("argument 's' must be in [0., 1.]") 

2860 

2861 if not isinstance(v, (float, int)): 

2862 raise TypeError("argument 'v' must be float or int") 

2863 v = float(v) 

2864 if v < 0. or v > 1.: raise ValueError("argument 'v' must be in [0., 1.]") 

2865 

2866 # _checkinput_ parameters (in the correct order): 

2867 # dtype, length = None, recycle = False, nansallowed = False, **kwargs 

2868 try: 

2869 h = self._checkinput_(int, 2, 2, False, h = h) 

2870 power = self._checkinput_(float, 1, 1, False, power = power)[0] 

2871 except Exception as e: 

2872 raise ValueError(str(e)) 

2873 

2874 # Save settins 

2875 self.settings = {"h1": int(h[0]), "h2": int(h[1]), 

2876 "s": s, "v": v, 

2877 "power": power, "fixup": fixup} 

2878 

2879 # If keyword arguments are set: 

2880 # overwrite the settings if possible. 

2881 if kwargs: 

2882 for key,val in kwargs.items(): 

2883 if not key in self._allowed_parameters + ["desc", "gui"]: 

2884 raise ValueError(f"argument `{key}` not allowed for {type(self).__name__}") 

2885 self.settings[key] = val 

2886 

2887 self.settings["rev"] = self._rev 

2888 

2889 

2890 

2891 # Return hex colors 

2892 def colors(self, n = 11, fixup = None, alpha = None, **kwargs): 

2893 """Get Colors 

2894 

2895 Returns the colors of the current color palette. 

2896 

2897 Args: 

2898 n (int): Number of colors which should be returned. 

2899 fixup (None, bool): Should sRGB colors be corrected if they lie 

2900 outside the defined color space? If `None` the `fixup` 

2901 parameter from the object will be used. Can be set to `True` or 

2902 `False` to explicitly control the fixup here. 

2903 alpha (None, float, list, or numpy.ndarray): Allows to add an transparency 

2904 (alpha channel) to the colors. Can be a single float, a list, or a 

2905 numpy array. If a list or array is provided it must be of length 1 or 

2906 of length `n` and be convertible to float, providing values 

2907 between `0.0` (full opacity) and `1.0` (full transparency) 

2908 **kwargs: Currently allows for `rev = True` to reverse the colors. 

2909 

2910 Returns: 

2911 list: Returns a list of str with `n` colors from the 

2912 color palette. 

2913 

2914 """ 

2915 

2916 from numpy import linspace, power, abs, repeat, where 

2917 from numpy import ndarray, ndenumerate 

2918 from .colorlib import HSV 

2919 

2920 alpha = self._get_alpha_array(alpha, n) 

2921 fixup = fixup if isinstance(fixup, bool) else self.settings["fixup"] 

2922 

2923 # Calculate palette 

2924 rval = linspace(-self.get("s"), self.get("s"), n) 

2925 

2926 # Calculate H, S, V coordinates 

2927 H = repeat(self.get("h1"), n) 

2928 H[where(rval > 0)] = self.get("h2") 

2929 S = power(abs(rval), self.get("power")) 

2930 V = repeat(self.get("v"), n) 

2931 

2932 # Generate color object 

2933 HSV = HSV(H, S, V, alpha) 

2934 HSV.to("RGB") # Force to go trough RGB (not sRGB directly) 

2935 

2936 # Reversing colors 

2937 rev = self._rev 

2938 if "rev" in kwargs.keys(): rev = kwargs["rev"] 

2939 

2940 # Return hex colors 

2941 return HSV.colors(fixup = fixup, rev = rev) 

2942 

2943 

2944 

2945# ------------------------------------------------------------------- 

2946# ------------------------------------------------------------------- 

2947class rainbow(hclpalette): 

2948 """Infamous sRGB Rainbow Color Palette 

2949 

2950 Implements the (in-)famous rainbow (or jet) color palette that was used 

2951 very frequently in many software packages but has been widely criticized 

2952 for its many perceptual problems. It is specified by a `start` and `end` 

2953 hue $\in [0.-1.]$ with `red = 0`, `yellow = 1/6`, `green = 2/6`, `cyan = 

2954 3/6`, blue = `4/6`, and `magenta = 5/6`. However, these are very flashy and 

2955 unbalanced with respect to both chroma and luminance which can lead to 

2956 various optical illusions. Also, the hues that are equispaced in RGB space 

2957 tend to cluster at the red, green, and blue primaries. Therefore, it is 

2958 recommended to use a suitable palette from `hcl.colors` instead of 

2959 `rainbow`. 

2960 

2961 `start` and/or `end` both allow for lambda functions with one single 

2962 argument `n` (number of colors), see examples. 

2963 

2964 See also: :py:class:`qualitative_hcl`, :py:class:`sequential_hcl`, 

2965 :py:class:`diverging_hcl`, :py:class:`divergingx_hcl`, 

2966 :py:class:`rainbow_hcl`, :py:class:`heat_hcl`, :py:class:`terrain_hcl`, and 

2967 :py:class:`diverging_hsv`. 

2968 

2969 Args: 

2970 s (float, int): saturation value, a value in `[0., 1.]`. Defaults to `1.0`. 

2971 v (float, int): value, a value in `[0., 1.]`. Defaults to `1.0`. 

2972 start (float, int, function): the (corrected) hue in `[0., 1.]` at which 

2973 the rainbow begins. Defaults to `0.`. Can be a function with one input 

2974 `n` (number of colors). If outside `[0., 1.]` it will be wrapped. 

2975 end (float, int, function): the (corrected) hue in `[0., 1.]` at which 

2976 the rainbow ends. Defaults to `0.`. Can be a function with one input 

2977 `n` (number of colors). If outside `[0., 1.]` it will be wrapped. 

2978 rev (bool): Should the color map be reversed. 

2979 *args: Unused. 

2980 **kwargs: Unused. 

2981 

2982 Returns: 

2983 Initialize new object, no return. Raises a set of errors if the parameters 

2984 are misspecified. Note that the object is callable, the default object call 

2985 can be used to return hex colors (identical to the `.colors()` method), 

2986 see examples. 

2987 

2988 Example: 

2989 

2990 >>> from colorspace import rainbow 

2991 >>> pal = rainbow() 

2992 >>> pal.colors(10) 

2993 >>> #: 

2994 >>> pal.swatchplot(show_names = False, figsize = (5.5, 0.5)); 

2995 >>> #: The standard call of the object also returns hex colors 

2996 >>> rainbow()(10) 

2997 >>> 

2998 >>> #: Using lambda functions for start/end 

2999 >>> p = rainbow(start = lambda n: 1 / n, end = lambda n: 1 - 1 / n) 

3000 >>> p.swatchplot(n = 5, show_names = False, figsize = (5.5, 0.5)); 

3001 >>> #: 

3002 >>> p.swatchplot(n = 10, show_names = False, figsize = (5.5, 0.5)); 

3003 >>> #: 

3004 >>> p.specplot(rgb = True, figsize = (8, 6)) 

3005 

3006 Raises: 

3007 TypeError: If `s` or `v` are not float or int. 

3008 ValueError: If `s` or `v` are outside range, must be in `[0., 1.]`. 

3009 TypeError: If `start` and `end` are not float/int in `[0., 1.]` or lambda functions. 

3010 TypeError: If `rev` is not bool. 

3011 """ 

3012 

3013 _allowed_parameters = ["s", "v", "start", "end"] 

3014 _name = "Diverging HCL" 

3015 

3016 def __init__(self, s = 1, v = 1, start = 0, end = lambda n: max(1., n - 1.) / n, 

3017 rev = False, *args, **kwargs): 

3018 

3019 self._set_rev(rev) 

3020 

3021 # Doing all the sanity checks. 

3022 if not isinstance(s, (float, int)): 

3023 raise TypeError("argument 's' must be float or int") 

3024 s = float(s) 

3025 if s < 0. or s > 1.: raise ValueError("argument 's' must be in [0., 1.]") 

3026 

3027 if not isinstance(v, (float, int)): 

3028 raise TypeError("argument 'v' must be float or int") 

3029 v = float(v) 

3030 if v < 0. or v > 1.: raise ValueError("argument 'v' must be in [0., 1.]") 

3031 

3032 if not isinstance(start, (float, int)) and not callable(start): 

3033 raise TypeError("argument `start` must be float, int, or lambda function") 

3034 if not isinstance(end, (float, int)) and not callable(end): 

3035 raise TypeError("argument `end` must be float, int, or lambda function") 

3036 

3037 # Check start and end. Either functions (lambda functions) 

3038 # or a single floating point number in [0-1]. 

3039 if callable(start): pass 

3040 elif isinstance(start, (float, int)): start = float(start) 

3041 if callable(end): pass 

3042 elif isinstance(end, (float, int)): end = float(end) 

3043 

3044 # Store these settings 

3045 self.settings = {"s": float(s), "v": float(v), "start": start, "end": end, "rev": self._rev} 

3046 

3047 

3048 # Return hex colors 

3049 def colors(self, n = 11, alpha = None, **kwargs): 

3050 """Get Colors 

3051 

3052 Returns the colors of the current color palette. 

3053 

3054 Args: 

3055 n (int): Number of colors which should be returned. Defaults to `11`. 

3056 alpha (None, float, list, or numpy.ndarray): Allows to add an transparency 

3057 (alpha channel) to the colors. Can be a single float, a list, or a 

3058 numpy array. If a list or array is provided it must be of length 1 or 

3059 of length `n` and be convertible to float, providing values 

3060 between `0.0` (full opacity) and `1.0` (full transparency) 

3061 **kwargs: Currently allows for `rev = True` to reverse the colors. 

3062 

3063 Returns: 

3064 list: Returns a list of str with `n` colors from the 

3065 color palette. 

3066 

3067 Examples: 

3068 >>> from colorspace import rainbow 

3069 >>> rainbow().colors(4) 

3070 

3071 Raises: 

3072 ValueError: If input `n` is not float/int or smaller than 1. 

3073 """ 

3074 

3075 from numpy import linspace, mod, repeat 

3076 from .colorlib import HSV 

3077 

3078 if not isinstance(n, int): 

3079 raise TypeError("argument `n` must be int") 

3080 n = int(n) 

3081 if n <= 0: raise ValueError("argument `n` must be positive (>= 1)") 

3082 

3083 alpha = self._get_alpha_array(alpha, n) 

3084 

3085 # Evaluate start and end given 'n' 

3086 start = self.settings["start"] 

3087 start = start(n) if callable(start) else start 

3088 end = self.settings["end"] 

3089 end = end(n) if callable(end) else end 

3090 

3091 h = mod(linspace(start, end, n), 1.) 

3092 s = repeat(self.settings["s"], n) 

3093 v = repeat(self.settings["v"], n) 

3094 

3095 # Generate HSV colorobject 

3096 HSV = HSV(h * 360., s, v, alpha) 

3097 # If kwargs have a key "colorobject" return HCL colorobject 

3098 if "colorobject" in kwargs.keys(): return HSV 

3099 

3100 # Reversing colors if needed 

3101 rev = self._rev 

3102 if "rev" in kwargs.keys(): rev = kwargs["rev"] 

3103 

3104 # Return hex colors 

3105 return HSV.colors(fixup = False, rev = rev) 

3106 

3107