Using mplcursors with more than one dataframe

985 Views Asked by At

from matplotlib import pyplot as plt
import mplcursors
from pandas import DataFrame


df = DataFrame(
    [("Alice", 163, 54),
     ("Bob", 174, 67),
     ("Charlie", 177, 73),
     ("Diane", 168, 57)],
    columns=["name", "height", "weight"])

fig,ax=plt.subplots(1,1)

ax.scatter(df["height"], df["weight"])


mplcursors.cursor().connect(
    "add", lambda sel: sel.annotation.set_text(df["name"][sel.target.index]))
  
plt.show()

Above code can display label when hovering a point; I want to display label of a point when using multiple dataframes and multiple scatter plots. When I use multiple dataframes and multiple scatter plots, it is displaying the labels from only one dataframe(whichever is mentioned in below part of the code) even when hovering over other points belonging to other dataframes.

mplcursors.cursor().connect(
"add", lambda sel: sel.annotation.set_text(df["name"][sel.target.index]))

Code to try with two dataframes:

from matplotlib import pyplot as plt
import mplcursors
from pandas import DataFrame


df = DataFrame(
    [("Alice", 163, 54),
     ("Bob", 174, 67),
     ("Charlie", 177, 73),
     ("Diane", 168, 57)],
    columns=["name", "height", "weight"])

df1 = DataFrame(
    [("Alice1", 140, 50),
     ("Bob1", 179, 60),
     ("Charlie1", 120, 70),
     ("Diane1", 122, 60)],
    columns=["name", "height", "weight"])

fig,ax=plt.subplots(1,1)

ax.scatter(df["height"], df["weight"])
ax.scatter(df1["height"], df1["weight"])

mplcursors.cursor(hover=True).connect(
    "add", lambda sel: sel.annotation.set_text(df["name"][sel.target.index]))

plt.show()

Thank you.

1

There are 1 best solutions below

4
JohanC On BEST ANSWER

Introducing a new attribute to the PathCollection that is returned by ax.scatter, we can store the names to be displayed.

The code below creates an attribute annotation_names which then can be retrieved by the annotation function.

from matplotlib import pyplot as plt
import mplcursors
from pandas import DataFrame

df = DataFrame([("Alice", 163, 54), ("Bob", 174, 67), ("Charlie", 177, 73), ("Diane", 168, 57)], columns=["name", "height", "weight"])
df1 = DataFrame([("Alice1", 140, 50), ("Bob1", 179, 60), ("Charlie1", 120, 70), ("Diane1", 122, 60)], columns=["name", "height", "weight"])

fig, ax = plt.subplots(1, 1)
scat = ax.scatter(df["height"], df["weight"])
scat.annotation_names = [f'{n}\nh: {h}' for n, h in zip(df["name"], df["height"])]
scat1 = ax.scatter(df1["height"], df1["weight"])
scat1.annotation_names = [f'{n}\nw: {w}' for n, w in zip(df1["name"], df1["weight"])]

cursor = mplcursors.cursor([scat, scat1], hover=True)
cursor.connect("add", lambda sel: sel.annotation.set_text(sel.artist.annotation_names[sel.target.index]))

plt.show()

PS: Here is an attempt to remove the annotation with a mouse move. There is a test whether the mouse moved more than 2 data units in x or y direction away from the target. The ideal distance might be different in your application.

from matplotlib import pyplot as plt
import mplcursors
from pandas import DataFrame

annotation_xy = [0,0]

def remove_annotations(event):
    global annotation_xy
    if event.xdata is not None and (abs(annotation_xy[0] - event.xdata) > 2 or abs(annotation_xy[1] - event.ydata) > 2):
        for s in cursor.selections:
            cursor.remove_selection(s)

def set_annotation(sel):
    global annotation_xy
    sel.annotation.set_text(sel.artist.annotation_names[sel.target.index])
    annotation_xy = sel.target

df = DataFrame([("Alice", 163, 54), ("Bob", 174, 67), ("Charlie", 177, 73), ("Diane", 168, 57)], columns=["name", "height", "weight"])
df1 = DataFrame([("Alice1", 140, 50), ("Bob1", 179, 60), ("Charlie1", 120, 70), ("Diane1", 122, 60)], columns=["name", "height", "weight"])

fig, ax = plt.subplots(1, 1)
scat = ax.scatter(df["height"], df["weight"])
scat.annotation_names = df["name"]
scat1 = ax.scatter(df1["height"], df1["weight"])
scat1.annotation_names = df1["name"]

cursor = mplcursors.cursor([scat, scat1], hover=True)
cursor.connect("add", set_annotation)
plt.connect('motion_notify_event', remove_annotations)

plt.show()