warped
warped

Reputation: 9482

matplotlib mouseover annotation between axes

I am looking to annotate lines that are drawn between two subplots.

At this moment, I am re-instantiating the annotation by accessing ax that fired the 'motion_notify_event'.

Minimal example:

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import ConnectionPatch

def update_line_annotation(event, line):
    x, y = event.xdata, event.ydata
    ax = event.inaxes

    global annot # in the real use case, annot is stored as class attribute
    annot = ax.annotate(
        f'{line}',
        xy=(x, y),
        xytext=(5, 5),
        textcoords='offset points',
    )

def hover(event):
    annot.set_visible(False)
    fig.canvas.draw_idle()

    for line in cons:
        cont, ind = line.contains(event)
        if cont:
            update_line_annotation(event, line)
            annot.set_visible(True)
            break
  

if __name__ == '__main__':
    fig, axes = plt.subplots(ncols=2)

    annot = axes[0].annotate(
        f'',
        xy=(0,0),
        xytext=(20, 20),
        textcoords='offset points',
    )
    annot.set_visible(False)

    cons = []
    for a in range(2):
        con = ConnectionPatch(
            xyA=np.random.randint(0,10,2),
            xyB=np.random.randint(0,10,2),
            coordsA='data',
            coordsB='data',
            axesA=axes[0],
            axesB=axes[1],
        )
        cons.append(con)
        axes[1].add_artist(con)

    fig.canvas.mpl_connect('motion_notify_event', hover)
    
    for ax in axes:
        ax.set_ylim(0,9)
        ax.set_xlim(0,9)

    plt.show()

There are two problems with my current approach:

I have two possible solutions, for which I cannot find the necessary functionality in matplotlib:

Help is greatly appreciated!

Upvotes: 2

Views: 642

Answers (1)

Aero Engy
Aero Engy

Reputation: 3608

Specify figure coordinates figure pixels for the xycoords of the annotation. Then use event.x and event.y instead of xdata and ydata.

You can also use plt.annotate instead of the axes so you don't need to use event.inaxes at all. So it doesn't matter if that returns None or not.

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import ConnectionPatch

def update_line_annotation(event, line):
    x, y = event.x, event.y

    global annot # in the real use case, annot is stored as class attribute
    annot = plt.annotate(
        f'{line}',
        xy=(x, y),
        xycoords='figure pixels',
        xytext=(5, 5),
        textcoords='offset points',
    )

def hover(event):
    annot.set_visible(False)
    fig.canvas.draw_idle()

    for line in cons:
        cont, ind = line.contains(event)
        if cont:
            update_line_annotation(event, line)
            annot.set_visible(True)
            break


if __name__ == '__main__':
    fig, axes = plt.subplots(ncols=2)
    annot = axes[0].annotate(
        f'',
        xy=(0,0),
        xytext=(20, 20),
        textcoords='offset points',
    )
    annot.set_visible(False)

    cons = []
    for a in range(2):
        con = ConnectionPatch(
            xyA=np.random.randint(0,10,2),
            xyB=np.random.randint(0,10,2),
            coordsA='data',
            coordsB='data',
            axesA=axes[0],
            axesB=axes[1],
        )
        cons.append(con)
        axes[1].add_artist(con)

    fig.canvas.mpl_connect('motion_notify_event', hover)

    for ax in axes:
        ax.set_ylim(0,9)
        ax.set_xlim(0,9)

    plt.show()

Upvotes: 1

Related Questions