Coverage for plotting/tm3018/components.py: 79%

1"""

2 ANSI/IES TM-30-18 Colour Rendition Report Components

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

4

5 Define the *ANSI/IES TM-30-18 Colour Rendition Report* components plotting

6 objects for comprehensive colour rendition evaluation and visualization.

7

8- :func:`colour.plotting.tm3018.components.plot_spectra_ANSIIESTM3018`

9- :func:`colour.plotting.tm3018.components.plot_colour_vector_graphic`

10- :func:`colour.plotting.tm3018.components.plot_16_bin_bars`

11- :func:`colour.plotting.tm3018.components.plot_local_chroma_shifts`

12- :func:`colour.plotting.tm3018.components.plot_local_hue_shifts`

13- :func:`colour.plotting.tm3018.components.plot_local_colour_fidelities`

14- :func:`colour.plotting.tm3018.components.plot_colour_fidelity_indexes`

15"""

16

17 from __future__ import annotations

18

19 import os

20 import typing

21

22 import numpy as np

23

24 if typing.TYPE_CHECKING:

25 from matplotlib.axes import Axes

26 from matplotlib.figure import Figure

27

28 from matplotlib.patches import Circle

29

30 from colour.algebra import sdiv, sdiv_mode

31 from colour.colorimetry import sd_to_XYZ

32

33 if typing.TYPE_CHECKING:

34 from colour.hints import Any, ArrayLike, Dict, Literal, Tuple

35 from colour.quality import ColourQuality_Specification_ANSIIESTM3018

36

37 from colour.io import read_image

38 from colour.plotting import (

39 CONSTANTS_COLOUR_STYLE,

40 artist,

41 override_style,

42 plot_image,

43 render,

44)

45 from colour.utilities import as_float_array, validate_method

46

47 __author__ = "Colour Developers"

48 __copyright__ = "Copyright 2013 Colour Developers"

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

50 __maintainer__ = "Colour Developers"

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

52 __status__ = "Production"

53

54 __all__ = [

55 "ROOT_RESOURCES_ANSIIESTM3018",

56 "plot_spectra_ANSIIESTM3018",

57 "plot_colour_vector_graphic",

58 "plot_16_bin_bars",

59 "plot_local_chroma_shifts",

60 "plot_local_hue_shifts",

61 "plot_local_colour_fidelities",

62 "plot_colour_fidelity_indexes",

63]

64

65 ROOT_RESOURCES_ANSIIESTM3018: str = os.path.join(os.path.dirname(__file__), "resources")

66"""Resources directory."""

67

68 _COLOURS_BIN_BAR: list = [

69 "#A35C60",

70 "#CC765E",

71 "#CC8145",

72 "#D8AC62",

73 "#AC9959",

74 "#919E5D",

75 "#668B5E",

76 "#61B290",

77 "#7BBAA6",

78 "#297A7E",

79 "#55788D",

80 "#708AB2",

81 "#988CAA",

82 "#735877",

83 "#8F6682",

84 "#BA7A8E",

85]

86

87 _COLOURS_BIN_ARROW: list = [

88 "#E62828",

89 "#E74B4B",

90 "#FB812E",

91 "#FFB529",

92 "#CBCA46",

93 "#7EB94C",

94 "#41C06D",

95 "#009C7C",

96 "#16BCB0",

97 "#00A4BF",

98 "#0085C3",

99 "#3B62AA",

100 "#4568AE",

101 "#6A4E85",

102 "#9D69A1",

103 "#A74F81",

104]

105

