Coverage for colour/plotting/temperature.py: 97%

152 statements  

« prev     ^ index     » next       coverage.py v7.11.0, created at 2025-11-15 19:01 +1300

1""" 

2Colour Temperature & Correlated Colour Temperature Plotting 

3=========================================================== 

4 

5Define the colour temperature and correlated colour temperature plotting 

6objects. 

7 

8- :func:`colour.plotting.lines_daylight_locus` 

9- :func:`colour.plotting.lines_planckian_locus` 

10- :func:`colour.plotting.\ 

11plot_planckian_locus_in_chromaticity_diagram_CIE1931` 

12- :func:`colour.plotting.\ 

13plot_planckian_locus_in_chromaticity_diagram_CIE1960UCS` 

14- :func:`colour.plotting.\ 

15plot_planckian_locus_in_chromaticity_diagram_CIE1976UCS` 

16""" 

17 

18from __future__ import annotations 

19 

20import typing 

21 

22import numpy as np 

23 

24if typing.TYPE_CHECKING: 

25 from matplotlib.axes import Axes 

26 

27from matplotlib.collections import LineCollection 

28 

29if typing.TYPE_CHECKING: 

30 from matplotlib.figure import Figure 

31 

32from colour.algebra import normalise_maximum, normalise_vector 

33from colour.colorimetry import CCS_ILLUMINANTS, MSDS_CMFS 

34from colour.constants import DTYPE_FLOAT_DEFAULT 

35 

36if typing.TYPE_CHECKING: 

37 from colour.hints import ( 

38 Any, 

39 ArrayLike, 

40 Callable, 

41 Dict, 

42 List, 

43 Literal, 

44 NDArray, 

45 NDArrayFloat, 

46 Sequence, 

47 Tuple, 

48 ) 

49 

50from colour.hints import cast 

51from colour.models import UCS_uv_to_xy, xy_to_XYZ 

52from colour.plotting import ( 

53 CONSTANTS_ARROW_STYLE, 

54 CONSTANTS_COLOUR_STYLE, 

55 METHODS_CHROMATICITY_DIAGRAM, 

56 XYZ_to_plotting_colourspace, 

57 artist, 

58 filter_passthrough, 

59 override_style, 

60 plot_chromaticity_diagram_CIE1931, 

61 plot_chromaticity_diagram_CIE1960UCS, 

62 plot_chromaticity_diagram_CIE1976UCS, 

63 render, 

64 update_settings_collection, 

65) 

66from colour.plotting.diagrams import plot_chromaticity_diagram 

67from colour.temperature import CCT_to_uv, CCT_to_xy_CIE_D, mired_to_CCT 

68from colour.utilities import ( 

69 CanonicalMapping, 

70 as_float_array, 

71 as_float_scalar, 

72 as_int_scalar, 

73 full, 

74 optional, 

75 tstack, 

76 validate_method, 

77 zeros, 

78) 

79 

80__author__ = "Colour Developers" 

81__copyright__ = "Copyright 2013 Colour Developers" 

82__license__ = "BSD-3-Clause - https://opensource.org/licenses/BSD-3-Clause" 

83__maintainer__ = "Colour Developers" 

84__email__ = "colour-developers@colour-science.org" 

85__status__ = "Production" 

86 

87__all__ = [ 

88 "lines_daylight_locus", 

89 "plot_daylight_locus", 

90 "LABELS_PLANCKIAN_LOCUS_DEFAULT", 

91 "lines_planckian_locus", 

92 "plot_planckian_locus", 

93 "plot_planckian_locus_in_chromaticity_diagram", 

94 "plot_planckian_locus_in_chromaticity_diagram_CIE1931", 

95 "plot_planckian_locus_in_chromaticity_diagram_CIE1960UCS", 

96 "plot_planckian_locus_in_chromaticity_diagram_CIE1976UCS", 

97] 

98 

99 

100def lines_daylight_locus( 

101 mireds: bool = False, 

102 method: (Literal["CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS"] | str) = "CIE 1931", 

103) -> Tuple[NDArray]: 

104 """ 

105 Return the *Daylight Locus* line vertices including positions, normals, 

106 and colours using the specified chromaticity diagram method. 

107 

108 Parameters 

109 ---------- 

110 mireds 

111 Whether to use micro reciprocal degrees for the iso-temperature 

112 lines. 

113 method 

114 *Daylight Locus* chromaticity diagram method. 

115 

116 Returns 

117 ------- 

118 :class:`tuple` 

119 Tuple of *Daylight Locus* line vertices containing position, 

120 normal, and colour data. 

121 

122 Examples 

123 -------- 

124 >>> lines = lines_daylight_locus() 

125 >>> len(lines) 

126 1 

127 >>> lines[0].dtype 

128 dtype([('position', '<f8', (2,)), ('normal', '<f8', (2,)), \ 

129('colour', '<f8', (3,))]) 

130 """ 

