如何在不更改覆盖 ax.text() 句柄位置的情况下在 matplotlib ax.imshow() 中使用 `extent`?

How to use `extent` in matplotlib ax.imshow() without changing the positions of the overlayed ax.text() handles?

我正在尝试注释热图。 matplotlib docs present an example, which suggests creating a helper function to format the annotations. I feel there must be a simpler way to do what I want. I can annotate inside the boxes of the heatmap, but these texts change position when editing the 。我的问题是如何在 ax.imshow(...) 中使用 extent,同时还使用 ax.text(...) 来注释正确的位置。下面是一个例子:

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import Normalize

def get_manhattan_distance_matrix(coordinates):
    shape = (coordinates.shape[0], 1, coordinates.shape[1])
    ct = coordinates.reshape(shape)
    displacement = coordinates - ct
    return np.sum(np.abs(displacement), axis=-1)

x = np.arange(11)[::-1]
y = x.copy()
coordinates = np.array([x, y]).T
distance_matrix = get_manhattan_distance_matrix(coordinates)

# print("\n .. {} COORDINATES:\n{}\n".format(coordinates.shape, coordinates))
# print("\n .. {} DISTANCE MATRIX:\n{}\n".format(distance_matrix.shape, distance_matrix))

norm = Normalize(vmin=np.min(distance_matrix), vmax=np.max(distance_matrix))

这里是修改extent的值的地方。

extent = (np.min(x), np.max(x), np.min(y), np.max(y))
# extent = None

根据matplotlib docs,默认的extentNone

fig, ax = plt.subplots()
handle = ax.imshow(distance_matrix, cmap='plasma', norm=norm, interpolation='nearest', origin='upper', extent=extent)

kws = dict(ha='center', va='center', color='gray', weight='semibold', fontsize=5)
for i in range(len(distance_matrix)):
    for j in range(len(distance_matrix[i])):
        if i == j:
            ax.text(j, i, '', **kws)
        else:
            ax.text(j, i, distance_matrix[i, j], **kws)

plt.show()
plt.close(fig)

一个人可以通过修改extent生成两个数字——简单地取消注释行的注释并注释未注释行。两图如下:

可以看出,通过设置extent,像素位置发生变化,进而改变ax.text(...)手柄的位置。有没有一个简单的解决方案来解决这个问题 - 即设置任意 extent 并且文本句柄仍然在每个框中居中?

extent=None时,x和y的有效范围都是从-0.5到10.5。所以中心位于整数位置。将范围设置为 0 到 10 不与像素对齐。您必须乘以 10/11 才能使它们正确。

最好的方法是设置 extent = (np.min(x)-0.5, np.max(x)+0.5, np.min(y)-0.5, np.max(y)+0.5) 以使中心回到整数位置。

另请注意,默认情况下图像是从顶部开始显示的,并且 y 轴是反转的。如果更改范围,要使图像正立,则需要 ax.imshow(..., origin='lower')。 (0,0 像素应该是示例图中的蓝色像素。)

要将文本放在像素的中心,您可以将水平索引加 0.5,除以以像素为单位的宽度,然后乘以 x 轴的差值。以及 y 轴的类似计算。为了获得更好的可读性,可以使文本颜色依赖于像素颜色。

# ... 

extent = (np.min(x), np.max(x), np.min(y), np.max(y))
x0, x1, y0, y1 = extent
fig, ax = plt.subplots()
handle = ax.imshow(distance_matrix, cmap='plasma', norm=norm, interpolation='nearest', origin='lower', extent=extent)

kws = dict(ha='center', va='center', weight='semibold', fontsize=5)
height = len(distance_matrix)
width = len(distance_matrix[0])
for i in range(height):
    for j in range(width):
        if i != j:
            val = distance_matrix[i, j]
            ax.text(x0 + (j + 0.5) / width * (x1 - x0), y0 + (i + 0.5) / height * (y1 - y0),
                    f'{val}\n{i},{j}', color='white' if norm(val) < 0.6 else 'black', **kws)
plt.show()