pythonmatplotlibmplcursors

Matplotlib Hover Coordinates with Labelled XTicks


I've got a matplotlib graph with labelled X-ticks:

Graph of random data, with labelled X-ticks

The labels repeat (in case that's relevant). In the real graph, there is a multi-level X-axis with more clarification in the lower layers.

That works fine, but I want to be able to hover the mouse and see the X-coordinate in the top-right of the graph. Whenever I set xticks to labels, I just get a blank X-coordinate:

Graph with cursor hover indicator highlighted

If I use ax.xaxis.set_major_formatter('{x:g}'), it gets rid of my labels but the cursor coordinate starts working:

Graph with cursor hover indicator highlighted

Is there any way to make the cursor location still show the X coordinate even when I have a labelled X axis?

This also affects mplcursors: it shows the X value as empty if I click on a line between points or with the label if I click exactly on a point (whereas I'd like to see the underlying numerical X-coordinate as "A" is a bit meaningless without the context from the secondary axis):

mplcursors example graph

Source code:

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

x = np.arange(0, 9)
y = np.random.rand(*x.shape)
labels = ['A', 'B', 'C']*3

fig, ax = plt.subplots()
ax.plot(x, y, 'bx-', label='random')
ax.set_xticks(x, labels=labels)

# This makes the coordinate display work, but gets rid of the labels:
# ax.xaxis.set_major_formatter('{x:g}')

mplcursors.cursor(multiple=True)

plt.show()

Solution

  • From [ Matplotlib mouse cursor coordinates not shown for empty tick labels ] adding ax.format_coord = lambda x, y: 'x={:g}, y={:g}'.format(x, y) anywhere before plt.show() makes it show the right coordinates.

    If you want to display A, B or C as x coordinates, you can make a custom coordinates format string function in which you round the current x position to an integer to get an index and get the corresponding label from labels

    def custom_format_coord(x_val, y_val):
        ix = int(round(x_val))
    
        if 0 <= ix < len(labels):
            x_label = labels[ix]
        else:
            x_label = f"{x_val}"
    
        return f"x={x_label}, y={y_val:g}"
    
    ax.format_coord = custom_format_coord