131 

132 method = validate_method(method, ("CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS")) 

133 

134 xy_to_ij = METHODS_CHROMATICITY_DIAGRAM[method]["xy_to_ij"] 

135 

136 def CCT_to_plotting_colourspace(CCT: ArrayLike) -> NDArrayFloat: 

137 """ 

138 Convert specified correlated colour temperature :math:`T_{cp}` to the 

139 default plotting colourspace. 

140 """ 

141 

142 return normalise_maximum( 

143 XYZ_to_plotting_colourspace(xy_to_XYZ(CCT_to_xy_CIE_D(CCT))), 

144 axis=-1, 

145 ) 

146 

147 start, end = (0, 1000) if mireds else (1e6 / 600, 1e6 / 10) 

148 

149 CCT = np.arange(start, end + 100, 10) * 1.4388 / 1.4380 

150 CCT = mired_to_CCT(CCT) if mireds else CCT 

151 

152 ij_sl = np.reshape(xy_to_ij(CCT_to_xy_CIE_D(CCT)), (-1, 2)) 

153 colour_sl = np.reshape(CCT_to_plotting_colourspace(CCT), (-1, 3)) 

154 

155 lines_sl = zeros( 

156 ij_sl.shape[0], 

157 [ 

158 ("position", DTYPE_FLOAT_DEFAULT, 2), 

159 ("normal", DTYPE_FLOAT_DEFAULT, 2), 

160 ("colour", DTYPE_FLOAT_DEFAULT, 3), 

161 ], # pyright: ignore 

162 ) 

163 

164 lines_sl["position"] = ij_sl 

165 lines_sl["colour"] = colour_sl 

166 

167 return (lines_sl,) 

168 

169 

170@override_style() 

171def plot_daylight_locus( 

172 daylight_locus_colours: ArrayLike | str | None = None, 

173 daylight_locus_opacity: float = 1, 

174 daylight_locus_mireds: bool = False, 

175 method: (Literal["CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS"] | str) = "CIE 1931", 

176 **kwargs: Any, 

177) -> Tuple[Figure, Axes]: 

178 """ 

179 Plot the *Daylight Locus* using the specified chromaticity diagram 

180 method. 

181 

182 Parameters 

183 ---------- 

184 daylight_locus_colours 

185 Colours of the *Daylight Locus*. If set to *RGB*, the colours will 

186 be computed from the corresponding chromaticity coordinates. 

187 daylight_locus_opacity 

188 Opacity of the *Daylight Locus*. 

189 daylight_locus_mireds 

190 Whether to use micro reciprocal degrees for the iso-temperature 

191 lines. 

192 method 

193 *Chromaticity Diagram* method. 

194 

195 Other Parameters 

196 ---------------- 

197 kwargs 

198 {:func:`colour.plotting.artist`, :func:`colour.plotting.render`}, 

199 See the documentation of the previously listed definitions. 

200 

201 Returns 

202 ------- 

203 :class:`tuple` 

204 Current figure and axes. 

205 

206 Examples 

207 -------- 

208 >>> plot_daylight_locus(daylight_locus_colours="RGB") 

209 ... # doctest: +ELLIPSIS 

210 (<Figure size ... with 1 Axes>, <...Axes...>) 

211 

212 .. image:: ../_static/Plotting_Plot_Daylight_Locus.png 

213 :align: center 

214 :alt: plot_daylight_locus 

215 """ 

216 

217 method = validate_method(method, ("CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS")) 

218 

219 use_RGB_daylight_locus_colours = str(daylight_locus_colours).upper() == "RGB" 

220 

221 daylight_locus_colours = optional( 

222 daylight_locus_colours, CONSTANTS_COLOUR_STYLE.colour.dark 

223 ) 

224 

225 settings: Dict[str, Any] = {"uniform": True} 

226 settings.update(kwargs) 

227 

228 _figure, axes = artist(**settings) 

229 

230 lines_sl, *_ = lines_daylight_locus(daylight_locus_mireds, method) 

231 

232 line_collection = LineCollection( 

233 np.reshape( 

234 np.concatenate( 

235 [lines_sl["position"][:-1], lines_sl["position"][1:]], axis=1 

236 ), 

237 (-1, 2, 2), 

238 ), # pyright: ignore 

239 colors=( 

240 lines_sl["colour"] 

241 if use_RGB_daylight_locus_colours 

242 else daylight_locus_colours 

243 ), 

244 alpha=daylight_locus_opacity, 

245 zorder=CONSTANTS_COLOUR_STYLE.zorder.foreground_line, 

246 ) 

247 axes.add_collection(line_collection) 

248 

249 settings = {"axes": axes} 

250 settings.update(kwargs) 

251 

252 return render(**settings) 

253 

254 

