pythonmatplotlibslideripywidgetsmplcursors

How to update hoover annotations when using a slider


My goal. I am using matplotlib slider to plot several series. I want to have hovering labels for each point. Each point corresponds to measurement. So I want to display measurement name to be on these hovering labels.

Question. How do I update labels for new series (for slider positions)? If I create new cursor in update function I get several labels for each point. So I need somehow to delete old label first. How do I do this?

Description. On first slide I get points with A and B labels. On second slide I should get C and D labels, but I am getting A and B again.

My code.

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider
import mplcursors as mplc
%matplotlib widget
data = {'Name': ['A', 'B', 'C', 'D'], 'x': [1,3,3,2],'y': [4,7,5,1],'color': [1,2,3,2],'size' :[50,40,10,30],'slide': [1,1,2,2]}
df=pd.DataFrame.from_dict(data)
dff=df.loc[df['slide']==1]
z=list(set(df['slide'].to_list()))
x = dff['x'].to_list()
y = dff['y'].to_list()
lbl=dff['Name'].to_list()
fig, ax = plt.subplots()
ax.clear()
points = ax.scatter(x,y,s=100, alpha=0.5)
mplc.cursor(ax, hover=True).connect(
"add", lambda sel: sel.annotation.set_text(lbl[sel.index]))
xmin=min(x)
xmax=max(x)
ymin=min(y)
ymax=max(y)
ax.set_ylim([xmin-10,xmax+10])
ax.set_xlim([ymin-10,xmax+10])

axfreq = fig.add_axes([0.15, 0.1, 0.65, 0.03])
plot_slider = Slider(
    ax=axfreq,
    label='',
    valmin=0,
    valmax=len(z),
    valinit=1,
    valstep=1,)

def update(val):
    dff=df.loc[df['slide']==plot_slider.val]
    x = dff['x'].to_list()
    y = dff['y'].to_list()
    lbl=dff['Name'].to_list()
    points.set_offsets(np.c_[x,y])
plot_slider.on_changed(update)
plt.show()

Solution

  • I had to (!pip install mplcursors ipympl) before I could run your code.

    Here is my workaround to get the correct annotations/labels after the slider being updated :

    sliders = df["slide"].unique()
    
    fig, ax = plt.subplots()
    
    d = {}
    for sl in sliders:
        lbl, x, y = df.loc[
            df["slide"].eq(sl), ["Name", "x", "y"]].T.to_numpy()
        pts = ax.scatter(x, y, s=100)
        curs = mplc.cursor(pts, hover=True)
        curs.connect("add", lambda sel, lbl=lbl:
            sel.annotation.set_text(lbl[sel.index]))
        d[sl] = {"cursor": curs, "scatter": pts}
        
    axfreq = fig.add_axes([0.15, 0.01, 0.73, 0.03]) # << I adjusted this
    
    plot_slider = Slider(
        ax=axfreq, label="", valinit=1, valstep=1,
        valmin=min(sliders), valmax=max(sliders))
    
    def curscatter(sl, op=0.5):
        """Display the cur/pts for the requested slider only"""
        for _sl, inf in d.items():
            curs = inf["cursor"]; pts = inf["scatter"]
            pts.set_alpha(op if _sl == sl else 0)
            curs.visible = (_sl == sl)
            
    def update(curr_sl):
        curscatter(curr_sl)
    
    curscatter(plot_slider.val) # or curscatter(1) 
    plot_slider.on_changed(update)
    plt.show()
    

    Name x y color size slide
    A 1 4 1 50 1
    B 3 7 2 40 1
    C 3 5 3 10 2
    D 2 1 2 30 2

    enter image description here