106 _COLOURS_TCS_BAR: list = [

107 "#F1BDCD",

108 "#CA6183",

109 "#573A40",

110 "#CD8791",

111 "#AD3F55",

112 "#925F62",

113 "#933440",

114 "#8C3942",

115 "#413D3E",

116 "#FA8070",

117 "#C35644",

118 "#DA604A",

119 "#824E39",

120 "#BCA89F",

121 "#C29A89",

122 "#8D593C",

123 "#915E3F",

124 "#99745B",

125 "#D39257",

126 "#D07F2C",

127 "#FEB45F",

128 "#EFA248",

129 "#F0DFBD",

130 "#FED586",

131 "#D0981E",

132 "#FED06A",

133 "#B5AC81",

134 "#645D37",

135 "#EAD163",

136 "#9E9464",

137 "#EBD969",

138 "#C4B135",

139 "#E6DE9C",

140 "#99912C",

141 "#61603A",

142 "#C2C2AF",

143 "#6D703B",

144 "#D2D7A1",

145 "#4B5040",

146 "#6B7751",

147 "#D3DCC3",

148 "#88B33A",

149 "#8EBF3E",

150 "#3E3F3D",

151 "#65984A",

152 "#83A96E",

153 "#92AE86",

154 "#91CD8E",

155 "#477746",

156 "#568C6A",

157 "#659477",

158 "#276E49",

159 "#008D62",

160 "#B6E2D4",

161 "#A5D9CD",

162 "#39C4AD",

163 "#00A18A",

164 "#009786",

165 "#B4E1D9",

166 "#CDDDDC",

167 "#99C1C0",

168 "#909FA1",

169 "#494D4E",

170 "#009FA8",

171 "#32636A",

172 "#007788",

173 "#007F95",

174 "#66A0B2",

175 "#687D88",

176 "#75B6DB",

177 "#1E5574",

178 "#AAB9C3",

179 "#3091C4",

180 "#3B3E41",

181 "#274D72",

182 "#376FB8",

183 "#496692",

184 "#3B63AC",

185 "#A0AED5",

186 "#9293C8",

187 "#61589D",

188 "#D4D3E5",

189 "#ACA6CA",

190 "#3E3B45",

191 "#5F5770",

192 "#A08CC7",

193 "#664782",

194 "#A77AB5",

195 "#6A4172",

196 "#7D4983",

197 "#C4BFC4",

198 "#937391",

199 "#AE91AA",

200 "#764068",

201 "#BF93B1",

202 "#D7A9C5",

203 "#9D587F",

204 "#CE6997",

205 "#AE4A79",

206]

207

208

209@override_style()

210 def plot_spectra_ANSIIESTM3018(

211 specification: ColourQuality_Specification_ANSIIESTM3018, **kwargs: Any

212) -> Tuple[Figure, Axes]:

213 """

214 Plot the spectral distributions of a test emission source and reference

215 illuminant for *ANSI/IES TM-30-18 Colour Rendition Report*.

216

217 Parameters

218 ----------

219 specification

220 *ANSI/IES TM-30-18 Colour Rendition Report* specification.

221

222 Other Parameters

223 ----------------

224 kwargs

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

226 See the documentation of the previously listed definitions.

227

228 Returns

229 -------

230 :class:`tuple`

231 Current figure and axes.

232

233 Examples

234 --------

235 >>> from colour import SDS_ILLUMINANTS

236 >>> from colour.quality import colour_fidelity_index_ANSIIESTM3018

237 >>> sd = SDS_ILLUMINANTS["FL2"]

238 >>> specification = colour_fidelity_index_ANSIIESTM3018(sd, True)

239 >>> plot_spectra_ANSIIESTM3018(specification)

240 ... # doctest: +ELLIPSIS

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

242 """

243

244 settings: Dict[str, Any] = dict(kwargs)

245

246 _figure, axes = artist(**settings)

247

248 Y_reference = sd_to_XYZ(specification.sd_reference)[1]

249 Y_test = sd_to_XYZ(specification.sd_test)[1]

250

251 with sdiv_mode():

252 reference_values = sdiv(specification.sd_reference.values, Y_reference)

253 test_values = sdiv(specification.sd_test.values, Y_test)

254

255 axes.plot(

256 specification.sd_reference.wavelengths,

257 reference_values,

258 "black",

259 label="Reference",

260 zorder=CONSTANTS_COLOUR_STYLE.zorder.midground_line,

261 )

262 axes.plot(

263 specification.sd_test.wavelengths,

264 test_values,

265 "#F05046",

266 label="Test",

267 zorder=CONSTANTS_COLOUR_STYLE.zorder.midground_line,

268 )

269 axes.tick_params(axis="y", which="both", length=0)

270 axes.set_yticklabels([])

271

272 settings = {

273 "axes": axes,

274 "legend": True,

275 "legend_columns": 2,

276 "x_label": "Wavelength (nm)",

277 "y_label": "Radiant Power\n(Equal Luminous Flux)",

278 }

279 settings.update(kwargs)

280

281 return render(**settings)

282

283

