I am using matplotlib to make scatter plots. Each point on the scatter plot is associated with a named object. I would like to be able to see the name of an object when I hover my cursor over the point on the scatter plot associated with that object. In particular, it would be nice to be able to quickly see the names of the points that are outliers. The closest thing I have been able to find while searching here is the annotate command, but that appears to create a fixed label on the plot. Unfortunately, with the number of points that I have, the scatter plot would be unreadable if I labeled each point. Does anyone know of a way to create labels that only appear when the cursor hovers in the vicinity of that point?


当前回答

来自http://matplotlib.sourceforge.net/examples/event_handling/pick_event_demo.html:

from matplotlib.pyplot import figure, show
import numpy as npy
from numpy.random import rand


if 1: # picking on a scatter plot (matplotlib.collections.RegularPolyCollection)

    x, y, c, s = rand(4, 100)
    def onpick3(event):
        ind = event.ind
        print('onpick3 scatter:', ind, npy.take(x, ind), npy.take(y, ind))

    fig = figure()
    ax1 = fig.add_subplot(111)
    col = ax1.scatter(x, y, 100*s, c, picker=True)
    #fig.savefig('pscoll.eps')
    fig.canvas.mpl_connect('pick_event', onpick3)

show()

这个食谱绘制了一个关于选择数据点的注释:http://scipy-cookbook.readthedocs.io/items/Matplotlib_Interactive_Plotting.html。 这个配方绘制了一个工具提示,但它需要wxPython: matplotlib中的点和线工具提示?

其他回答

也许这对任何人都有帮助,但我已经改编了@ImportanceOfBeingErnest的答案,以与补丁和类一起工作。特点:

整个框架包含在单个类中,因此所有使用的变量仅在其相关范围内可用。 可以创建多个不同的补丁集吗 将鼠标悬停在补丁上将打印补丁集合名称和补丁子名称 将鼠标悬停在一个补丁上,通过将其边缘颜色更改为黑色来高亮该集合的所有补丁

注意:对于我的应用程序,重叠是不相关的,因此一次只显示一个对象的名称。如果你愿意,可以随意扩展到多个对象,这并不太难。

使用

fig, ax = plt.subplots(tight_layout=True)

ap = annotated_patches(fig, ax)
ap.add_patches('Azure', 'circle', 'blue', np.random.uniform(0, 1, (4,2)), 'ABCD', 0.1)
ap.add_patches('Lava', 'rect', 'red', np.random.uniform(0, 1, (3,2)), 'EFG', 0.1, 0.05)
ap.add_patches('Emerald', 'rect', 'green', np.random.uniform(0, 1, (3,2)), 'HIJ', 0.05, 0.1)

plt.axis('equal')
plt.axis('off')

plt.show()

实现

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
from matplotlib.collections import PatchCollection

np.random.seed(1)


class annotated_patches:
    def __init__(self, fig, ax):
        self.fig = fig
        self.ax = ax

        self.annot = self.ax.annotate("", xy=(0,0),
                            xytext=(20,20),
                            textcoords="offset points",
                            bbox=dict(boxstyle="round", fc="w"),
                            arrowprops=dict(arrowstyle="->"))
        
        self.annot.set_visible(False)
        
        self.collectionsDict = {}
        self.coordsDict = {}
        self.namesDict = {}
        self.isActiveDict = {}

        self.motionCallbackID = self.fig.canvas.mpl_connect("motion_notify_event", self.hover)

    def add_patches(self, groupName, kind, color, xyCoords, names, *params):
        if kind=='circle':
            circles = [mpatches.Circle(xy, *params, ec="none") for xy in xyCoords]
            thisCollection = PatchCollection(circles, facecolor=color, alpha=0.5, edgecolor=None)
            ax.add_collection(thisCollection)
        elif kind == 'rect':
            rectangles = [mpatches.Rectangle(xy, *params, ec="none") for xy in xyCoords] 
            thisCollection = PatchCollection(rectangles, facecolor=color, alpha=0.5, edgecolor=None)
            ax.add_collection(thisCollection)
        else:
            raise ValueError('Unexpected kind', kind)
            
        self.collectionsDict[groupName] = thisCollection
        self.coordsDict[groupName] = xyCoords
        self.namesDict[groupName] = names
        self.isActiveDict[groupName] = False
        
    def update_annot(self, groupName, patchIdxs):
        self.annot.xy = self.coordsDict[groupName][patchIdxs[0]]
        self.annot.set_text(groupName + ': ' + self.namesDict[groupName][patchIdxs[0]])
        
        # Set edge color
        self.collectionsDict[groupName].set_edgecolor('black')
        self.isActiveDict[groupName] = True

    def hover(self, event):
        vis = self.annot.get_visible()
        updatedAny = False
        if event.inaxes == self.ax:            
            for groupName, collection in self.collectionsDict.items():
                cont, ind = collection.contains(event)
                if cont:
                    self.update_annot(groupName, ind["ind"])
                    self.annot.set_visible(True)
                    self.fig.canvas.draw_idle()
                    updatedAny = True
                else:
                    if self.isActiveDict[groupName]:
                        collection.set_edgecolor(None)
                        self.isActiveDict[groupName] = True
                    
            if (not updatedAny) and vis:
                self.annot.set_visible(False)
                self.fig.canvas.draw_idle()