255LABELS_PLANCKIAN_LOCUS_DEFAULT: CanonicalMapping = CanonicalMapping( 

256 { 

257 "Default": (1e6 / 600, 2000, 2500, 3000, 4000, 6000, 1e6 / 100), 

258 "Mireds": (0, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000), 

259 } 

260) 

261"""*Planckian Locus* default labels.""" 

262 

263 

264def lines_planckian_locus( 

265 labels: Sequence | None = None, 

266 mireds: bool = False, 

267 iso_temperature_lines_D_uv: float = 0.05, 

268 method: (Literal["CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS"] | str) = "CIE 1931", 

269) -> Tuple[NDArray, NDArray]: 

270 """ 

271 Return the *Planckian Locus* line vertices with positions, normals, and 

272 colours using the specified chromaticity diagram method. 

273 

274 Parameters 

275 ---------- 

276 labels 

277 Array of labels used to customise which iso-temperature lines will 

278 be drawn along the *Planckian Locus*. Passing an empty array will 

279 result in no iso-temperature lines being drawn. 

280 mireds 

281 Whether to use micro reciprocal degrees for the iso-temperature 

282 lines. 

283 iso_temperature_lines_D_uv 

284 Iso-temperature lines :math:`\\Delta_{uv}` length on each side of 

285 the *Planckian Locus*. 

286 method 

287 *Planckian Locus* method. 

288 

289 Returns 

290 ------- 

291 :class:`tuple` 

292 Tuple of *Planckian Locus* vertices and wavelength labels vertices. 

293 

294 Examples 

295 -------- 

296 >>> lines = lines_planckian_locus() 

297 >>> len(lines) 

298 2 

299 >>> lines[0].dtype 

300 dtype([('position', '<f8', (2,)), ('normal', '<f8', (2,)), \ 

301('colour', '<f8', (3,))]) 

302 >>> lines[1].dtype 

303 dtype([('position', '<f8', (2,)), ('normal', '<f8', (2,)), \ 

304('colour', '<f8', (3,))]) 

305 """ 

306 

307 method = validate_method(method, ("CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS")) 

308 

309 labels = cast( 

310 "tuple", 

311 optional( 

312 labels, 

313 LABELS_PLANCKIAN_LOCUS_DEFAULT["Mireds" if mireds else "Default"], 

314 ), 

315 ) 

316 D_uv = iso_temperature_lines_D_uv 

317 

318 uv_to_ij = METHODS_CHROMATICITY_DIAGRAM[method]["uv_to_ij"] 

319 

320 def CCT_D_uv_to_plotting_colourspace(CCT_D_uv: ArrayLike) -> NDArrayFloat: 

321 """ 

322 Convert specified correlated colour temperature :math:`T_{cp}` and 

323 :math:`\\Delta_{uv}` to the default plotting colourspace. 

324 """ 

325 

326 return normalise_maximum( 

327 XYZ_to_plotting_colourspace( 

328 xy_to_XYZ(UCS_uv_to_xy(CCT_to_uv(CCT_D_uv, "Robertson 1968"))) 

329 ), 

330 axis=-1, 

331 ) 

332 

333 # Planckian Locus 

334 start, end = (0, 1000) if mireds else (1e6 / 600, 1e6 / 10) 

335 

336 CCT = np.arange(start, end + 100, 100) 

337 CCT = mired_to_CCT(CCT) if mireds else CCT 

338 CCT_D_uv = tstack([CCT, zeros(CCT.shape)]) 

339 

340 ij_pl = uv_to_ij(CCT_to_uv(CCT_D_uv, "Robertson 1968")) 

341 colour_pl = CCT_D_uv_to_plotting_colourspace(CCT_D_uv) 

342 

343 lines_pl = zeros( 

344 ij_pl.shape[0], 

345 [ 

346 ("position", DTYPE_FLOAT_DEFAULT, 2), 

347 ("normal", DTYPE_FLOAT_DEFAULT, 2), 

348 ("colour", DTYPE_FLOAT_DEFAULT, 3), 

349 ], # pyright: ignore 

350 ) 

351 

352 lines_pl["position"] = ij_pl 

353 lines_pl["colour"] = colour_pl 

354 

355 # Labels 

356 ij_itl, normal_itl, colour_itl = [], [], [] 

357 for label in labels: 

358 CCT_D_uv = tstack( 

359 [ 

360 full( 

361 20, 

362 as_float_scalar(mired_to_CCT(label)) if mireds else label, 

363 ), 

364 np.linspace(-D_uv, D_uv, 20), 

365 ] 

366 ) 

367 

368 ij = uv_to_ij(CCT_to_uv(CCT_D_uv, "Robertson 1968")) 

369 ij_itl.append(ij) 

370 normal_itl.append(np.tile(normalise_vector(ij[-1, ...] - ij[0, ...]), (20, 1))) 

371 colour_itl.append(CCT_D_uv_to_plotting_colourspace(CCT_D_uv)) 

372 

373 ij_l = np.reshape(as_float_array(ij_itl), (-1, 2)) 