284 def plot_colour_vector_graphic(

285 specification: ColourQuality_Specification_ANSIIESTM3018, **kwargs: Any

286) -> Tuple[Figure, Axes]:

287 """

288 Plot *Color Vector Graphic* using the *ANSI/IES TM-30-18 Colour

289 Rendition Report*.

290

291 Parameters

292 ----------

293 specification

294 *ANSI/IES TM-30-18 Colour Rendition Report* specification.

295

296 Other Parameters

297 ----------------

298 kwargs

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

300 See the documentation of the previously listed definitions.

301

302 Returns

303 -------

304 :class:`tuple`

305 Current figure and axes.

306

307 Examples

308 --------

309 >>> from colour import SDS_ILLUMINANTS

310 >>> from colour.quality import colour_fidelity_index_ANSIIESTM3018

311 >>> sd = SDS_ILLUMINANTS["FL2"]

312 >>> specification = colour_fidelity_index_ANSIIESTM3018(sd, True)

313 >>> plot_colour_vector_graphic(specification)

314 ... # doctest: +ELLIPSIS

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

316 """

317

318 settings: Dict[str, Any] = dict(kwargs)

319 settings["show"] = False

320

321 # Background

322 background_image = read_image(

323 os.path.join(ROOT_RESOURCES_ANSIIESTM3018, "CVG_Background.jpg")

324 )

325 _figure, axes = plot_image(

326 background_image,

327 imshow_kwargs={"extent": [-1.5, 1.5, -1.5, 1.5]},

328 **settings,

329 )

330

331 # Lines dividing the hues in 16 equal parts along with bin numbers.

332 axes.plot(0, 0, "+", color="#A6A6A6")

333 for i in range(16):

334 angle = 2 * np.pi * i / 16

335 dx = np.cos(angle)

336 dy = np.sin(angle)

337 axes.plot(

338 (0.15 * dx, 1.5 * dx),

339 (0.15 * dy, 1.5 * dy),

340 "--",

341 color="#A6A6A6",

342 lw=0.75,

343 zorder=CONSTANTS_COLOUR_STYLE.zorder.midground_line,

344 )

345

346 angle = 2 * np.pi * (i + 0.5) / 16

347 axes.annotate(

348 str(i + 1),

349 color="#A6A6A6",

350 ha="center",

351 va="center",

352 xy=(1.41 * np.cos(angle), 1.41 * np.sin(angle)),

353 weight="bold",

354 size=9,

355 zorder=CONSTANTS_COLOUR_STYLE.zorder.midground_annotation,

356 )

357

358 # Circles.

359 circle = Circle(

360 (0, 0),

361 1,

362 color="black",

363 lw=1.25,

364 fill=False,

365 zorder=CONSTANTS_COLOUR_STYLE.zorder.midground_polygon,

366 )

367 axes.add_artist(circle)

368 for radius in [0.8, 0.9, 1.1, 1.2]:

369 circle = Circle(

370 (0, 0),

371 radius,

372 color="white",

373 lw=0.75,

374 fill=False,

375 zorder=CONSTANTS_COLOUR_STYLE.zorder.midground_polygon,

376 )

377 axes.add_artist(circle)

378

379 # -/+20% marks near the white circles.

380 props = {"ha": "right", "color": "white", "size": 7}

381 axes.annotate(

382 "-20%",

383 xy=(0, -0.8),

384 va="bottom",

385 zorder=CONSTANTS_COLOUR_STYLE.zorder.midground_annotation,

386 **props,

387 )

388 axes.annotate(

389 "+20%",

390 xy=(0, -1.2),

391 va="top",

392 zorder=CONSTANTS_COLOUR_STYLE.zorder.midground_annotation,

393 **props,

394 )

395

396 # Average "CAM02" h correlate for each bin, in radians.

397 average_hues = np.radians(

398 [

399 np.mean(

400 specification.colorimetry_data[1].JMh[specification.bins == j, 2],

401 )

402 for j in range(16)

403 ]

404 )

405 xy_reference = np.transpose(np.vstack([np.cos(average_hues), np.sin(average_hues)]))

406

407 # Arrow offsets as defined by the standard.

408 offsets = (

409 specification.averages_test - specification.averages_reference

410 ) / specification.average_norms[:, None]

411 xy_test = xy_reference + offsets