Mpld3为我解决它。 编辑(新增代码):

import matplotlib.pyplot as plt
import numpy as np
import mpld3

fig, ax = plt.subplots(subplot_kw=dict(axisbg='#EEEEEE'))
N = 100

scatter = ax.scatter(np.random.normal(size=N),
                 np.random.normal(size=N),
                 c=np.random.random(size=N),
                 s=1000 * np.random.random(size=N),
                 alpha=0.3,
                 cmap=plt.cm.jet)
ax.grid(color='white', linestyle='solid')

ax.set_title("Scatter Plot (with tooltips!)", size=20)

labels = ['point {0}'.format(i + 1) for i in range(N)]
tooltip = mpld3.plugins.PointLabelTooltip(scatter, labels=labels)
mpld3.plugins.connect(fig, tooltip)

mpld3.show()

你可以检查这个例子

Mplcursors对我很有用。Mplcursors为matplotlib提供了可单击的注释。它很大程度上受到mpldatacursor (https://github.com/joferkington/mpldatacursor)的启发,具有非常简化的API

import matplotlib.pyplot as plt
import numpy as np
import mplcursors

data = np.outer(range(10), range(1, 5))

fig, ax = plt.subplots()
lines = ax.plot(data)
ax.set_title("Click somewhere on a line.\nRight-click to deselect.\n"
             "Annotations can be dragged.")

mplcursors.cursor(lines) # or just mplcursors.cursor()

plt.show()

这个解决方案适用于悬停一行而不需要单击它:

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()

在matplotlib状态栏中显示对象信息

特性

不需要额外的库 干净的情节 没有厂牌和艺人的重叠 支持多艺术家标签 可以处理来自不同绘图调用的艺术家(如scatter, plot, add_patch) 库风格的代码

Code

### imports
import matplotlib as mpl
import matplotlib.pylab as plt
import numpy as np


# https://stackoverflow.com/a/47166787/7128154
# https://matplotlib.org/3.3.3/api/collections_api.html#matplotlib.collections.PathCollection
# https://matplotlib.org/3.3.3/api/path_api.html#matplotlib.path.Path
# https://stackoverflow.com/questions/15876011/add-information-to-matplotlib-navigation-toolbar-status-bar
# https://stackoverflow.com/questions/36730261/matplotlib-path-contains-point
# https://stackoverflow.com/a/36335048/7128154
class StatusbarHoverManager:
    """
    Manage hover information for mpl.axes.Axes object based on appearing
    artists.

    Attributes
    ----------
    ax : mpl.axes.Axes
        subplot to show status information
    artists : list of mpl.artist.Artist
        elements on the subplot, which react to mouse over
    labels : list (list of strings) or strings
        each element on the top level corresponds to an artist.
        if the artist has items
        (i.e. second return value of contains() has key 'ind'),
        the element has to be of type list.
        otherwise the element if of type string
    cid : to reconnect motion_notify_event
    """
    def __init__(self, ax):
        assert isinstance(ax, mpl.axes.Axes)


        def hover(event):
            if event.inaxes != ax:
                return
            info = 'x={:.2f}, y={:.2f}'.format(event.xdata, event.ydata)
            ax.format_coord = lambda x, y: info
        cid = ax.figure.canvas.mpl_connect("motion_notify_event", hover)

        self.ax = ax
        self.cid = cid
        self.artists = []
        self.labels = []

    def add_artist_labels(self, artist, label):
        if isinstance(artist, list):
            assert len(artist) == 1
            artist = artist[0]

        self.artists += [artist]
        self.labels += [label]

        def hover(event):
            if event.inaxes != self.ax:
                return
            info = 'x={:.2f}, y={:.2f}'.format(event.xdata, event.ydata)
            for aa, artist in enumerate(self.artists):
                cont, dct = artist.contains(event)
                if not cont:
                    continue
                inds = dct.get('ind')
                if inds is not None:  # artist contains items
                    for ii in inds:
                        lbl = self.labels[aa][ii]
                        info += ';   artist [{:d}, {:d}]: {:}'.format(
                            aa, ii, lbl)
                else:
                    lbl = self.labels[aa]
                    info += ';   artist [{:d}]: {:}'.format(aa, lbl)
            self.ax.format_coord = lambda x, y: info

        self.ax.figure.canvas.mpl_disconnect(self.cid)
        self.cid = self.ax.figure.canvas.mpl_connect(
            "motion_notify_event", hover)



def demo_StatusbarHoverManager():
    fig, ax = plt.subplots()
    shm = StatusbarHoverManager(ax)

    poly = mpl.patches.Polygon(
        [[0,0], [3, 5], [5, 4], [6,1]], closed=True, color='green', zorder=0)
    artist = ax.add_patch(poly)
    shm.add_artist_labels(artist, 'polygon')

    artist = ax.scatter([2.5, 1, 2, 3], [6, 1, 1, 7], c='blue', s=10**2)
    lbls = ['point ' + str(ii) for ii in range(4)]
    shm.add_artist_labels(artist, lbls)

    artist = ax.plot(
        [0, 0, 1, 5, 3], [0, 1, 1, 0, 2], marker='o', color='red')
    lbls = ['segment ' + str(ii) for ii in range(5)]
    shm.add_artist_labels(artist, lbls)

    plt.show()


# --- main
if __name__== "__main__":
    demo_StatusbarHoverManager()