Matplotlib交互导航工具栏导致图例选择器失效问题问询
解决Matplotlib交互式图例在缩放/平移后失效的问题
首先,我注意到你的代码里有个明显的语法错误(entry.set_visi没写完),这会直接导致图例点击功能异常,更别说缩放平移后的问题了。另外,在Matplotlib 2.0.0这个旧版本中,缩放/平移操作会触发图表重绘,原来的图例条目和绘图元素的关联容易丢失,加上你的事件绑定和可见性控制逻辑有一些疏漏,才导致了失效的问题。
问题根源分析
- 语法错误:
onpick函数中entry.set_visi未完成,应该是entry.set_visible(vis) - 重绘导致的关联丢失:Matplotlib在缩放/平移后会重新渲染图表,原来绑定的图例条目和绘图元素的映射关系没有被正确保留
- 直方图可见性处理不当:直方图返回的是元组,你只保存了
line3[2](patches),但控制可见性时的遍历逻辑有问题 - 图例条目绑定逻辑不严谨:你用了临时创建的
Line2D对象作为组图例条目,但没有正确关联到组内所有绘图元素的可见性状态
修复后的完整代码
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 # 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]: # Plot line line1, = ax1.plot(xline, y1+np.random.randint(low=0, high=datavariance, size=datalength), lw=1, label=member, marker='.') # Plot scatter line2 = ax2.scatter(np.random.randn(datalength)+4, y1+np.random.randint(low=0, high=datavariance, size=datalength), s=5, label=member) # Plot histogram hist_out = ax3.hist(np.random.randn(datalength)+4, 20, histtype='step', lw=1, label=member) # Save all relevant artists: line, scatter, histogram patches grouping[group][member].extend([line1, line2, hist_out[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')) # Get handles and labels from subplot 1 leghandles, leglabels = ax1.get_legend_handles_labels() grouplines = {} # Dictionary of lines by group groupvisible = {} # Dictionary for visibility control # Add group entries to legend handles/labels for group in grouping: # Collect all artists in the group group_artists = [] for member in grouping[group]: group_artists.extend(grouping[group][member]) grouplines[group] = group_artists groupvisible[group] = True # Add a dummy line for group legend entry leghandles.append(Line2D([], [], label=group, marker='_', markersize=7, lw=4, color='black')) leglabels.append(group) # Sort legend by name leghandles, leglabels = zip(*sorted(zip(leghandles, leglabels), key=operator.itemgetter(1))) leghandlelist = list(leghandles) leglabellist = list(leglabels) # Add spacing between groups in legend for i in range(groups): leghandlelist.insert((members + 2)*i, Line2D([], [], lw=0)) leglabellist.insert((members + 2)*i, ' ') leghandles = tuple(leghandlelist) leglabels = tuple(leglabellist) # Create legend mylegend = fig.legend(leghandles, leglabels, fancybox=True, shadow=True, loc='upper left', ncol=1, title='Process Groups') # Create a mapping from legend labels to their corresponding artists (members or groups) legend_label_map = {} # First map member labels to their artists for group in grouping: for member in grouping[group]: legend_label_map[member] = grouping[group][member] # Then map group labels to their artists for group in grouping: legend_label_map[group] = grouplines[group] # Set pickers on all legend entries for entry in mylegend.get_lines(): entry.set_picker(5) def onpick(event): legend_entry = event.artist label = legend_entry.get_label() # Skip empty spacing entries if label.strip() == '': return # Toggle visibility state new_vis = not legend_entry.get_visible() legend_entry.set_visible(new_vis) # Update corresponding artists if label in legend_label_map: artists = legend_label_map[label] for artist in artists: # Handle different artist types if isinstance(artist, list): # For histogram patches (list of patches) for patch in artist: patch.set_visible(new_vis) else: # For lines and scatter points artist.set_visible(new_vis) # Update group member legend entries if a group is toggled if label in grouping: for member in grouping[label]: for entry in mylegend.get_lines(): if entry.get_label() == member: entry.set_visible(new_vis) # Update group legend entry if a member is toggled (optional: sync group state) else: # Find which group this member belongs to member_group = None for group in grouping: if label in grouping[group]: member_group = group break if member_group: # Check if all members are visible/invisible to sync group legend all_vis = all(artist.get_visible() for artist in legend_label_map[label]) for entry in mylegend.get_lines(): if entry.get_label() == member_group: entry.set_visible(all_vis) # Redraw the figure to apply changes fig.canvas.draw_idle() # Bind the pick event fig.canvas.mpl_connect('pick_event', onpick) plt.subplots_adjust(left=0.2, right=0.95, top=0.90, bottom=.05, wspace=.2, hspace=.3) plt.show()
关键修改说明
- 修复语法错误:补全了
onpick函数中未完成的entry.set_visible(vis)调用 - 优化艺术家存储:直方图返回的元组中,正确保存了所有patches(
hist_out[2]),并统一处理不同类型的艺术家(线条、散点、直方图补丁) - 更严谨的图例映射:创建了
legend_label_map直接关联图例标签和对应的所有绘图艺术家,避免重绘后关联丢失 - 同步组和成员的可见性:当点击组图例时,自动同步组内所有成员的图例条目可见性;点击成员时,可选同步组图例的状态(根据成员的可见性自动更新组的状态)
- 强制重绘:在
onpick函数末尾调用fig.canvas.draw_idle()确保可见性更改立即生效,即使在缩放/平移后也能正确更新 - 跳过空条目:忽略图例中的空间隔条目,避免无效点击
经过这些修改后,即使使用缩放和平移工具,图例选择器也能正常工作,可见性切换不会失效。
内容的提问来源于stack exchange,提问作者GeorgeLPerkins
相关产品推荐
相关产品推荐

