Как использовать Matplotlib imshow () с аннотированными прямоугольниками для создания помеченного 2-мерного графика - PullRequest
0 голосов
/ 27 января 2020

Я пытаюсь создать двумерный график разреженного массива с imshow() и использую plt.text() для наложения его на текстовые поля. Я придумал дополнительную опцию, используя plt.scatter(). Во втором случае цветные плитки и текстовые поля слишком малы и не могут быть увеличены. В первом случае размер цветных плиток, создаваемых imshow(), и текстовых полей имеют декогерентный размер, и график выглядит нормально только при использовании функции масштабирования диалогового окна. Это иллюстрируется приведенным ниже кодом.

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

#https://matplotlib.org/gallery/images_contours_and_fields/image_annotated_heatmap.html

P=[1, 4, 11, 18, 20, 39, 40, 41, 41, 71, 71, 71, 71, 71, 71, 72, 72, 72, 72, 72, 72, 73, 73, 73, 74, 74, 74, 74, 74, 74, 75, 75, 75, 71]
N=[2, 3, 11, 19, 25, 49, 48, 50, 54, 101, 102, 103, 103, 106, 106, 100, 103, 106, 106, 107, 109, 105, 106, 109, 104, 107, 109, 110, 111, 112, 108, 109, 109, 101]
B=np.random.rand(34)

# crate the array to use with imshow()
A=np.zeros((max(N)+1,max(N)+1))
for i,j,k in zip(N,P,B):
     A[i,j]=k

def plot_map(N,P,A):     
    fig, ax = plt.subplots()     
    plt.imshow(A,norm=colors.LogNorm(),cmap='jet',origin='lower')
    plt.colorbar()
    for n,p in zip(N,P):
            ax.text(p,n, "\n%s\n%s\n%5.1E"%(p,n,A[n,p]),
                ha="center", va="center",
            bbox=dict(fc="none",boxstyle = "square"))
    plt.tight_layout()
    plt.show()

# call the plot function
plot_map(N,P,A)    

# attempt tow using plt.scatter() 
plt.scatter(N,P,c=B,marker='s',s=70,norm=colors.LogNorm(),cmap='jet')
for n,p in zip(N,P):
         plt.text(n,p, "\n%s\n%s"%(p,n), size=3,
             va="center", ha="center", multialignment="left",
             bbox=dict(fc="none",boxstyle = "square"))
plt.colorbar()
plt.show()

В идеале я хотел бы создать что-то вроде этого

То, что производят мои подпрограммы, выглядит не очень хорошо, и цветные плитки, и ящики с комментариями прерываются.
Поэтому я был бы признателен за вашу помощь.

1 Ответ

2 голосов
/ 27 января 2020

В следующем подходе для отображения информации на экране используется mplcursors, а также сохраняется файл изображения, который можно распечатать.

При печати на бумаге формата A4 размер каждого маленького квадрата будет около 2x2 мм, поэтому вам могут пригодиться хороший принтер и зеркало. Возможно, вы захотите поэкспериментировать с размером шрифта.

На экране mplcursors отображает всплывающую аннотацию при нажатии на маленький квадрат. При увеличении требуется двойной щелчок, чтобы не мешать работе с масштабированием. mplcursors также имеет режим «зависания», но при увеличении масштаба информация не отображается.

Некоторый код, демонстрирующий, как это может работать:

import matplotlib.pyplot as plt
import matplotlib.colors as colors
import mplcursors
import numpy as np

P = [1, 4, 11, 18, 20, 39, 40, 41, 41, 71, 71, 71, 71, 71, 71, 72, 72, 72, 72, 72, 72, 73, 73, 73, 74, 74, 74, 74, 74, 74, 75, 75, 75, 71]
N = [2, 3, 11, 19, 25, 49, 48, 50, 54, 101, 102, 103, 103, 106, 106, 100, 103, 106, 106, 107, 109, 105, 106, 109, 104,  107, 109, 110, 111, 112, 108, 109, 109, 101]
B = np.random.rand(34)

