Set custom background colors with collections.QuadMesh.set_facecolors()

I want to set the background color of some fields of a seaborn heatmap AFTER it has been created (customising some colours based on their position).

I have adapted some code from here for drawing custom confusion matrixes: pretty-print-confusion-matrix

The approach accesses the facecolor values via the matplotlib collections.QuadMesh object. When I modify the values (I have tried different ways) the changes get reflected in the data structures (numpy ndarray), but the facecolors shown when displaying or exporting the plot stay unchanged.

What am I missing here?

Remark: This approach actually used to work for me before updating the dependencies.

This is a (non-)working example:

import numpy as np
import numpy.testing
import pandas as pd
import seaborn as sns
from matplotlib.collections import QuadMesh
import matplotlib.pyplot as plt

CUSTOM_BACKGROUND = np.array([0.35, 0.8, 0.55, 1.0])

df = pd.DataFrame(data={'col1': [1, 1], 'col2': [2,2]})
matrix_size = df.shape[0]
ax = sns.heatmap(df)
quadmesh = ax.findobj(QuadMesh)[0]
facecolors_old = quadmesh.get_facecolors()
facecolors_new = facecolors_old.copy()
facecolors_new[1] = CUSTOM_BACKGROUND

# Not working:
quadmesh.set_facecolors(facecolors_new)
# quadmesh._original_facecolor = facecolors_new
# facecolors_old[1] = CUSTOM_BACKGROUND
# quadmesh._facecolors[1] = CUSTOM_BACKGROUND

# works:
# quadmesh.set_facecolors('none')

# tests pass:
facecolors_actual = quadmesh.get_facecolors()
numpy.testing.assert_array_equal(facecolors_new, facecolors_actual)
facecolors_actual = quadmesh._facecolors
numpy.testing.assert_array_equal(facecolors_new, facecolors_actual)
# NOTE: inspecting `quadmesh._facecolors` however shows the old values!

# shows old colors
plt.show()

Dependency versions:

[tool.poetry.dependencies]
python = "^3.8"
pandas = "^1.2"
seaborn = "^0.11.2"

Related stackoverflow issue: How can i set custom background colors of seaborn heatmap fields?

If set_facecolrs isn’t working is open an issue. Preferably with a very minimal non-seaborn example.

I noticed that quadmesh.get_facecolors() yields a list of RGBA colours (2D array with 4 values per cell) after creating the quadmesh via axes= seaborn.heatmap(...). However modifying this RGBA color array doesn’t change the resulting plot.

In contrast when creating the quadmesh via pcolormesh, quadmesh.get_facecolors() yields a 2D array of scalar colors for being mapped with a cmap (one value per cell). Hence I worked around the problem by creating my own scalar color array and calling axes.pcolormesh(...) with it. Using the same axes object this will simply overwrite the old color values.

Still it would be nice to have the full flexibility of changing RGBA colors not just scalar colormap values.
Unfortunately I did not have the time to investigate further to get to the root of this problem.
Please let me know if you have any idea.

Hi @jannikmi !

Could you share the workable solution with you example code? Thanks so much!

Here is a minimal working example. As I said, this does not offer the full functionality i was originally looking for. More a temporary workaround

def set_background_colors(value_data_frame, axes) -> QuadMesh:
    # NOTE: scale the heatmap color intensities in the different regions independently
    intensities = value_data_frame.values.astype(float)
    intensities[1, 0] = 0.0
    ...
    assert np.max(intensities) > 0.0, "normalisation assumes positive values"
    assert np.max(intensities) <= 1.0
    return axes.pcolormesh(intensities, cmap=CMAP, vmax=1.0)