374 normal_l = np.reshape(as_float_array(normal_itl), (-1, 2)) 

375 colour_l = np.reshape(as_float_array(colour_itl), (-1, 3)) 

376 

377 lines_l = zeros( 

378 ij_l.shape[0], 

379 [ 

380 ("position", DTYPE_FLOAT_DEFAULT, 2), 

381 ("normal", DTYPE_FLOAT_DEFAULT, 2), 

382 ("colour", DTYPE_FLOAT_DEFAULT, 3), 

383 ], # pyright: ignore 

384 ) 

385 

386 lines_l["position"] = ij_l 

387 lines_l["normal"] = normal_l 

388 lines_l["colour"] = colour_l 

389 

390 return lines_pl, lines_l 

391 

392 

393@override_style() 

394def plot_planckian_locus( 

395 planckian_locus_colours: ArrayLike | str | None = None, 

396 planckian_locus_opacity: float = 1, 

397 planckian_locus_labels: Sequence | None = None, 

398 planckian_locus_mireds: bool = False, 

399 planckian_locus_iso_temperature_lines_D_uv: float = 0.05, 

400 method: (Literal["CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS"] | str) = "CIE 1931", 

401 **kwargs: Any, 

402) -> Tuple[Figure, Axes]: 

403 """ 

404 Plot the *Planckian Locus* using the specified method. 

405 

406 Parameters 

407 ---------- 

408 planckian_locus_colours 

409 Colours of the *Planckian Locus*, if ``planckian_locus_colours`` is 

410 set to *RGB*, the colours will be computed using the corresponding 

411 chromaticity coordinates. 

412 planckian_locus_opacity 

413 Opacity of the *Planckian Locus*. 

414 planckian_locus_labels 

415 Array of labels used to customise which iso-temperature lines will 

416 be drawn along the *Planckian Locus*. Passing an empty array will 

417 result in no iso-temperature lines being drawn. 

418 planckian_locus_mireds 

419 Whether to use micro reciprocal degrees for the iso-temperature 

420 lines. 

421 planckian_locus_iso_temperature_lines_D_uv 

422 Iso-temperature lines :math:`\\Delta_{uv}` length on each side of 

423 the *Planckian Locus*. 

424 method 

425 *Chromaticity Diagram* method. 

426 

427 Other Parameters 

428 ---------------- 

429 kwargs 

430 {:func:`colour.plotting.artist`, :func:`colour.plotting.render`}, 

431 See the documentation of the previously listed definitions. 

432 

433 Returns 

434 ------- 

435 :class:`tuple` 

436 Current figure and axes. 

437 

438 Examples 

439 -------- 

440 >>> plot_planckian_locus(planckian_locus_colours="RGB") 

441 ... # doctest: +ELLIPSIS 

442 (<Figure size ... with 1 Axes>, <...Axes...>) 

443 

444 .. image:: ../_static/Plotting_Plot_Planckian_Locus.png 

445 :align: center 

446 :alt: plot_planckian_locus 

447 """ 

448 

449 planckian_locus_colours = optional( 

450 planckian_locus_colours, CONSTANTS_COLOUR_STYLE.colour.dark 

451 ) 

452 

453 use_RGB_planckian_locus_colours = str(planckian_locus_colours).upper() == "RGB" 

454 

455 labels = cast( 

456 "tuple", 

457 optional( 

458 planckian_locus_labels, 

459 LABELS_PLANCKIAN_LOCUS_DEFAULT[ 

460 "Mireds" if planckian_locus_mireds else "Default" 

461 ], 

462 ), 

463 ) 

464 

465 settings: Dict[str, Any] = {"uniform": True} 

466 settings.update(kwargs) 

467 

468 _figure, axes = artist(**settings) 

469 

470 lines_pl, lines_l = lines_planckian_locus( 

471 labels, 

472 planckian_locus_mireds, 

473 planckian_locus_iso_temperature_lines_D_uv, 

474 method, 

475 ) 

476 

477 axes.add_collection( 

478 LineCollection( 

479 np.reshape( 

480 np.concatenate( 

481 [lines_pl["position"][:-1], lines_pl["position"][1:]], axis=1 

482 ), 

483 (-1, 2, 2), 

484 ), # pyright: ignore 

485 colors=( 

486 lines_pl["colour"] 

487 if use_RGB_planckian_locus_colours 

488 else planckian_locus_colours 

489 ), 

490 alpha=planckian_locus_opacity, 

491 zorder=CONSTANTS_COLOUR_STYLE.zorder.foreground_line, 

492 ) 

493 ) 

494 

495 lines_itl = np.reshape(lines_l["position"], (len(labels), 20, 2)) 

496 colours_itl = np.reshape(lines_l["colour"], (len(labels), 20, 3)) 

497 for i, label in enumerate(labels): 

