0

I have been studying the answer to this question, trying to make annotations visible when I hover over my data point. Everything about the code works, except that the annotations are consistently showing up at the position where we initialized the annotation, and not at the position of the data point.

Here is a minimal working example:

import tkinter as tk
import sys
import matplotlib
matplotlib.use("TkAgg")
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg


class SimplePlot(tk.Tk):
    def __init__(self):
        tk.Tk.__init__(self)
        print(f"Matplotlib version: {matplotlib.__version__}")
        print(f"tkinter version: {tk.TkVersion}")

        self.make_plot()

    def make_plot(self):
        container = tk.Toplevel(self)

        fig = Figure()
        ax = fig.add_subplot(111)

        # Make dummy annotation
        annot = ax.annotate("", xy=(3,3))
        annot.set_visible(False)

        def update_annot(ind):
            pos = sc.get_offsets()[ind]
            annot.xy = pos
            text = "x: {}, y: {}".format(*pos)
            annot.set_text(text)

        def hover(event):
            vis = annot.get_visible()
            if event.inaxes == ax:
                cont, index = sc.contains(event)
                if cont:
                    ind = index["ind"][0]  # Get useful value from dictionary
                    update_annot(ind)
                    annot.set_visible(True)
                    fig.canvas.draw_idle()
                else:
                    if vis:
                        annot.set_visible(False)
                        fig.canvas.draw_idle()

        canvas = FigureCanvasTkAgg(fig, container)
        canvas.show()
        canvas.get_tk_widget().grid(row=1, column=0)
        fig.canvas.mpl_connect("motion_notify_event", hover)

        xs = [1, 2, 3, 4, 5]
        ys = [1, 4, 7, 4, 8]

        sc = ax.scatter(xs, ys, marker="o")


if __name__ == "__main__":
    app = SimplePlot()
    app.resizable(False, False)
    app.mainloop()

Why would the position of the annotation not be updated, since clearly the xy attribute of the annotation has been updated?

Versions:

Matplotlib version: 2.1.2
tkinter version: 8.6
Python version: 3.6.4 |Anaconda, Inc.| (default, Jan 16 2018, 12:04:33) 
[GCC 4.2.1 Compatible Clang 4.0.1 (tags/RELEASE_401/final)]
Yoda
  • 574
  • 1
  • 9
  • 21
  • The answer you link to is a minimal runnable example. Your code here is not a [mcve], so it's ultimaltely harder to debug. The only thing I can say, is that the original code should work fine and if it doesn't for you *that* would be the point to start debugging. – ImportanceOfBeingErnest Nov 17 '19 at 13:13
  • With your example script, I apparently have to keep my mouse clicked for it to work... But that did not make my code work. I will try to make an MWE. – Yoda Nov 17 '19 at 13:21
  • Mhh, the code works fine for me. It does create two windows though, is that desired? – ImportanceOfBeingErnest Nov 17 '19 at 13:57
  • That is the desired behavior in my application, but I suppose not needed here. Weird that it works for you... Could different matplotlib versions play a role? I added version information in the question. – Yoda Nov 17 '19 at 14:04
  • Oh, sorry, I thought that the position was desired. You made a mistake copying the code from my answer, it's `annot = ax.annotate("", xy=(3,3), xytext=(0,0),textcoords="offset points")` – ImportanceOfBeingErnest Nov 17 '19 at 14:20
  • Yes, that worked. I did not realize the actual functionality of those keywords. Thanks. – Yoda Nov 18 '19 at 12:33

1 Answers1

0

From the comments, the following line

annot = ax.annotate("", xy=(3,3))

should be replaced with

annot = ax.annotate("", xy=(3,3), xytext=(0,0),textcoords="offset points")

Thus, a fully functional MWE is the following:

import tkinter as tk
import sys
import matplotlib
matplotlib.use("TkAgg")
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg


class SimplePlot(tk.Tk):
    def __init__(self):
        tk.Tk.__init__(self)
        print(f"Matplotlib version: {matplotlib.__version__}")
        print(f"tkinter version: {tk.TkVersion}")

        self.make_plot()

    def make_plot(self):
        container = tk.Toplevel(self)

        fig = Figure()
        ax = fig.add_subplot(111)

        # Make dummy annotation
        annot = ax.annotate("", xy=(3,3), xytext=(0,0),textcoords="offset points")
        annot.set_visible(False)

        def update_annot(ind):
            pos = sc.get_offsets()[ind]
            annot.xy = pos
            text = "x: {}, y: {}".format(*pos)
            annot.set_text(text)

        def hover(event):
            vis = annot.get_visible()
            if event.inaxes == ax:
                cont, index = sc.contains(event)
                if cont:
                    ind = index["ind"][0]  # Get useful value from dictionary
                    update_annot(ind)
                    annot.set_visible(True)
                    fig.canvas.draw_idle()
                else:
                    if vis:
                        annot.set_visible(False)
                        fig.canvas.draw_idle()

        canvas = FigureCanvasTkAgg(fig, container)
        canvas.show()
        canvas.get_tk_widget().grid(row=1, column=0)
        fig.canvas.mpl_connect("motion_notify_event", hover)

        xs = [1, 2, 3, 4, 5]
        ys = [1, 4, 7, 4, 8]

        sc = ax.scatter(xs, ys, marker="o")


if __name__ == "__main__":
    app = SimplePlot()
    app.resizable(False, False)
    app.mainloop()
Yoda
  • 574
  • 1
  • 9
  • 21