Coverage for plotting/tm3018/components.py: 79%
146 statements
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-16 22:49 +1300
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-16 22:49 +1300
1"""
2ANSI/IES TM-30-18 Colour Rendition Report Components
3====================================================
5Define the *ANSI/IES TM-30-18 Colour Rendition Report* components plotting
6objects for comprehensive colour rendition evaluation and visualization.
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"""
17from __future__ import annotations
19import os
20import typing
22import numpy as np
24if typing.TYPE_CHECKING:
25 from matplotlib.axes import Axes
26 from matplotlib.figure import Figure
28from matplotlib.patches import Circle
30from colour.algebra import sdiv, sdiv_mode
31from colour.colorimetry import sd_to_XYZ
33if typing.TYPE_CHECKING:
34 from colour.hints import Any, ArrayLike, Dict, Literal, Tuple
35 from colour.quality import ColourQuality_Specification_ANSIIESTM3018
37from colour.io import read_image
38from colour.plotting import (
39 CONSTANTS_COLOUR_STYLE,
40 artist,
41 override_style,
42 plot_image,
43 render,
44)
45from colour.utilities import as_float_array, validate_method
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"
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]
65ROOT_RESOURCES_ANSIIESTM3018: str = os.path.join(os.path.dirname(__file__), "resources")
66"""Resources directory."""
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]
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]
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]
209@override_style()
210def 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*.
217 Parameters
218 ----------
219 specification
220 *ANSI/IES TM-30-18 Colour Rendition Report* specification.
222 Other Parameters
223 ----------------
224 kwargs
225 {:func:`colour.plotting.artist`, :func:`colour.plotting.render`},
226 See the documentation of the previously listed definitions.
228 Returns
229 -------
230 :class:`tuple`
231 Current figure and axes.
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 """
244 settings: Dict[str, Any] = dict(kwargs)
246 _figure, axes = artist(**settings)
248 Y_reference = sd_to_XYZ(specification.sd_reference)[1]
249 Y_test = sd_to_XYZ(specification.sd_test)[1]
251 with sdiv_mode():
252 reference_values = sdiv(specification.sd_reference.values, Y_reference)
253 test_values = sdiv(specification.sd_test.values, Y_test)
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([])
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)
281 return render(**settings)
284def 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*.
291 Parameters
292 ----------
293 specification
294 *ANSI/IES TM-30-18 Colour Rendition Report* specification.
296 Other Parameters
297 ----------------
298 kwargs
299 {:func:`colour.plotting.artist`, :func:`colour.plotting.render`},
300 See the documentation of the previously listed definitions.
302 Returns
303 -------
304 :class:`tuple`
305 Current figure and axes.
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 """
318 settings: Dict[str, Any] = dict(kwargs)
319 settings["show"] = False
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 )
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 )
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 )
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)
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 )
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)]))
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
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 )
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 )
439 def corner_label_and_text(label: str, text: str, ha: str, va: str) -> None:
440 """Draw a label and text in specified corner."""
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
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 )
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")
473 settings = {"show": True}
474 settings.update(kwargs)
476 return render(**settings)
479def 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*.
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.
501 Other Parameters
502 ----------------
503 kwargs
504 {:func:`colour.plotting.artist`, :func:`colour.plotting.render`},
505 See the documentation of the previously listed definitions.
507 Returns
508 -------
509 :class:`tuple`
510 Current figure and axes.
512 Examples
513 --------
514 >>> plot_16_bin_bars(np.arange(16), "{0}")
515 ... # doctest: +ELLIPSIS
516 (<Figure size ... with 1 Axes>, <...Axes...>)
517 """
519 values = as_float_array(values)
521 label_orientation = validate_method(label_orientation, ("Horizontal", "Vertical"))
523 _figure, axes = artist(**kwargs)
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([])
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 )
575 return render(**kwargs)
578def 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*.
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.
594 Other Parameters
595 ----------------
596 kwargs
597 {:func:`colour.plotting.artist`, :func:`colour.plotting.render`},
598 See the documentation of the previously listed definitions.
600 Returns
601 -------
602 :class:`tuple`
603 Current figure and axes.
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 """
616 settings: Dict[str, Any] = dict(kwargs)
617 settings["show"] = False
619 _figure, axes = plot_16_bin_bars(
620 specification.R_cs, "{0:.0f}%", x_ticker, **settings
621 )
623 axes.set_ylim(-40, 40)
624 axes.set_ylabel("Local Chroma Shift ($R_{cs,hj}$)")
626 ticks = np.arange(-40, 41, 10)
627 axes.set_yticks(ticks)
628 axes.set_yticklabels([f"{value}%" for value in ticks])
630 settings = {"show": True}
631 settings.update(kwargs)
633 return render(**settings)
636def 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*.
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.
652 Other Parameters
653 ----------------
654 kwargs
655 {:func:`colour.plotting.artist`, :func:`colour.plotting.render`},
656 See the documentation of the previously listed definitions.
658 Returns
659 -------
660 :class:`tuple`
661 Current figure and axes.
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 """
674 settings: Dict[str, Any] = dict(kwargs)
675 settings["show"] = False
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}$)")
684 settings = {"show": True}
685 settings.update(kwargs)
687 return render(**settings)
690def 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.
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.
706 Other Parameters
707 ----------------
708 kwargs
709 {:func:`colour.plotting.artist`, :func:`colour.plotting.render`},
710 See the documentation of the previously listed definitions.
712 Returns
713 -------
714 :class:`tuple`
715 Current figure and axes.
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 """
728 settings: Dict[str, Any] = dict(kwargs)
729 settings["show"] = False
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}$)")
738 settings = {"show": True}
739 settings.update(kwargs)
741 return render(**settings)
744def 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*.
751 Parameters
752 ----------
753 specification
754 *ANSI/IES TM-30-18 Colour Rendition Report* specification.
756 Other Parameters
757 ----------------
758 kwargs
759 {:func:`colour.plotting.artist`, :func:`colour.plotting.render`},
760 See the documentation of the previously listed definitions.
762 Returns
763 -------
764 :class:`tuple`
765 Current figure and axes.
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 """
778 _figure, axes = artist(**kwargs)
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}$)")
795 ticks = list(range(1, bar_count + 1, 1))
796 axes.set_xticks(ticks)
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)
801 return render(**kwargs)