412

413 # Arrows.

414 for i in range(16):

415 axes.arrow(

416 xy_reference[i, 0],

417 xy_reference[i, 1],

418 offsets[i, 0],

419 offsets[i, 1],

420 length_includes_head=True,

421 width=0.005,

422 head_width=0.04,

423 linewidth=None,

424 color=_COLOURS_BIN_ARROW[i],

425 zorder=CONSTANTS_COLOUR_STYLE.zorder.midground_annotation,

426 )

427

428 # Red (test) gamut shape.

429 loop = np.append(xy_test, xy_test[0, None], axis=0)

430 axes.plot(

431 loop[:, 0],

432 loop[:, 1],

433 "-",

434 color="#F05046",

435 lw=2,

436 zorder=CONSTANTS_COLOUR_STYLE.zorder.midground_line,

437 )

438

439 def corner_label_and_text(label: str, text: str, ha: str, va: str) -> None:

440 """Draw a label and text in specified corner."""

441

442 x = -1.45 if ha == "left" else 1.45

443 y = 1.45 if va == "top" else -1.45

444 y_text = -15 if va == "top" else 15

445

446 axes.annotate(

447 text,

448 xy=(x, y),

449 color="black",

450 ha=ha,

451 va=va,

452 weight="bold",

453 size="larger",

454 zorder=CONSTANTS_COLOUR_STYLE.zorder.foreground_label,

455 )

456 axes.annotate(

457 label,

458 xy=(x, y),

459 color="black",

460 xytext=(0, y_text),

461 textcoords="offset points",

462 ha=ha,

463 va=va,

464 size="small",

465 zorder=CONSTANTS_COLOUR_STYLE.zorder.foreground_label,

466 )

467

468 corner_label_and_text("$R_f$", f"{specification.R_f:.0f}", "left", "top")

469 corner_label_and_text("$R_g$", f"{specification.R_g:.0f}", "right", "top")

470 corner_label_and_text("CCT", f"{specification.CCT:.0f} K", "left", "bottom")

471 corner_label_and_text("$D_{uv}$", f"{specification.D_uv:.4f}", "right", "bottom")

472

473 settings = {"show": True}

474 settings.update(kwargs)

475

476 return render(**settings)

477

478

479 def plot_16_bin_bars(

480 values: ArrayLike,

481 label_template: str,

482 x_ticker: bool = False,

483 label_orientation: Literal["Horizontal", "Vertical"] | str = "Vertical",

484 **kwargs: Any,

485) -> Tuple[Figure, Axes]:

486 """

487 Plot 16 bin bars for the specified values using *ANSI/IES TM-30-18 Colour

488 Rendition Report*.

489

490 Parameters

491 ----------

492 values

493 Values to generate the bin bars for.

494 label_template

495 Template to format the labels.

496 x_ticker

497 Whether to show the *X* axis ticker and the associated label.

498 label_orientation

499 Orientation of the labels.

500

501 Other Parameters

502 ----------------

503 kwargs

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

505 See the documentation of the previously listed definitions.

506

507 Returns

508 -------

509 :class:`tuple`

510 Current figure and axes.

511

512 Examples

513 --------

514 >>> plot_16_bin_bars(np.arange(16), "{0}")

515 ... # doctest: +ELLIPSIS

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

517 """

518

519 values = as_float_array(values)

520

521 label_orientation = validate_method(label_orientation, ("Horizontal", "Vertical"))

522

523 _figure, axes = artist(**kwargs)

524

525 bar_count = len(_COLOURS_BIN_BAR)

526 axes.bar(

527 np.arange(bar_count) + 1,

528 values,

529 color=_COLOURS_BIN_BAR,

530 width=1,

531 edgecolor="black",

532 linewidth=CONSTANTS_COLOUR_STYLE.geometry.short / 3,

533 zorder=CONSTANTS_COLOUR_STYLE.zorder.background_polygon,

534 )

535 axes.set_xlim(0.5, bar_count + 0.5)

536 if x_ticker:

537 axes.set_xticks(np.arange(1, bar_count + 1))

538 axes.set_xlabel("Hue-Angle Bin (j)")

539 else:

540 axes.set_xticks([])

541

542 label_orientation = label_orientation.lower()

543 value_max = np.max(values)

544 for i, value in enumerate(values):

