2

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.

Trenton McKinney
  • 56,955
  • 33
  • 144
  • 158
darthV
  • 355
  • 1
  • 3
  • 16
  • Let me know how to get the graph with multiple scatter plots to display labels by hovering using matplotlib even handling – darthV Jul 23 '20 at 09:32

1 Answers1

1

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()
JohanC
  • 71,591
  • 8
  • 33
  • 66
  • So if I have N scatter plots, for this to work, I have to explicitly remove N-1 selections in each of the function definitions under `show_annotation(sel)` in above example? I have at least 10 scatter plots. Is there an efficient way to display labels while hovering using just matplotlib widgets? – darthV Jul 24 '20 at 06:12
  • It's working fine. How to make the label disappear as soon as I move my cursor. At present, the label persists until I move on to another point in the plot. – darthV Jul 25 '20 at 09:52
  • In the above example, is there way for 'scat' to display name and height while 'scat1' displays name and weight? – darthV Jul 25 '20 at 12:53
  • 1
    You can store whatever in the `annotation_names` list. I updated the example. – JohanC Jul 25 '20 at 13:23