如何为图表添加悬停注释
我正在使用matplotlib来制作散点图。散点图上的每个点都与一个有名字的对象相关联。我希望当我把鼠标悬停在与某个对象相关的点上时,能够看到这个对象的名字。特别是,我希望能快速看到那些异常值的点的名字。我在这里搜索时找到的最接近的东西是annotate命令,但这个命令似乎是在图上创建一个固定的标签。不幸的是,由于我有很多点,如果给每个点都加标签,散点图就会变得难以阅读。有没有人知道有没有办法创建那种只有在鼠标悬停在某个点附近时才会出现的标签?
13 个回答
47
- 使用
mplcursors
这个包可能是最简单的选择。- mplcursors:查看文档
- mplcursors:GitHub页面
- 如果你在用 Anaconda,可以按照这些 说明来安装;如果不是,就可以用这些 说明通过
pip
来安装。
- 这个图必须在一个互动窗口中绘制,而不是直接在代码里显示。
- 在 Jupyter 中,像在一个单元格里执行
%matplotlib qt
这样的命令可以开启互动绘图。你可以查看 如何在 IPython 笔记本中打开互动的 matplotlib 窗口?
- 在 Jupyter 中,像在一个单元格里执行
- 在
python 3.10
、pandas 1.4.2
、matplotlib 3.5.1
和seaborn 0.11.2
中测试过
import matplotlib.pyplot as plt
import pandas_datareader as web # only for test data; must be installed with conda or pip
from mplcursors import cursor # separate package must be installed
# reproducible sample data as a pandas dataframe
df = web.DataReader('aapl', data_source='yahoo', start='2021-03-09', end='2022-06-13')
plt.figure(figsize=(12, 7))
plt.plot(df.index, df.Close)
cursor(hover=True)
plt.show()
Pandas
ax = df.plot(y='Close', figsize=(10, 7))
cursor(hover=True)
plt.show()
Seaborn
- 可以与轴级绘图(比如
sns.lineplot
)和图级绘图(比如sns.relplot
)一起使用。
import seaborn as sns
# load sample data
tips = sns.load_dataset('tips')
sns.relplot(data=tips, x="total_bill", y="tip", hue="day", col="time")
cursor(hover=True)
plt.show()
76
这个解决方案可以在鼠标悬停在一条线上的时候使用,不需要点击它:
import matplotlib.pyplot as plt
# Need to create as global variable so our callback(on_plot_hover) can access
fig = plt.figure()
plot = fig.add_subplot(111)
# create some curves
for i in range(4):
# Giving unique ids to each data member
plot.plot(
[i*1,i*2,i*3,i*4],
gid=i)
def on_plot_hover(event):
# Iterating over each data member plotted
for curve in plot.get_lines():
# Searching which data member corresponds to current mouse position
if curve.contains(event)[0]:
print("over %s" % curve.get_gid())
fig.canvas.mpl_connect('motion_notify_event', on_plot_hover)
plt.show()
252
这里有一段代码,它使用了散点图,并且在鼠标悬停在散点上时会显示注释。
import matplotlib.pyplot as plt
import numpy as np; np.random.seed(1)
x = np.random.rand(15)
y = np.random.rand(15)
names = np.array(list("ABCDEFGHIJKLMNO"))
c = np.random.randint(1,5,size=15)
norm = plt.Normalize(1,4)
cmap = plt.cm.RdYlGn
fig,ax = plt.subplots()
sc = plt.scatter(x,y,c=c, s=100, cmap=cmap, norm=norm)
annot = ax.annotate("", xy=(0,0), xytext=(20,20),textcoords="offset points",
bbox=dict(boxstyle="round", fc="w"),
arrowprops=dict(arrowstyle="->"))
annot.set_visible(False)
def update_annot(ind):
pos = sc.get_offsets()[ind["ind"][0]]
annot.xy = pos
text = "{}, {}".format(" ".join(list(map(str,ind["ind"]))),
" ".join([names[n] for n in ind["ind"]]))
annot.set_text(text)
annot.get_bbox_patch().set_facecolor(cmap(norm(c[ind["ind"][0]])))
annot.get_bbox_patch().set_alpha(0.4)
def hover(event):
vis = annot.get_visible()
if event.inaxes == ax:
cont, ind = sc.contains(event)
if cont:
update_annot(ind)
annot.set_visible(True)
fig.canvas.draw_idle()
else:
if vis:
annot.set_visible(False)
fig.canvas.draw_idle()
fig.canvas.mpl_connect("motion_notify_event", hover)
plt.show()
因为很多人也想用这个方法来做折线图,而不是散点图,下面的代码就是用来做折线图的(它的工作方式稍有不同)。
import matplotlib.pyplot as plt
import numpy as np; np.random.seed(1)
x = np.sort(np.random.rand(15))
y = np.sort(np.random.rand(15))
names = np.array(list("ABCDEFGHIJKLMNO"))
norm = plt.Normalize(1,4)
cmap = plt.cm.RdYlGn
fig,ax = plt.subplots()
line, = plt.plot(x,y, marker="o")
annot = ax.annotate("", xy=(0,0), xytext=(-20,20),textcoords="offset points",
bbox=dict(boxstyle="round", fc="w"),
arrowprops=dict(arrowstyle="->"))
annot.set_visible(False)
def update_annot(ind):
x,y = line.get_data()
annot.xy = (x[ind["ind"][0]], y[ind["ind"][0]])
text = "{}, {}".format(" ".join(list(map(str,ind["ind"]))),
" ".join([names[n] for n in ind["ind"]]))
annot.set_text(text)
annot.get_bbox_patch().set_alpha(0.4)
def hover(event):
vis = annot.get_visible()
if event.inaxes == ax:
cont, ind = line.contains(event)
if cont:
update_annot(ind)
annot.set_visible(True)
fig.canvas.draw_idle()
else:
if vis:
annot.set_visible(False)
fig.canvas.draw_idle()
fig.canvas.mpl_connect("motion_notify_event", hover)
plt.show()
如果有人在寻找双坐标轴的折线图解决方案,可以参考如何在多个坐标轴上悬停时显示标签?
如果有人在寻找柱状图的解决方案,可以参考例如这个回答。