545 if label_orientation == "vertical":

546 va, vo = (

547 ("bottom", value_max * 0.15)

548 if value > 0

549 else ("top", -value_max * 0.15)

550 )

551 axes.annotate(

552 label_template.format(value),

553 xy=(i + 1, value + vo),

554 rotation=90,

555 fontsize="xx-small-colour-science",

556 ha="center",

557 va=va,

558 zorder=CONSTANTS_COLOUR_STYLE.zorder.midground_label,

559 )

560 elif label_orientation == "horizontal":

561 va, vo = (

562 ("bottom", value_max * 0.025)

563 if value < 90

564 else ("top", -value_max * 0.025)

565 )

566 axes.annotate(

567 label_template.format(value),

568 xy=(i + 1, value + vo),

569 fontsize="xx-small-colour-science",

570 ha="center",

571 va=va,

572 zorder=CONSTANTS_COLOUR_STYLE.zorder.midground_label,

573 )

574

575 return render(**kwargs)

576

577

578 def plot_local_chroma_shifts(

579 specification: ColourQuality_Specification_ANSIIESTM3018,

580 x_ticker: bool = False,

581 **kwargs: Any,

582) -> Tuple[Figure, Axes]:

583 """

584 Plot the local chroma shifts using the *ANSI/IES TM-30-18 Colour

585 Rendition Report*.

586

587 Parameters

588 ----------

589 specification

590 *ANSI/IES TM-30-18 Colour Rendition Report* specification.

591 x_ticker

592 Whether to show the *X* axis ticker and the associated label.

593

594 Other Parameters

595 ----------------

596 kwargs

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

598 See the documentation of the previously listed definitions.

599

600 Returns

601 -------

602 :class:`tuple`

603 Current figure and axes.

604

605 Examples

606 --------

607 >>> from colour import SDS_ILLUMINANTS

608 >>> from colour.quality import colour_fidelity_index_ANSIIESTM3018

609 >>> sd = SDS_ILLUMINANTS["FL2"]

610 >>> specification = colour_fidelity_index_ANSIIESTM3018(sd, True)

611 >>> plot_local_chroma_shifts(specification)

612 ... # doctest: +ELLIPSIS

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

614 """

615

616 settings: Dict[str, Any] = dict(kwargs)

617 settings["show"] = False

618

619 _figure, axes = plot_16_bin_bars(

620 specification.R_cs, "{0:.0f}%", x_ticker, **settings

621 )

622

623 axes.set_ylim(-40, 40)

624 axes.set_ylabel("Local Chroma Shift ($R_{cs,hj}$)")

625

626 ticks = np.arange(-40, 41, 10)

627 axes.set_yticks(ticks)

628 axes.set_yticklabels([f"{value}%" for value in ticks])

629

630 settings = {"show": True}

631 settings.update(kwargs)

632

633 return render(**settings)

634

635

636 def plot_local_hue_shifts(

637 specification: ColourQuality_Specification_ANSIIESTM3018,

638 x_ticker: bool = False,

639 **kwargs: Any,

640) -> Tuple[Figure, Axes]:

641 """

642 Plot the local hue shifts using *ANSI/IES TM-30-18 Colour Rendition

643 Report*.

644

645 Parameters

646 ----------

647 specification

648 *ANSI/IES TM-30-18 Colour Rendition Report* specification.

649 x_ticker

650 Whether to show the *X* axis ticker and the associated label.

651

652 Other Parameters

653 ----------------

654 kwargs

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

656 See the documentation of the previously listed definitions.

657

658 Returns

659 -------

660 :class:`tuple`

661 Current figure and axes.

662

663 Examples

664 --------

665 >>> from colour import SDS_ILLUMINANTS

666 >>> from colour.quality import colour_fidelity_index_ANSIIESTM3018

667 >>> sd = SDS_ILLUMINANTS["FL2"]

668 >>> specification = colour_fidelity_index_ANSIIESTM3018(sd, True)

669 >>> plot_local_hue_shifts(specification)

670 ... # doctest: +ELLIPSIS

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

672 """

673

674 settings: Dict[str, Any] = dict(kwargs)

675 settings["show"] = False

676

677 _figure, axes = plot_16_bin_bars(

678 specification.R_hs, "{0:.2f}", x_ticker, **settings

679 )

