pythonmatplotlibtkintermplcursors

How do I display the value of a heatmap element by hovering the cursor over it?


I would like to display the value of a particular element of a heatmap when I hover the mouse over it.

I have got it to display the value of the heatmap but it displayed information I do not want as well and when I first run the program, there are a lot of errors and I cannot figure out why.

I have tried various methods of displaying the value that I have seen online, such as datacursor(hover=True) but mplcursors.cursor(hover=True) is the only one that 'works'.

import tkinter as tk                                                    
from tkinter import ttk
from tkinter import messagebox
import numpy as np
from math import pi
import random
import matplotlib.pyplot as plt

from mpldatacursor import datacursor
import mplcursors

from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg


import pandas as pd
from openpyxl import load_workbook
from tkinter import filedialog

root = tk.Tk()                                                          
root.title("Stage 2 GUI")                                               
root.geometry("1270x590")  

mapArr = np.zeros([2,2],dtype=float)

mapArr=([113,62,31,63],
        [50,101,72,47],
        [92,10,40,12],
        [83,21,128,16])

xy=(['A','B','C','D','E','F'])


figure, axis = plt.subplots(figsize=(8,7))                              
heatmap = axis.imshow(
    mapArr,cmap="gray",interpolation='nearest',vmin=0, vmax=128)        

heatmap.axes.get_xaxis().set_visible(False)                             
heatmap.axes.get_yaxis().set_visible(False)

cb = figure.colorbar(heatmap)                                           

canvas = FigureCanvasTkAgg(figure, root)                                
canvas.get_tk_widget().place(x=-60,y=-60)                               

mplcursors.cursor(hover=True)

plt.show()

I would like to display the value of the heatmap element but not the x and y coordinates but I am not sure how to remove/ customise the information displayed and I would like it if there were not errors whenever I run the program (even if it technically does work).


Solution

  • You need to decide if you want to use pyplot or embedd matplotlib into tk. The following assumes you want to embedd (in that case, don't use pyplot!!).

    Using mplcursors

    The mplcursors documentation explains how to customize the output. Essentially it consists of connecting to an event called "add".

    import numpy as np
    import matplotlib
    from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
    import tkinter as tk
    import mplcursors
    
    root = tk.Tk()
    root.geometry("1270x590")  
    
    mapArr=np.array(([113,62,31,63],
                     [50,101,72,47],
                     [92,10,40,12],
                     [83,21,128,16]))
    
    xy=(['A','B','C','D','E','F'])
    
    
    fig = matplotlib.figure.Figure() 
    ax = fig.add_subplot()
    
    heatmap = ax.imshow(mapArr,cmap="gray",interpolation='nearest',vmin=0, vmax=128)        
    
    cb = fig.colorbar(heatmap)                                           
    
    canvas = FigureCanvasTkAgg(fig, root)                                
    canvas.get_tk_widget().place(x=60,y=60)                               
    
    cursor = mplcursors.cursor(heatmap, hover=True)
    
    @cursor.connect("add")
    def on_add(sel):
        i,j = sel.target.index
        sel.annotation.set_text(mapArr[i,j])
    
    tk.mainloop()
    

    Manually crating the hover box

    You can do the same as above without the use of mplcursors. This would be done by creating an annotation and changing its position and text depending on the mouse position.

    import numpy as np
    import matplotlib
    from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
    import tkinter as tk
    
    root = tk.Tk()
    root.geometry("1270x590")  
    
    mapArr=np.array(([113,62,31,63],
                     [50,101,72,47],
                     [92,10,40,12],
                     [83,21,128,16]))
    
    xy=(['A','B','C','D','E','F'])
    
    
    fig = matplotlib.figure.Figure() 
    ax = fig.add_subplot()
    
    heatmap = ax.imshow(mapArr,cmap="gray",interpolation='nearest',vmin=0, vmax=128)        
    
    cb = fig.colorbar(heatmap)                                           
    
    canvas = FigureCanvasTkAgg(fig, root)                                
    canvas.get_tk_widget().place(x=60,y=60)                               
    
    annot = ax.annotate("", xy=(0,0), xytext=(20,20), textcoords="offset points",
                        arrowprops=dict(arrowstyle="->"), visible=False,
                        bbox=dict(boxstyle="round", fc="w"))
    
    def hover(event):
        if event.inaxes == ax:
            x,y = event.xdata, event.ydata
            j,i = np.round(np.array((x,y))).astype(int)
            annot.xy = (x,y)
            annot.set_text(str(mapArr[i,j]))
            annot.set_visible(True)
        else:
            annot.set_visible(False)
        fig.canvas.draw_idle()
    
    canvas.mpl_connect("motion_notify_event", hover)
    
    tk.mainloop()