498 axes.add_collection( 

499 LineCollection( 

500 np.reshape( 

501 np.concatenate( 

502 [lines_itl[i][:-1], lines_itl[i][1:]], # pyright: ignore 

503 axis=1, 

504 ), 

505 (-1, 2, 2), 

506 ), 

507 colors=( 

508 colours_itl[i] 

509 if use_RGB_planckian_locus_colours 

510 else planckian_locus_colours 

511 ), 

512 alpha=planckian_locus_opacity, 

513 zorder=CONSTANTS_COLOUR_STYLE.zorder.foreground_line, 

514 ) 

515 ) 

516 

517 axes.text( 

518 lines_itl[i][-1, 0], 

519 lines_itl[i][-1, 1], 

520 f"{as_int_scalar(label)}{'M' if planckian_locus_mireds else 'K'}", 

521 clip_on=True, 

522 ha="left", 

523 va="bottom", 

524 fontsize="x-small-colour-science", 

525 zorder=CONSTANTS_COLOUR_STYLE.zorder.foreground_label, 

526 ) 

527 

528 settings = {"axes": axes} 

529 settings.update(kwargs) 

530 

531 return render(**settings) 

532 

533 

534@override_style() 

535def plot_planckian_locus_in_chromaticity_diagram( 

536 illuminants: str | Sequence[str] | None = None, 

537 chromaticity_diagram_callable: Callable = plot_chromaticity_diagram, 

538 method: (Literal["CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS"] | str) = "CIE 1931", 

539 annotate_kwargs: dict | List[dict] | None = None, 

540 plot_kwargs: dict | List[dict] | None = None, 

541 **kwargs: Any, 

542) -> Tuple[Figure, Axes]: 

543 """ 

544 Plot the *Planckian Locus* and specified illuminants in the 

545 *Chromaticity Diagram* using the specified method. 

546 

547 Parameters 

548 ---------- 

549 illuminants 

550 Illuminants to plot. ``illuminants`` elements can be of any 

551 type or form supported by the 

552 :func:`colour.plotting.common.filter_passthrough` definition. 

553 chromaticity_diagram_callable 

554 Callable responsible for drawing the *Chromaticity Diagram*. 

555 method 

556 *Chromaticity Diagram* method. 

557 annotate_kwargs 

558 Keyword arguments for the :func:`matplotlib.pyplot.annotate` 

559 definition, used to annotate the resulting chromaticity 

560 coordinates with their respective spectral distribution names. 

561 ``annotate_kwargs`` can be either a single dictionary applied 

562 to all the arrows with same settings or a sequence of 

563 dictionaries with different settings for each spectral 

564 distribution. The following special keyword arguments can also 

565 be used: 

566 

567 - ``annotate`` : Whether to annotate the spectral distributions. 

568 plot_kwargs 

569 Keyword arguments for the :func:`matplotlib.pyplot.plot` 

570 definition, used to control the style of the plotted 

571 illuminants. ``plot_kwargs`` can be either a single dictionary 

572 applied to all the plotted illuminants with the same settings 

573 or a sequence of dictionaries with different settings for each 

574 plotted illuminant. 

575 

576 Other Parameters 

577 ---------------- 

578 kwargs 

579 {:func:`colour.plotting.artist`, 

580 :func:`colour.plotting.diagrams.plot_chromaticity_diagram`, 

581 :func:`colour.plotting.temperature.plot_planckian_locus`, 

582 :func:`colour.plotting.render`}, 

583 See the documentation of the previously listed definitions. 

584 

585 Returns 

586 ------- 

587 :class:`tuple` 

588 Current figure and axes. 

589 

590 Examples 

591 -------- 

592 >>> annotate_kwargs = [ 

593 ... {"xytext": (-25, 15), "arrowprops": {"arrowstyle": "-"}}, 

594 ... {"arrowprops": {"arrowstyle": "-["}}, 

595 ... {}, 

596 ... ] 

597 >>> plot_kwargs = [ 

598 ... { 

599 ... "markersize": 15, 

600 ... }, 

601 ... {"color": "r"}, 

602 ... {}, 

603 ... ] 

604 >>> plot_planckian_locus_in_chromaticity_diagram( 

605 ... ["A", "B", "C"], 

606 ... annotate_kwargs=annotate_kwargs, 

607 ... plot_kwargs=plot_kwargs, 

608 ... ) # doctest: +ELLIPSIS 

609 (<Figure size ... with 1 Axes>, <...Axes...>) 

610 

611 .. image:: ../_static/Plotting_\ 

612Plot_Planckian_Locus_In_Chromaticity_Diagram.png 

613 :align: center 

614 :alt: plot_planckian_locus_in_chromaticity_diagram 

615 """ 

616 

617 illuminants = optional(illuminants, []) 

618 

619 method = validate_method(method, ("CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS")) 

620 

621 cmfs = MSDS_CMFS["CIE 1931 2 Degree Standard Observer"] 

