Skip to content

[Bug]: QuadMesh.get_cursor_data() returns incorrect value #27570

Description

@davidgilbertson

Bug summary

QuadMesh.get_cursor_data() returns the incorrect value, but only for some figure sizes, when an event is near the boundary between two cells and the axis is categorical. By 'incorrect', I mean it doesn't align with the event's xdata and ydata values.

In the below sample I get the cell value by two means and compare them:

  • by rounding the xdata and ydata values from the event to index into the underlying data
  • using get_cursor_data.

They almost always match, but not always.

Code for reproduction

import matplotlib.pyplot as plt

import numpy as np

plt.rcParams["backend"] = "TKAgg"
plt.ion()
fig, ax = plt.subplots()

data = np.arange(6).reshape(2, 3)

qm = ax.pcolormesh(
    ["col 1", "col 2", "col 3"],
    ["row 1", "row 2"],
    data,
)


def on_mouse_move(event):
    if not event.inaxes:
        return
    cd = qm.get_cursor_data(event)
    if cd is None:
        return

    # The value of the cell, according to get_cursor_data()
    cd_val = cd[0]

    # The value of the cell, according to the data row/col
    data_val = data[round(event.ydata), round(event.xdata)]

    if cd_val == data_val:
        print("Fine")
    else:
        print(event)
        print(cd)
        raise ValueError(f"Value mismatch. {cd_val} != {data_val}")


fig.canvas.mpl_connect("motion_notify_event", on_mouse_move)
fig.canvas.manager.window.geometry("800x1054+100+100")

Actual outcome

As you hover the cursor right in the middle where two cells touch, it raises the error when the values don't match.
image

Expected outcome

The two values should always match.

Additional information

Trying to track down the issue, I noticed that it only happens at some figure heights. When I changed from my default to pretty much anything else, it was difficult to get the error.

So I can confirm that it isn't caused by the call to fig.canvas.manager.window.geometry, but if you don't do this you likely won't be able to replicate the issue.

The real world situation where I discovered this: I was producing a tooltip for a heatmap. I got the y label (Country) using ax.format_ydata(event.ydata) and the cell value using quad_mesh.get_cursor_data(event). I only noticed when the cells are quite small, it gives me incorrect information a lot, e.g. Canada has no bananas, so should show nan (short for no nanas), but it shows the value from Cape Verde instead.

image

Zoomed in
image

You can also replicate this without interaction by just firing a bunch of mouse events near the border, this will fail regardless of the figure size.

fig.canvas.draw()

for ax_y in np.linspace(0.49, 0.51, 100):
    x, y = ax.transAxes.transform((0.5, ax_y))
    mouse_event = MouseEvent(
        name="motion_notify_event",
        canvas=fig.canvas,
        x=x,
        y=y,
    )
    on_mouse_move(mouse_event)

I'm not sure what the fix would look like, it seems to go quite deep. A good interim step would be a note in the docs for get_cursor_data() warning that it isn't guaranteed to align with xdata and ydata near the borders.

Operating system

Windows

Matplotlib Version

3.8.0

Matplotlib Backend

TKAgg

Python version

3.10

Jupyter version

No response

Installation

pip

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions