首页 > 解决方案 > 为什么 matplotlib 的交互式导航工具栏会导致图例选择器不起作用

问题描述

我们有时会在一张图表上查看很多系统进程的比较。我用 matplotlib 创建了一个模型,如下所示。由于数据点太多,图例与选择器交互,这些选择器可以单独和整个组切换线条/艺术家的可见性。

我遇到的问题是,在使用交互式导航缩放或平移后,交互式图例在再次单击同一个按钮之前不起作用。如果我使用缩放和平移,图例将完全没有响应,需要重新加载图表。

Python 3.6.0 和 Matplotlib 2.0.0

import numpy as np
import random
import operator
import matplotlib.pyplot as plt
from matplotlib.lines import Line2D

#Generate some fake data
datalength = 60
datavariance = 25
xline = np.array(range(datalength))
y1 = np.array(range(datalength))
groups = 5 #Number of groups
members = 4 #Number of members per group
grouping = {} #Nested dictionary to hold groups and member dictionaries
for g in range(groups):
    groupmembers = {}
    for m in range(members):
        groupmembers[f'SP_{g+1}_{m+1}'] = []
    grouping[f'SP_{g+1}'] = groupmembers

#Produces a nested dictionary like:
#grouping = {'PSP_1': {'PSP_1_1':[], 'PSP_1_2':[], 'PSP_1_3':[]},
#            'PSP_2': {'PSP_2_1':[], 'PSP_2_2':[], 'PSP_2_3':[]},
#            'PSP_3': {'PSP_3_1':[], 'PSP_3_2':[], 'PSP_3_3':[]}
#            }

#Establish the figure and arrange the subplots
fig = plt.figure(figsize=(10,7))
ax1 = plt.subplot2grid((3,1), (0,0), rowspan=1, colspan=1, facecolor='white')
ax2 = plt.subplot2grid((3,1), (1,0), rowspan=1, colspan=1, facecolor='white')
ax3 = plt.subplot2grid((3,1), (2,0), rowspan=1, colspan=1, facecolor='white')

# Each member is plotted in each subplot and then the artist is added to the array in the nested dictionary.
for group in grouping:
    for member in grouping[group]:
        line1 = ax1.plot(xline, y1+np.random.randint(low=0, high=datavariance, size=datalength), lw=1, label=member, marker='.')
        line2= ax2.scatter(np.random.randn(datalength)+4, y1+np.random.randint(low=0, high=datavariance, size=datalength), s=5, label=member)
        line3 = ax3.hist(np.random.randn(datalength)+4, 20, histtype='step', lw=1, label=member)
        #Dictionary entry of lines for member
        grouping[group][member].extend((line1[0], line2, line3[2]))

#Chart labeling
ax1.set_title('Process Line', fontsize=10, bbox=dict(facecolor='white'))
ax2.set_title('Process Scatter', fontsize=10, bbox=dict(facecolor='white'))
ax3.set_title('Process Histogram', fontsize=10, bbox=dict(facecolor='white'))

fig.suptitle('System Processes', fontsize=16, bbox=dict(facecolor='white'))

leghandles, leglabels = ax1.get_legend_handles_labels() #Get handels and labels from subplot 1 for use in the legend.

grouplines = {} #Dictionary of lines by group
groupvisible = {} #Dictionary for visibility control
for group in grouping:
    linearray = []
    for member in grouping[group]:
        for line in grouping[group][member]:
            linearray.append(line)
    grouplines[group] = linearray #Dictionary entry of Group:array of all lines in group
    groupvisible[group] = True #Dictionary to control Group visibility
    leghandles.append(Line2D([], [], label=group, marker='_', markersize=7, lw=4, color='black')) #Array entry of non-existent lines for Group legend
    leglabels.append(group)

leghandles, leglabels = zip(*sorted(zip(leghandles, leglabels), key=operator.itemgetter(1))) #Sort legend by name
leghandlelist = list(leghandles) #Change tuple to list for inserting
leglabellist = list(leglabels) #Change tuple to list for inserting

for i in range(groups):
    leghandlelist.insert((members+2)*i, Line2D([], [], lw=.0)) #Add fake like
    leglabellist.insert((members+2)*i, ' ') #Add blank label

leghandles = tuple(leghandlelist) #Return handles to tuple
leglabels = tuple(leglabellist) #Return labels to tuple
mylegend = fig.legend(leghandles, leglabels, fancybox=True, shadow=True, loc='upper left', ncol=1, title='Process Groups')

#Associate the legend to plotted lines and set pickers on the legend item
legenditems = {}
for legendentry, plottedline in zip(mylegend.get_lines(), leghandles):
    legendentry.set_picker(5)
    legenditems[legendentry] = plottedline

plt.subplots_adjust(left=0.2, right=0.95, top=0.90, bottom=.05, wspace=.2, hspace=.3)


def onpick(event):
    legendclick = event.artist #Legend object
    legendlabel = legendclick.get_label() #Name of selected lines or group to toggle

    #Check line visibility and toggle accordingly
    vis = not legendclick.get_visible()
    legendclick.set_visible(vis) #Toggle the clicked legend item

    if legendlabel in grouplines.keys(): #If the selected legend item is a group name
        for plottedline in grouplines[legendlabel]: #Toggle plotted lines for group members
            try: #Toggle lines and scatter points
                plottedline.set_visible(vis)
            except: #Toggle histograms
                for hi in range(len(plottedline)):
                    plottedline[hi].set_visible(vis)
        for member in grouping[legendlabel]: #Toggle legend entries for group members
            for entry in legenditems:
                if entry.get_label() == member:
                    try: #Toggle lines and scatter points
                        entry.set_visible(vis)
                    except: #Toggle histograms
                        for hi in range(len(entry)):
                            print(hi)
    else: #If a member item was selected
        for group in grouping:
            for member in grouping[group]:
                if member == legendlabel: #Toggle member lines in each chart.
                    for i in range(len(grouping[group][member])):
                        try: #Toggle lines and scatter
                            selectedline = grouping[group][member][i]
                            selectedline.set_visible(vis)
                        except: #Toggle histogram
                            for hi in range(len(grouping[group][member][i])):
                                selectedline = grouping[group][member][i][hi]
                                selectedline.set_visible(vis)

    fig.canvas.draw()

fig.canvas.mpl_connect('pick_event', onpick)

plt.show()

在此处输入图像描述

标签: python-3.xmatplotliblegendinteractive

解决方案


推荐阅读