622 

623 illuminants_filtered = filter_passthrough(CCS_ILLUMINANTS[cmfs.name], illuminants) 

624 

625 settings: Dict[str, Any] = {"uniform": True} 

626 settings.update(kwargs) 

627 

628 _figure, axes = artist(**settings) 

629 

630 method = method.upper() 

631 

632 settings = {"axes": axes, "method": method} 

633 settings.update(kwargs) 

634 settings["show"] = False 

635 

636 chromaticity_diagram_callable(**settings) 

637 

638 plot_planckian_locus(**settings) 

639 

640 xy_to_ij = METHODS_CHROMATICITY_DIAGRAM[method]["xy_to_ij"] 

641 

642 if method == "CIE 1931": 

643 bounding_box = (-0.1, 0.9, -0.1, 0.9) 

644 

645 elif method == "CIE 1960 UCS": 

646 bounding_box = (-0.1, 0.7, -0.2, 0.6) 

647 

648 elif method == "CIE 1976 UCS": 

649 bounding_box = (-0.1, 0.7, -0.1, 0.7) 

650 

651 annotate_settings_collection = [ 

652 { 

653 "annotate": True, 

654 "xytext": (-50, 30), 

655 "textcoords": "offset points", 

656 "arrowprops": CONSTANTS_ARROW_STYLE, 

657 "zorder": CONSTANTS_COLOUR_STYLE.zorder.foreground_annotation, 

658 } 

659 for _ in range(len(illuminants_filtered)) 

660 ] 

661 

662 if annotate_kwargs is not None: 

663 update_settings_collection( 

664 annotate_settings_collection, 

665 annotate_kwargs, 

666 len(illuminants_filtered), 

667 ) 

668 

669 plot_settings_collection = [ 

670 { 

671 "color": CONSTANTS_COLOUR_STYLE.colour.brightest, 

672 "label": f"{illuminant}", 

673 "marker": "o", 

674 "markeredgecolor": CONSTANTS_COLOUR_STYLE.colour.dark, 

675 "markeredgewidth": CONSTANTS_COLOUR_STYLE.geometry.short * 0.75, 

676 "markersize": ( 

677 CONSTANTS_COLOUR_STYLE.geometry.short * 6 

678 + CONSTANTS_COLOUR_STYLE.geometry.short * 0.75 

679 ), 

680 "zorder": CONSTANTS_COLOUR_STYLE.zorder.foreground_line, 

681 } 

682 for illuminant in illuminants_filtered 

683 ] 

684 

685 if plot_kwargs is not None: 

686 update_settings_collection( 

687 plot_settings_collection, plot_kwargs, len(illuminants_filtered) 

688 ) 

689 

690 for i, (illuminant, xy) in enumerate(illuminants_filtered.items()): 

691 plot_settings = plot_settings_collection[i] 

692 

693 ij = cast("tuple[float, float]", xy_to_ij(xy)) 

694 

695 axes.plot(ij[0], ij[1], **plot_settings) 

696 

697 if annotate_settings_collection[i]["annotate"]: 

698 annotate_settings = annotate_settings_collection[i] 

699 annotate_settings.pop("annotate") 

700 

701 axes.annotate(illuminant, xy=ij, **annotate_settings) 

702 

703 title = ( 

704 ( 

705 f"{', '.join(illuminants_filtered)} Illuminants - Planckian Locus\n" 

706 f"{method.upper()} Chromaticity Diagram - " 

707 "CIE 1931 2 Degree Standard Observer" 

708 ) 

709 if illuminants_filtered 

710 else ( 

711 f"Planckian Locus\n{method.upper()} Chromaticity Diagram - " 

712 f"CIE 1931 2 Degree Standard Observer" 

713 ) 

714 ) 

715 

716 settings.update( 

717 { 

718 "axes": axes, 

719 "show": True, 

720 "bounding_box": bounding_box, 

721 "title": title, 

722 } 

723 ) 

724 settings.update(kwargs) 

725 

726 return render(**settings) 

727 

728 

729@override_style() 

730def plot_planckian_locus_in_chromaticity_diagram_CIE1931( 

731 illuminants: str | Sequence[str] | None = None, 

732 chromaticity_diagram_callable_CIE1931: Callable = ( 

733 plot_chromaticity_diagram_CIE1931 

734 ), 

735 annotate_kwargs: dict | List[dict] | None = None, 

736 plot_kwargs: dict | List[dict] | None = None, 

737 **kwargs: Any, 

738) -> Tuple[Figure, Axes]: 