680 axes.set_ylim(-0.5, 0.5)

681 axes.set_yticks(np.arange(-0.5, 0.51, 0.1))

682 axes.set_ylabel("Local Hue Shift ($R_{hs,hj}$)")

683

684 settings = {"show": True}

685 settings.update(kwargs)

686

687 return render(**settings)

688

689

690 def plot_local_colour_fidelities(

691 specification: ColourQuality_Specification_ANSIIESTM3018,

692 x_ticker: bool = False,

693 **kwargs: Any,

694) -> Tuple[Figure, Axes]:

695 """

696 Plot local colour fidelities using the *ANSI/IES TM-30-18 Colour

697 Rendition Report* specification.

698

699 Parameters

700 ----------

701 specification

702 *ANSI/IES TM-30-18 Colour Rendition Report* specification.

703 x_ticker

704 Whether to display the *X* axis ticker and its associated label.

705

706 Other Parameters

707 ----------------

708 kwargs

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

710 See the documentation of the previously listed definitions.

711

712 Returns

713 -------

714 :class:`tuple`

715 Current figure and axes.

716

717 Examples

718 --------

719 >>> from colour import SDS_ILLUMINANTS

720 >>> from colour.quality import colour_fidelity_index_ANSIIESTM3018

721 >>> sd = SDS_ILLUMINANTS["FL2"]

722 >>> specification = colour_fidelity_index_ANSIIESTM3018(sd, True)

723 >>> plot_local_colour_fidelities(specification)

724 ... # doctest: +ELLIPSIS

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

726 """

727

728 settings: Dict[str, Any] = dict(kwargs)

729 settings["show"] = False

730

731 _figure, axes = plot_16_bin_bars(

732 specification.R_fs, "{0:.0f}", x_ticker, "Horizontal", **settings

733 )

734 axes.set_ylim(0, 100)

735 axes.set_yticks(np.arange(0, 101, 10))

736 axes.set_ylabel("Local Color Fidelity ($R_{f,hj}$)")

737

738 settings = {"show": True}

739 settings.update(kwargs)

740

741 return render(**settings)

742

743

744 def plot_colour_fidelity_indexes(

745 specification: ColourQuality_Specification_ANSIIESTM3018, **kwargs: Any

746) -> Tuple[Figure, Axes]:

747 """

748 Plot colour fidelity indexes using *ANSI/IES TM-30-18 Colour Rendition

749 Report*.

750

751 Parameters

752 ----------

753 specification

754 *ANSI/IES TM-30-18 Colour Rendition Report* specification.

755

756 Other Parameters

757 ----------------

758 kwargs

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

760 See the documentation of the previously listed definitions.

761

762 Returns

763 -------

764 :class:`tuple`

765 Current figure and axes.

766

767 Examples

768 --------

769 >>> from colour import SDS_ILLUMINANTS

770 >>> from colour.quality import colour_fidelity_index_ANSIIESTM3018

771 >>> sd = SDS_ILLUMINANTS["FL2"]

772 >>> specification = colour_fidelity_index_ANSIIESTM3018(sd, True)

773 >>> plot_colour_fidelity_indexes(specification)

774 ... # doctest: +ELLIPSIS

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

776 """

777

778 _figure, axes = artist(**kwargs)

779

780 bar_count = len(_COLOURS_TCS_BAR)

781 axes.bar(

782 np.arange(bar_count) + 1,

783 specification.R_s,

784 color=_COLOURS_TCS_BAR,

785 width=1,

786 edgecolor="black",

787 linewidth=CONSTANTS_COLOUR_STYLE.geometry.short / 3,

788 zorder=CONSTANTS_COLOUR_STYLE.zorder.background_polygon,

789 )

790 axes.set_xlim(0.5, bar_count + 0.5)

791 axes.set_ylim(0, 100)

792 axes.set_yticks(np.arange(0, 110, 10))

793 axes.set_ylabel("Color Sample Fidelity ($R_{f,CESi}$)")

794

795 ticks = list(range(1, bar_count + 1, 1))

796 axes.set_xticks(ticks)

797

798 labels = [f"CES{i:02d}" if i % 3 == 1 else "" for i in range(1, bar_count + 1)]

799 axes.set_xticklabels(labels, rotation=90)

800

801 return render(**kwargs)