Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Show z-value at mouse pointer position in status line with matplotlib's pcolormesh()

When using imshow() the z-value of the mouse pointer is shown in the status line as shown in the screen shot (on the right): screenshot imshow and pcolormesh
How do I achieve the same behavior with pcolormesh()?

The image was generated by the following code:

import numpy as np
import matplotlib.pyplot as plt

t = np.linspace(-1, 1, 101)
X, Y = np.meshgrid(t, 2*t)
Z = np.sin(2*np.pi*(X**2+Y**2))
fig, axx = plt.subplots(1, 2)
axx[0].set_title("imshow()")
axx[0].imshow(Z, origin='lower', aspect='auto', extent=[-1, 1, -2, 2])
axx[1].set_title("pcolormesh()")
axx[1].pcolormesh(X, Y, Z)
fig.tight_layout()
plt.show()
like image 205
Dietrich Avatar asked Mar 03 '17 11:03

Dietrich


1 Answers

One idea is to monkey patch the ax.format_coord function to include the desired value. This is also shown in a matplotlib example.

Specific solution

Now if you want both plots share the same function, a little bit of work needs to be spent on getting the axes limits correct.

import numpy as np
import matplotlib.pyplot as plt

t = np.linspace(-1, 1, 101)
X, Y = np.meshgrid(t, 2*t)
Z = np.sin(np.pi*(X**2+Y**2))


fig, axx = plt.subplots(1, 2)

axx[0].set_title("imshow()")
extent = [-1-(t[1]-t[0])/2., 1+(t[1]-t[0])/2., -2-(t[1]-t[0]), 2+(t[1]-t[0])]
axx[0].imshow(Z, origin='lower', aspect='auto', extent=extent)

axx[1].set_title("pcolormesh()")
axx[1].pcolormesh(X-(t[1]-t[0])/2., Y-(t[1]-t[0]), Z)
axx[1].set_xlim(-1-(t[1]-t[0])/2., 1+(t[1]-t[0])/2.)
axx[1].set_ylim( -2-(t[1]-t[0]), 2+(t[1]-t[0]) )

def format_coord(x, y):
    x0, x1 = axx[1].get_xlim()
    y0, y1 = axx[1].get_ylim()
    col = int(np.floor((x-x0)/float(x1-x0)*X.shape[1]))
    row = int(np.floor((y-y0)/float(y1-y0)*Y.shape[0]))
    if col >= 0 and col < Z.shape[1] and row >= 0 and row < Z.shape[0]:
        z = Z[row, col]
        return 'x=%1.4f, y=%1.4f, z=%1.4f' % (x, y, z)
    else:
        return 'x=%1.4f, y=%1.4f' % (x, y)

axx[1].format_coord = format_coord


fig.tight_layout()
plt.show()

enter image description here

General solution

The above is specific to the data in the question and has the drawback of not allowing to zoom and pan in the plot. A completely general solution would need to take into account the possibility that the image does not fill the complete axes and also the fact that pcolormesh pixels may be of unequal sizes.

This could look as follows, where in addition, the pixel numbers are shown:

import numpy as np; np.random.seed(42)
import matplotlib.pyplot as plt

x = [-3, -2, 0, 1.5, 2.2, 3.2, 3.9, 5, 6.75, 9]
y = [7, 7.1, 7.5, 7.7, 8, 8.2, 8.4, 8.8, 9]

X,Y = np.meshgrid(x,y)
Z = np.random.randint(0, 100, size=np.array(X.shape)-1)


fig, ax = plt.subplots()

pc = ax.pcolormesh(X,Y,Z)
fig.colorbar(pc)

def format_coord(x, y):
    xarr = X[0,:]
    yarr = Y[:,0]
    if ((x > xarr.min()) & (x <= xarr.max()) & 
        (y > yarr.min()) & (y <= yarr.max())):
        col = np.searchsorted(xarr, x)-1
        row = np.searchsorted(yarr, y)-1
        z = Z[row, col]
        return f'x={x:1.4f}, y={y:1.4f}, z={z:1.4f}   [{row},{col}]'
    else:
        return f'x={x:1.4f}, y={y:1.4f}'

ax.format_coord = format_coord

plt.show()

enter image description here

like image 196
ImportanceOfBeingErnest Avatar answered Nov 03 '22 15:11

ImportanceOfBeingErnest