739 """ 

740 Plot the *Planckian Locus* and specified illuminants in the 

741 *CIE 1931 Chromaticity Diagram*. 

742 

743 Parameters 

744 ---------- 

745 illuminants 

746 Illuminants to plot. ``illuminants`` elements can be of any type or 

747 form supported by the 

748 :func:`colour.plotting.common.filter_passthrough` definition. 

749 chromaticity_diagram_callable_CIE1931 

750 Callable responsible for drawing the *CIE 1931 Chromaticity Diagram*. 

751 annotate_kwargs 

752 Keyword arguments for the :func:`matplotlib.pyplot.annotate` 

753 definition, used to annotate the resulting chromaticity 

754 coordinates with their respective spectral distribution names. 

755 ``annotate_kwargs`` can be either a single dictionary applied 

756 to all the arrows with same settings or a sequence of 

757 dictionaries with different settings for each spectral 

758 distribution. The following special keyword arguments can also 

759 be used: 

760 

761 - ``annotate`` : Whether to annotate the spectral distributions. 

762 plot_kwargs 

763 Keyword arguments for the :func:`matplotlib.pyplot.plot` definition, 

764 used to control the style of the plotted illuminants. ``plot_kwargs`` 

765 can be either a single dictionary applied to all the plotted 

766 illuminants with the same settings or a sequence of dictionaries with 

767 different settings for each plotted illuminant. 

768 

769 Other Parameters 

770 ---------------- 

771 kwargs 

772 {:func:`colour.plotting.artist`, 

773 :func:`colour.plotting.diagrams.plot_chromaticity_diagram`, 

774 :func:`colour.plotting.temperature.plot_planckian_locus`, 

775 :func:`colour.plotting.temperature.\ 

776plot_planckian_locus_in_chromaticity_diagram`, 

777 :func:`colour.plotting.render`}, 

778 See the documentation of the previously listed definitions. 

779 

780 Returns 

781 ------- 

782 :class:`tuple` 

783 Current figure and axes. 

784 

785 Examples 

786 -------- 

787 >>> plot_planckian_locus_in_chromaticity_diagram_CIE1931(["A", "B", "C"]) 

788 ... # doctest: +ELLIPSIS 

789 (<Figure size ... with 1 Axes>, <...Axes...>) 

790 

791 .. image:: ../_static/Plotting_\ 

792Plot_Planckian_Locus_In_Chromaticity_Diagram_CIE1931.png 

793 :align: center 

794 :alt: plot_planckian_locus_in_chromaticity_diagram_CIE1931 

795 """ 

796 

797 settings = dict(kwargs) 

798 settings.update({"method": "CIE 1931"}) 

799 

800 return plot_planckian_locus_in_chromaticity_diagram( 

801 illuminants, 

802 chromaticity_diagram_callable_CIE1931, 

803 annotate_kwargs=annotate_kwargs, 

804 plot_kwargs=plot_kwargs, 

805 **settings, 

806 ) 

807 

808 

809@override_style() 

810def plot_planckian_locus_in_chromaticity_diagram_CIE1960UCS( 

811 illuminants: str | Sequence[str] | None = None, 

812 chromaticity_diagram_callable_CIE1960UCS: Callable = ( 

813 plot_chromaticity_diagram_CIE1960UCS 

814 ), 

815 annotate_kwargs: dict | List[dict] | None = None, 

816 plot_kwargs: dict | List[dict] | None = None, 

817 **kwargs: Any, 

818) -> Tuple[Figure, Axes]: 

819 """ 

820 Plot the *Planckian Locus* and specified illuminants in the 

821 *CIE 1960 UCS Chromaticity Diagram*. 

822 

823 Parameters 

824 ---------- 

825 illuminants 

826 Illuminants to plot. ``illuminants`` elements can be of any type or 

827 form supported by the 

828 :func:`colour.plotting.common.filter_passthrough` definition. 

829 chromaticity_diagram_callable_CIE1960UCS 

830 Callable responsible for drawing the 

831 *CIE 1960 UCS Chromaticity Diagram*. 

832 annotate_kwargs 

833 Keyword arguments for the :func:`matplotlib.pyplot.annotate` 

834 definition, used to annotate the resulting chromaticity 

835 coordinates with their respective spectral distribution names. 

836 ``annotate_kwargs`` can be either a single dictionary applied 

837 to all the arrows with same settings or a sequence of 

838 dictionaries with different settings for each spectral 

839 distribution. The following special keyword arguments can also 

840 be used: 

841 

842 - ``annotate`` : Whether to annotate the spectral distributions. 

843 plot_kwargs 

844 Keyword arguments for the :func:`matplotlib.pyplot.plot` 

845 definition, used to control the style of the plotted illuminants. 

846 ``plot_kwargs`` can be either a single dictionary applied to all 

847 the plotted illuminants with the same settings or a sequence of 

848 dictionaries with different settings for each plotted illuminant. 

849 

850 Other Parameters 

851 ---------------- 

852 kwargs 

853 {:func:`colour.plotting.artist`, 

854 :func:`colour.plotting.diagrams.plot_chromaticity_diagram`, 

855 :func:`colour.plotting.temperature.plot_planckian_locus`, 

856 :func:`colour.plotting.temperature.\ 

857plot_planckian_locus_in_chromaticity_diagram`, 

858 :func:`colour.plotting.render`}, 

859 See the documentation of the previously listed definitions. 

860 

861 Returns 

862 ------- 

863 :class:`tuple` 

864 Current figure and axes. 

865 

866 Examples 

867 -------- 

868 >>> plot_planckian_locus_in_chromaticity_diagram_CIE1960UCS( 

869 ... ["A", "C", "E"] 

870 ... ) # doctest: +ELLIPSIS 

871 (<Figure size ... with 1 Axes>, <...Axes...>) 

872 

873 .. image:: ../_static/Plotting_\ 

874Plot_Planckian_Locus_In_Chromaticity_Diagram_CIE1960UCS.png 

875 :align: center 

876 :alt: plot_planckian_locus_in_chromaticity_diagram_CIE1960UCS 

877 """ 

