# How would I construct the CIE Yxy diagram? [closed]

It seems most references showing a CIE Yxy diagram can be reduced to very few (one?) sources. If I wanted to construct the diagram myself from some published measurement values, how would I do it?

I'm assuming there is some parametric function `f(wavelength)` giving a vector `(x, y)` for each `f`. But how would that function `f` look like?

Actually I wanted to construct a CIE Luv* diagram, but when knowing the `(x, y)` vectors for CIE Yxy, I could easily convert those to `(u*, v*)`, right?

Update (2019-09-03):

Drawing the outline (spectral line) is one part of the problem. Another part of the problem is filling in the correct colors (I'm aware that no existing computer display device can actually present them): Is it OK to put the C white at its position and use Gouraud-Shaded Triangle Meshes to interpolate the area between two points on the spectral line and that white point (Example Image connecting 470nm with 500nm and with Illuminant C)? That seems to be OK.

In case it's OK, how would I fill the "non-spectral triangle" outlines by both ends of the spectral line and the white-point? Trying that I found out that the ends of the spectral line practically represent black, so the triangle came out much more black than purple, and opposed to all diagrams I've seen before, there's a discontinuity where the non-spectral colors are (Example Image)

As an experiment, I cut off the end of the spectrum to 435-645nm, and the transition from spectral to non-spectral colors is rather smooth then. (Example Image)

However when you use normalized data the dark colors become bright, and the brighter colors become darker. Effectively I realized that the "white point" actually is a gray point (several diagrams seem to have that wrong), and it seems that Illuminant C is the correct one, too , so this is my final version so far.

• For each wavelength, get the CIEXYZ values from a table, Add them together and divide X and Y by the sum which yields x and y. Plot the point. Do the same for each wavelength. Connect the dots. Possible duplicate of photo.stackexchange.com/questions/53510/… – doug Jun 3 at 14:36
• I'm voting to close this question as off-topic because as presented it has nothing to do with actually taking a photograph. – Michael C Jun 3 at 17:13
• @Michael C: Please suggest where to ask the question: There is no generic "color" or "colormetry" site, so this is the site where most questions about CIE Yxy diagrams had been asked before. Also many questions are not about fundamentals, but very specific (individual) problems. Why not allow a generic question? I think any answer to this question will give significant insights to what the CIE Yxy diagram means... – U. Windl Jun 3 at 22:19
• @doug: So I would need the XYZ CMFs (not the RGB CMFs) to start with? Those from "2-deg coordinates from 2-deg XYZ CMFs" at cvrl.org/ciexyzpr.htm? – U. Windl Jun 3 at 22:30
• The 2 degree, 1931 chromaticity coordinates tables at your link have already been normalized. That is the XYZ values add up to 1. As a consequence, the XY values correspond to the xy coordinates of the classic, horseshoe CIE Chromaticity graph. Just plot the X and Y as dots for each wavelength and connect the dots. Then draw a straight line between the lower left and lower right points (violet to magenta ro red) which is just mixtures of the lowest and highest wavelengths. – doug Jun 3 at 22:47

The conversion path from CIE xyY to a given RGB colourspace, e.g. sRGB, is typically as follows:

CIE xyY --> CIE XYZ --> RGB

With that in mind, generating the diagram can be done by generating a regularly spaced 2D grid of samples in domain [0, 1] and apply the conversion path to them.

As you found out, no display device is capable of representing the visible spectrum, so you will be limited by its colorimetry boundaries.

Colour implements the three common Chromaticity Diagrams:

The Python code for drawing the colours and bounding them with the spectral locus is as follows:

``````@override_style()
def plot_chromaticity_diagram_colours(
samples=256,
diagram_opacity=1.0,
diagram_clipping_path=None,
cmfs='CIE 1931 2 Degree Standard Observer',
method='CIE 1931',
**kwargs):
"""
Plots the *Chromaticity Diagram* colours according to given method.

Parameters
----------
samples : numeric, optional
Samples count on one axis.
diagram_opacity : numeric, optional
Opacity of the *Chromaticity Diagram* colours.
diagram_clipping_path : array_like, optional
Path of points used to clip the *Chromaticity Diagram* colours.
cmfs : unicode, optional
Standard observer colour matching functions used for
*Chromaticity Diagram* bounds.
method : unicode, optional
**{'CIE 1931', 'CIE 1960 UCS', 'CIE 1976 UCS'}**,
*Chromaticity Diagram* method.

Other Parameters
----------------
\\**kwargs : dict, optional
{:func:`colour.plotting.artist`, :func:`colour.plotting.render`},
Please refer to the documentation of the previously listed definitions.

Returns
-------
tuple
Current figure and axes.

Examples
--------
>>> plot_chromaticity_diagram_colours()  # doctest: +SKIP

.. image:: ../_static/Plotting_Plot_Chromaticity_Diagram_Colours.png
:align: center
:alt: plot_chromaticity_diagram_colours
"""

settings = {'uniform': True}
settings.update(kwargs)

_figure, axes = artist(**settings)

method = method.upper()

cmfs = first_item(filter_cmfs(cmfs).values())

illuminant = COLOUR_STYLE_CONSTANTS.colour.colourspace.whitepoint

ii, jj = np.meshgrid(
np.linspace(0, 1, samples), np.linspace(1, 0, samples))
ij = tstack([ii, jj])

# NOTE: Various values in the grid have potential to generate
# zero-divisions, they could be avoided by perturbing the grid, e.g. adding
# a small epsilon. It was decided instead to disable warnings.
with suppress_warnings(python_warnings=True):
if method == 'CIE 1931':
XYZ = xy_to_XYZ(ij)
spectral_locus = XYZ_to_xy(cmfs.values, illuminant)
elif method == 'CIE 1960 UCS':
XYZ = xy_to_XYZ(UCS_uv_to_xy(ij))
spectral_locus = UCS_to_uv(XYZ_to_UCS(cmfs.values))
elif method == 'CIE 1976 UCS':
XYZ = xy_to_XYZ(Luv_uv_to_xy(ij))
spectral_locus = Luv_to_uv(
XYZ_to_Luv(cmfs.values, illuminant), illuminant)
else:
raise ValueError(
'Invalid method: "{0}", must be one of '
'{{\'CIE 1931\', \'CIE 1960 UCS\', \'CIE 1976 UCS\'}}'.format(
method))

RGB = normalise_maximum(
XYZ_to_plotting_colourspace(XYZ, illuminant), axis=-1)

polygon = Polygon(
spectral_locus
if diagram_clipping_path is None else diagram_clipping_path,
facecolor='none',
edgecolor='none')
# Preventing bounding box related issues as per
# https://github.com/matplotlib/matplotlib/issues/10529
image = axes.imshow(
RGB,
interpolation='bilinear',
extent=(0, 1, 0, 1),
clip_path=None,
alpha=diagram_opacity)
image.set_clip_path(polygon)

settings = {'axes': axes}
settings.update(kwargs)

return render(**kwargs)
``````

It is obviously dependent on a lot of Colour functions but it should be enough to illustrate the algorithm.

• I found out that I actually use different colors as the rest (it seems): If you look at my plot, it even seems to look a bit three-dimensional, like viewing the RGB-cube from the white corner. That is, because I projected the XYZ colors to the `X + Y + Z = 1` plane instead of using the colors in that plane. – U. Windl Jun 9 at 21:39
• Note that we are normalising the RGB colours on the maximum value of each RGB triplet but there are no defined rules on how the normalisation should be done. Colour scientists tend to prefer to not use colours at all in chromaticity diagrams because they can be misleading. – Kel Solaar Jun 10 at 1:41
• Actually after all my experience I'd say all of the above three diagrams are wrong regarding the white point: It is white in the diagrams, but it should be gray (as bright as the neighbors). Maybe compare with i.stack.imgur.com/n5x4w.png. – U. Windl Sep 2 at 22:35
• No they are correct, the whitepoint which has unit Luminance is sitting at the top of a given RGB colourspace gamut given in the CIE xyY (or any colour model for that matter). Here is a 3D view of it to convince you, the summit of the volume is the whitepoint: imgur.com/a/2iAb3l4, rotate the camera to be normal to the x,y plane and you will see the sRGB gamut in the CIE 1931 Chromaticity Diagram. – Kel Solaar Sep 3 at 7:54
• The CIE does not say it has to be a specific plane. CIE xyY is a projective transformation of CIE XYZ, whether you decide to take the colours at the CIE XYZ simplex for the diagram or something else is a matter of taste. The advantage of using the maximum Luminance, i.e. conversion from a grid of chromaticity coordinates, is that is obvious where the whitepoint is located in the diagram, it becomes important when you are studying other whitepoints. – Kel Solaar Sep 4 at 7:52

You can find the coordinates to plot for all of the CIE Standards on the Web at the Color & Vision Research Laboratory
They are available as CSV (Comma separated variables) suitable for Excel or other applications.

Good luck

• Actually I've been there (Color & Vision Research Laboratory) weeks before asking this question. Which data exactly are you referring to, or do you just guess that data might be there? – U. Windl Jun 3 at 22:14
• @U.Windl Navigate to the site, locate the plot you want, and click on the blue encircled "i" to reveal (in a drop-down page) the formulas to construct the plot data for each wavelength you wish. – Stan Jun 3 at 22:47
• Unfortunately the site has at least two issues: 1) Navigating does not change the URL displayed (due to using frames). 2) The formula display seems to work correctly only with Microsoft IE: With Firefox I need to scroll to see the formulas. – U. Windl Jun 3 at 23:41