Dynamic labels in matplotlib stacked area chart

浪尽此生 提交于 2021-01-24 09:40:07

问题


I'm working with a stacked area plot created by pandas. The screenshots shows one such typical plot (labels are deliberatly not shown): The relevant code producing this plot is

fig, axes = plt.subplots(nrows=2, ncols=1)
coredata = nonzero.loc[:, nonzero.columns != 'Busy'].plot.area(figsize=(9, 8), ax=axes[0], colormap='jet')

where nonzero is a larger dataframe. The issue is that there are too many columns leading to a crowded legend. Instead of moving the legend out of the picture I'd like to use matplotlib's events to tell me which element of the chart I'm hovering over.

def on_move(event):
    if event.inaxes == coredata:
        # help please

fig.canvas.mpl_connect("motion_notify_event", on_move)

The event fires exactly as desired but I have trouble extracting the area I'm hovering over (respectively its label). coredata.artists is empty, coredata.lines is a matplotlib.lines.Line2D element (supposedly too low level). How can I access the current area under the cursor in order to display its label?

Edit: following is a minimal example:

from pandas import DataFrame, Series
from matplotlib import pyplot as plt

# mock data
d = {'one' : Series([1., 2., 3.], index=['a', 'b', 'c']),
     'two' : Series([1., 2., 3., 4.], index=['a', 'b', 'c', 'd']),
     'three': Series([0.5, 0.2, 0.3, 0.1], index=['a', 'b', 'c', 'd']),
     'four': Series([3., 2., 1., 0.3], index=['a', 'b', 'c', 'd']),
}
df = DataFrame(d)

fig, axes = plt.subplots()
chart = df.plot.area(ax=axes)

# create and initially hide annotation
annot = axes.annotate("", xy=(0,0), xytext=(-20,20),textcoords="offset points",
                    bbox=dict(boxstyle="round", fc="w"))
annot.set_visible(False)
def on_move(event):
    if event.inaxes == chart:
        pass # help plz: how do I best check I currently hover over one, two, three or four?
        print(event.xdata, event.ydata)
fig.canvas.mpl_connect("motion_notify_event", on_move)

plt.show()

回答1:


Just as when hovering a scatter, see e.g. here or here you need to check if any of the collections contains the mouseevent. To this end you would look over the collections of interest, do the check and if successful may add an identifier to a list.

which = []
for i,c in enumerate(axes.collections):
    if c.contains(event)[0]:
        which.append(i)

You may then use this list to draw a new legend with only the collections identified in that list. Since redrawing the canvas is expensive and may slow down the application one would try to do it as seldom as possible. While moving the mouse, the exact same result would be expected a lot of times, so we may store it and only create a new legend in case it needs to be changed.

from pandas import DataFrame, Series
from matplotlib import pyplot as plt

# mock data
d = {'one' : Series([1., 2., 3.], index=['a', 'b', 'c']),
     'two' : Series([1., 2., 3., 4.], index=['a', 'b', 'c', 'd']),
     'three': Series([0.5, 0.2, 0.3, 0.1], index=['a', 'b', 'c', 'd']),
     'four': Series([3., 2., 1., 0.3], index=['a', 'b', 'c', 'd']),
}
df = DataFrame(d)

fig, axes = plt.subplots()
df.plot.area(ax=axes, legend=False)

# create and initially hide annotation
annot = axes.annotate("", xy=(0,0), xytext=(-20,20),textcoords="offset points",
                    bbox=dict(boxstyle="round", fc="w"))
annot.set_visible(False)

last = [None]
def on_move(event):
    if event.inaxes == axes:
        which = []
        for i,c in enumerate(axes.collections):
            if c.contains(event)[0]:
                which.append(i)
        if which != last[0]:
            last[0] = which
            axes.legend([axes.collections[i] for i in which],
                        [df.columns[i] for i in which])
            fig.canvas.draw_idle()

fig.canvas.mpl_connect("motion_notify_event", on_move)

plt.show()



来源:https://stackoverflow.com/questions/47635939/dynamic-labels-in-matplotlib-stacked-area-chart

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!