878 

879 settings = dict(kwargs) 

880 settings.update({"method": "CIE 1960 UCS"}) 

881 

882 return plot_planckian_locus_in_chromaticity_diagram( 

883 illuminants, 

884 chromaticity_diagram_callable_CIE1960UCS, 

885 annotate_kwargs=annotate_kwargs, 

886 plot_kwargs=plot_kwargs, 

887 **settings, 

888 ) 

889 

890 

891@override_style() 

892def plot_planckian_locus_in_chromaticity_diagram_CIE1976UCS( 

893 illuminants: str | Sequence[str] | None = None, 

894 chromaticity_diagram_callable_CIE1976UCS: Callable = ( 

895 plot_chromaticity_diagram_CIE1976UCS 

896 ), 

897 annotate_kwargs: dict | List[dict] | None = None, 

898 plot_kwargs: dict | List[dict] | None = None, 

899 **kwargs: Any, 

900) -> Tuple[Figure, Axes]: 

901 """ 

902 Plot the *Planckian Locus* and specified illuminants in the 

903 *CIE 1976 UCS Chromaticity Diagram*. 

904 

905 Parameters 

906 ---------- 

907 illuminants 

908 Illuminants to plot. ``illuminants`` elements can be of any type 

909 or form supported by the 

910 :func:`colour.plotting.common.filter_passthrough` definition. 

911 chromaticity_diagram_callable_CIE1976UCS 

912 Callable responsible for drawing the 

913 *CIE 1976 UCS Chromaticity Diagram*. 

914 annotate_kwargs 

915 Keyword arguments for the :func:`matplotlib.pyplot.annotate` 

916 definition, used to annotate the resulting chromaticity 

917 coordinates with their respective spectral distribution names. 

918 ``annotate_kwargs`` can be either a single dictionary applied 

919 to all the arrows with same settings or a sequence of 

920 dictionaries with different settings for each spectral 

921 distribution. The following special keyword arguments can also 

922 be used: 

923 

924 - ``annotate`` : Whether to annotate the spectral distributions. 

925 plot_kwargs 

926 Keyword arguments for the :func:`matplotlib.pyplot.plot` 

927 definition, used to control the style of the plotted illuminants. 

928 ``plot_kwargs`` can be either a single dictionary applied to all 

929 the plotted illuminants with the same settings or a sequence of 

930 dictionaries with different settings for each plotted illuminant. 

931 

932 Other Parameters 

933 ---------------- 

934 kwargs 

935 {:func:`colour.plotting.artist`, 

936 :func:`colour.plotting.diagrams.plot_chromaticity_diagram`, 

937 :func:`colour.plotting.temperature.plot_planckian_locus`, 

938 :func:`colour.plotting.temperature.\ 

939plot_planckian_locus_in_chromaticity_diagram`, 

940 :func:`colour.plotting.render`}, 

941 See the documentation of the previously listed definitions. 

942 

943 Returns 

944 ------- 

945 :class:`tuple` 

946 Current figure and axes. 

947 

948 Examples 

949 -------- 

950 >>> plot_planckian_locus_in_chromaticity_diagram_CIE1976UCS( 

951 ... ["A", "C", "E"] 

952 ... ) # doctest: +ELLIPSIS 

953 (<Figure size ... with 1 Axes>, <...Axes...>) 

954 

955 .. image:: ../_static/Plotting_\ 

956Plot_Planckian_Locus_In_Chromaticity_Diagram_CIE1976UCS.png 

957 :align: center 

958 :alt: plot_planckian_locus_in_chromaticity_diagram_CIE1976UCS 

959 """ 

960 

961 settings = dict(kwargs) 

962 settings.update({"method": "CIE 1976 UCS"}) 

963 

964 return plot_planckian_locus_in_chromaticity_diagram( 

965 illuminants, 

966 chromaticity_diagram_callable_CIE1976UCS, 

967 annotate_kwargs=annotate_kwargs, 

968 plot_kwargs=plot_kwargs, 

969 **settings, 

970 )