# create the array to use with imshow()
A = np.zeros((max(N) + 1, max(N) + 1))
for i, j, k in zip(N, P, B):
    A[i, j] = k

fig, ax = plt.subplots(figsize=(21, 15))
img = ax.imshow(A, norm=colors.LogNorm(), cmap='jet', origin='lower')
plt.colorbar(img)
for n, p in zip(N, P):
    plt.text(p, n, "%s\n%s\n%5.1E"%(p,n,A[n,p]), size=2,
             va="center", ha="center", multialignment="left")

cursor = mplcursors.cursor(img, hover=False)
@cursor.connect("add")
def on_add(sel):
    i,j = sel.target.index
    if A[i][j] == 0:
        sel.annotation.set_visible(False)
    else:
        sel.annotation.set_text(f'P: {j}\nN: {i}\n{A[i][j]:.3f}')

plt.tight_layout()
plt.savefig('test.png', dpi=300)
plt.show()

Слева показано, как он будет выглядеть на экране при увеличении и двойном щелчке по квадрату. Справа, как будет выглядеть изображение для печати при увеличении.

resulting plots

Чтобы получить текст, который увеличивается при увеличении, TextPath необходимо, как объяснено в этом посте . Поскольку TextPath на самом деле не работает с несколькими строками и выравниваниями, код вычисляет позиции. Кроме того, в зависимости от цвета поля текст легче читать, когда он белый. Вам нужно проверить, какие значения являются хорошими срезами в вашей ситуации и цветовой карте.

Чтобы справиться с пустым пространством, вы можете увеличить данные до трех мест. Приведенный ниже код создает подзаговор для каждой из этих областей.

import matplotlib.pyplot as plt
import matplotlib.colors as colors
from matplotlib.textpath import TextPath
from matplotlib.patches import PathPatch
from matplotlib.ticker import MaxNLocator
import numpy as np

P = [1, 4, 11, 18, 20, 39, 40, 41, 41, 71, 71, 71, 71, 71, 71, 72, 72, 72, 72, 72, 72, 73, 73, 73, 74, 74, 74, 74, 74, 74, 75, 75, 75, 71]
N = [2, 3, 11, 19, 25, 49, 48, 50, 54, 101, 102, 103, 103, 106, 106, 100, 103, 106, 106, 107, 109, 105, 106, 109, 104,  107, 109, 110, 111, 112, 108, 109, 109, 101]
B = np.random.rand(34)

# create the array to use with imshow()
A = np.zeros((max(N) + 1, max(N) + 1))
for i, j, k in zip(N, P, B):
    A[i, j] = k

plot_limits = [[[0, 19], [1, 20]],
               [[38, 42], [47 - 1, 55 + 2]],  # second subplot with higher y-range to better fit with the rest
               [[70, 76], [99, 113]],
               [[0, 0.05], [0, 1]]]  # separate subplot for the colorbar

width_ratios = [(lim[0][1] - lim[0][0] ) / (lim[1][1] - lim[1][0]) for lim in plot_limits]

fig, ax = plt.subplots(figsize=(16, 8), ncols=4, gridspec_kw={'width_ratios': width_ratios})
for i in range(3):
    img = ax[i].imshow(A, norm=colors.LogNorm(), cmap='jet', origin='lower')
    for n, p in zip(N, P):
        textsize = 0.3
        for line, label in zip((n + 0.2, n - 0.1, n - 0.4), (f"{p}", f"{n}", f"{A[n, p]:.3f}")):
            tp = TextPath((p - 0.4, line), label, size=0.3)
            ax[i].add_patch(PathPatch(tp, color="black" if 0.08 < A[n, p] < 0.7 else "white"))
    ax[i].xaxis.set_major_locator(MaxNLocator(integer=True))
    ax[i].yaxis.set_major_locator(MaxNLocator(integer=True))
    ax[i].set_xlim(plot_limits[i][0])
    ax[i].set_ylim(plot_limits[i][1])

plt.colorbar(img, cax=ax[3])
plt.tight_layout()
plt.show()

Вот как это выглядит:

triple plot version

...