Matplotlib点击散图图例切换元素可见性 代码运行异常求助
核心问题定位
原代码点击图例没效果,是几个逻辑错误叠加导致的:
- 你用的是单个散点对象+颜色映射实现的分类散点图,所有点都归同一个
PathCollection对象管理,直接对整个散点调用set_visible()只能全显/全藏,根本无法单独隐藏某一类点 - 拾取回调里写的
self.scatter.picked_legend是不存在的属性,代码运行到这行直接抛出属性错误,后续逻辑完全没有执行,只是PyQt默认不会把回调内的错误弹窗提示,看起来就像点了没反应 - 仅给第一个标签为'10'的图例项开启了拾取功能,剩下两个图例项点击后根本不会触发事件
- 没有建立图例项和对应数据类别的映射关系,就算事件正常触发,也无法定位到需要隐藏的点集合,也没有实现图例淡显的状态逻辑
修改后完整可运行代码
import sys import pandas as pd from PyQt5 import QtWidgets from PyQt5.QtWidgets import QVBoxLayout, QWidget from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as canvas from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar from matplotlib.figure import Figure from matplotlib.colors import ListedColormap import matplotlib.colors as colors import matplotlib.cm as cm import numpy as np class MplCanvas(canvas): def __init__(self, parent=None, width=5, height=4, dpi=100): fig = Figure(figsize=(width, height), dpi=dpi) self.axes = fig.add_subplot(111) super().__init__(fig) class MainWindow(QtWidgets.QMainWindow): def __init__(self): super().__init__() df = pd.DataFrame({ "X": [1, 2, 3, 4, 5], "Y": [1, 2, 3, 4, 5], "pitch": [10, 10, 20, 20, 30], }) layout_main = QVBoxLayout() self.sc = MplCanvas(self, width=5, height=4, dpi=100) color = cm.jet self.u, div = np.unique(df.pitch.values, return_inverse=True) self.div = div # 保存每个点对应的类别索引 colorlist = [colors.rgb2hex(color(i)) for i in np.linspace(1, 0, len(self.u))] cmap = ListedColormap(colorlist) self.scatter = self.sc.axes.scatter(df['X'], df['Y'], c=div, cmap=cmap) # 保存原始点颜色,用于状态恢复 self.original_facecolor = self.scatter.get_facecolors().copy() legend_handles, _ = self.scatter.legend_elements(prop='colors', num=len(colorlist)) legend = self.sc.axes.legend(legend_handles, self.u, loc=2) # 给所有图例项绑定拾取,建立图例和类别索引的映射 self.legend_to_idx = {} for idx, leg_item in enumerate(legend.get_lines()): leg_item.set_picker(True) leg_item.set_pickradius(10) self.legend_to_idx[leg_item] = idx self.scatter.figure.canvas.mpl_connect('pick_event', self.on_pick) toolbar = NavigationToolbar(self.sc, self) layout_main.addWidget(toolbar) layout_main.addWidget(self.sc) widget = QWidget() widget.setLayout(layout_main) self.setCentralWidget(widget) def on_pick(self, event): leg_artist = event.artist # 过滤非图例的拾取事件 if leg_artist not in self.legend_to_idx: return cat_idx = self.legend_to_idx[leg_artist] # 切换图例淡显状态 current_alpha = leg_artist.get_alpha() new_alpha = 0.2 if (current_alpha == 1.0 or current_alpha is None) else 1.0 leg_artist.set_alpha(new_alpha) # 切换对应类别点的显示状态 point_colors = self.scatter.get_facecolors() for point_idx, point_cat in enumerate(self.div): if point_cat == cat_idx: if new_alpha < 1: # 隐藏:将点透明度设为0 point_colors[point_idx, -1] = 0 else: # 显示:还原原始颜色 point_colors[point_idx] = self.original_facecolor[point_idx] self.scatter.set_facecolors(point_colors) # 重绘画布生效 self.sc.draw() app = QtWidgets.QApplication(sys.argv) window = MainWindow() window.show() app.exec_()
关键修改点说明
- 提前缓存每个点的类别索引、原始颜色参数,切换状态时可以直接还原,不会出现显示错乱
- 给所有图例项统一开启拾取,用字典绑定图例对象和对应类别索引的关系,点击时可以直接定位需要操作的点集合
- 单类点的隐藏通过修改对应点颜色的alpha通道实现,不需要拆分多个散点对象绘制,点量大的时候也能保持流畅
- 点击图例时同步修改图例自身透明度,0.2为淡显状态、1.0为正常状态,完全匹配预期交互逻辑
- 每次状态修改后主动调用画布重绘方法,确保界面即时更新
运行效果:点击任意图例项,对应分类的散点会完全隐藏,被点击的图例变为淡显状态;再次点击同个图例项,散点和图例都会恢复到初始显示状态。
内容的提问来源于stack exchange,提问作者Brian Kim
相关产品推荐
相关产品推荐

