步骤十页面修改
This commit is contained in:
@ -39,6 +39,23 @@ from PyQt5.QtCore import Qt, QThread, pyqtSignal
|
|||||||
from src.gui.components.custom_widgets import FileSelectWidget
|
from src.gui.components.custom_widgets import FileSelectWidget
|
||||||
from src.gui.styles import ModernStylesheet
|
from src.gui.styles import ModernStylesheet
|
||||||
|
|
||||||
|
# ==========================================
|
||||||
|
# 必须放在所有 import 的下方,class 的上方!
|
||||||
|
# ==========================================
|
||||||
|
CATEGORY_CHINESE_MAP = {
|
||||||
|
'Total_Suspended_Matter': '总悬浮物 (TSM)',
|
||||||
|
'Phycocyanin (BGA_PC)': '藻蓝蛋白 (PC)',
|
||||||
|
'Turbidity': '浊度 (Turbidity)',
|
||||||
|
'chlorophyll_a': '叶绿素a (Chl-a)',
|
||||||
|
'Colored_Dissolved_Organic_Matter': '有色可溶性有机物 (CDOM)',
|
||||||
|
'Secchi_Disk_Depth': '透明度 (SDD)',
|
||||||
|
'Total_Nitrogen': '总氮 (TN)',
|
||||||
|
'Total_Phosphorus': '总磷 (TP)',
|
||||||
|
'Chemical_Oxygen_Demand': '化学需氧量 (COD)',
|
||||||
|
'Ammonia_Nitrogen': '氨氮 (NH3-N)',
|
||||||
|
'Dissolved_Oxygen': '溶解氧 (DO)'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class WaterIndexWorker(QThread):
|
class WaterIndexWorker(QThread):
|
||||||
"""后台线程:散点 CSV → 逐行公式计算 → 多 CSV 输出
|
"""后台线程:散点 CSV → 逐行公式计算 → 多 CSV 输出
|
||||||
@ -99,6 +116,16 @@ class WaterIndexWorker(QThread):
|
|||||||
self.error.emit(f"{e}\n{traceback.format_exc()}")
|
self.error.emit(f"{e}\n{traceback.format_exc()}")
|
||||||
|
|
||||||
|
|
||||||
|
class NoScrollPassListWidget(QListWidget):
|
||||||
|
"""一个绝对不会把滚轮事件传给外层父组件的列表控件"""
|
||||||
|
def wheelEvent(self, event):
|
||||||
|
# 先执行原本正常的列表内部滚动逻辑
|
||||||
|
super().wheelEvent(event)
|
||||||
|
# 核心黑科技:强制接收该事件。
|
||||||
|
# 告诉 PyQt:"这个滚轮操作到我这里就结束了,无论如何不要传给外层的 ScrollArea"
|
||||||
|
event.accept()
|
||||||
|
|
||||||
|
|
||||||
class Step10WatercolorPanel(QWidget):
|
class Step10WatercolorPanel(QWidget):
|
||||||
"""步骤10:水色指数反演(散点 CSV 模式)"""
|
"""步骤10:水色指数反演(散点 CSV 模式)"""
|
||||||
|
|
||||||
@ -109,89 +136,141 @@ class Step10WatercolorPanel(QWidget):
|
|||||||
self._categories: List[str] = []
|
self._categories: List[str] = []
|
||||||
self._all_formulas: List[Dict] = []
|
self._all_formulas: List[Dict] = []
|
||||||
self._formula_list_widgets: Dict[str, QListWidgetItem] = {}
|
self._formula_list_widgets: Dict[str, QListWidgetItem] = {}
|
||||||
|
self._current_type_filter = "all" # 'all', 'ratio', 'concentration'
|
||||||
self.init_ui()
|
self.init_ui()
|
||||||
self._load_formulas()
|
self._load_formulas()
|
||||||
|
|
||||||
def init_ui(self):
|
def init_ui(self):
|
||||||
layout = QVBoxLayout()
|
layout = QVBoxLayout()
|
||||||
|
# 注意:这里彻底删除了顶部的 title 和 hint 变量,保持全局页面清爽
|
||||||
|
|
||||||
# ---- 标题 ----
|
# ==========================================
|
||||||
title = QLabel("步骤10:水色指数反演(散点 CSV 模式)")
|
# 卡片 1:输入数据配置
|
||||||
title.setFont(QFont("Arial", 12, QFont.Bold))
|
# ==========================================
|
||||||
layout.addWidget(title)
|
input_group = QGroupBox("📁 输入数据")
|
||||||
|
# 【关键修正1】必须用 QVBoxLayout,如果用 QFormLayout 会导致标签文字重复!
|
||||||
|
input_layout = QVBoxLayout()
|
||||||
|
input_layout.setSpacing(16)
|
||||||
|
input_layout.setContentsMargins(20, 24, 20, 20)
|
||||||
|
|
||||||
# ---- 说明 ----
|
self.formula_file = FileSelectWidget(
|
||||||
hint = QLabel(
|
"内置公式库源:",
|
||||||
"读取 Step 4 生成的 sampling_spectra.csv 散点光谱,"
|
"CSV Files (*.csv);;All Files (*.*)"
|
||||||
"对每个采样点逐行套用 waterindex.csv 中勾选的公式,"
|
|
||||||
"输出每公式一个 CSV(列:longitude, latitude, 公式值)。"
|
|
||||||
"结果可被 Step 11 直接以 ContentMapper 模式消费。"
|
|
||||||
)
|
)
|
||||||
hint.setWordWrap(True)
|
self.formula_file.line_edit.setReadOnly(True)
|
||||||
hint.setStyleSheet(f"color: {ModernStylesheet.COLORS.get('text_secondary', '#666')};")
|
self.formula_file.label.setMinimumWidth(100)
|
||||||
layout.addWidget(hint)
|
builtin_csv = self._find_waterindex_csv()
|
||||||
|
if builtin_csv:
|
||||||
# ---- 输入采样点数据 ----
|
self.formula_file.set_path(builtin_csv)
|
||||||
input_group = QGroupBox("输入采样点数据")
|
input_layout.addWidget(self.formula_file)
|
||||||
input_layout = QFormLayout()
|
|
||||||
|
|
||||||
self.sampling_csv_file = FileSelectWidget(
|
self.sampling_csv_file = FileSelectWidget(
|
||||||
"采样点 CSV:",
|
"采样点 CSV:",
|
||||||
"CSV Files (*.csv);;All Files (*.*)"
|
"CSV Files (*.csv);;All Files (*.*)"
|
||||||
)
|
)
|
||||||
self.sampling_csv_file.line_edit.setPlaceholderText(
|
self.sampling_csv_file.label.setMinimumWidth(100)
|
||||||
"选择 Step 4 输出的 sampling_spectra.csv"
|
input_layout.addWidget(self.sampling_csv_file)
|
||||||
)
|
|
||||||
input_layout.addRow("采样点 CSV:", self.sampling_csv_file)
|
|
||||||
|
|
||||||
# 数据规模提示(运行后回填,避免启动时强制 read_csv)
|
# 注意:彻底去掉了 self.meta_label 及其相关的布局代码
|
||||||
self.meta_label = QLabel("未加载采样点数据")
|
|
||||||
self.meta_label.setStyleSheet(
|
|
||||||
"background: #f0f0f0; padding: 4px 8px; border-radius: 4px; "
|
|
||||||
"font-size: 12px; color: #333;"
|
|
||||||
)
|
|
||||||
input_layout.addRow("数据信息:", self.meta_label)
|
|
||||||
|
|
||||||
input_group.setLayout(input_layout)
|
input_group.setLayout(input_layout)
|
||||||
layout.addWidget(input_group)
|
layout.addWidget(input_group)
|
||||||
|
|
||||||
# ---- 公式选择 ----
|
# ---- 公式选择 ----
|
||||||
formula_group = QGroupBox("公式选择")
|
formula_group = QGroupBox("公式选择")
|
||||||
formula_layout = QGridLayout()
|
formula_layout = QVBoxLayout()
|
||||||
|
formula_layout.setSpacing(16)
|
||||||
|
formula_layout.setContentsMargins(20, 24, 20, 20)
|
||||||
|
|
||||||
# 类别过滤
|
# 顶部工具栏 (筛选 + 按钮)
|
||||||
formula_layout.addWidget(QLabel("按类别筛选:"), 0, 0)
|
toolbar_layout = QHBoxLayout()
|
||||||
|
toolbar_layout.setContentsMargins(0, 0, 0, 0)
|
||||||
|
toolbar_layout.setSpacing(10)
|
||||||
|
|
||||||
|
toolbar_layout.addWidget(QLabel("按类别筛选:"))
|
||||||
self.category_combo = QComboBox()
|
self.category_combo = QComboBox()
|
||||||
self.category_combo.currentTextChanged.connect(self._on_category_changed)
|
self.category_combo.setStyleSheet("""
|
||||||
formula_layout.addWidget(self.category_combo, 0, 1, 1, 2)
|
QComboBox { padding: 4px 8px; border: 1px solid #C0C0C0; border-radius: 4px; min-height: 24px; }
|
||||||
|
""")
|
||||||
|
self.category_combo.currentIndexChanged.connect(self._on_category_changed)
|
||||||
|
toolbar_layout.addWidget(self.category_combo)
|
||||||
|
toolbar_layout.addSpacing(20)
|
||||||
|
|
||||||
# 全选/取消全选
|
|
||||||
select_btn_layout = QHBoxLayout()
|
|
||||||
self.select_all_btn = QPushButton("全选")
|
self.select_all_btn = QPushButton("全选")
|
||||||
self.select_all_btn.setMaximumWidth(80)
|
|
||||||
self.select_all_btn.clicked.connect(self._select_all)
|
|
||||||
select_btn_layout.addWidget(self.select_all_btn)
|
|
||||||
|
|
||||||
self.deselect_all_btn = QPushButton("取消全选")
|
self.deselect_all_btn = QPushButton("取消全选")
|
||||||
self.deselect_all_btn.setMaximumWidth(80)
|
self.select_ratio_btn = QPushButton("仅比值型")
|
||||||
self.deselect_all_btn.clicked.connect(self._deselect_all)
|
self.select_conc_btn = QPushButton("仅浓度型")
|
||||||
select_btn_layout.addWidget(self.deselect_all_btn)
|
|
||||||
select_btn_layout.addStretch()
|
|
||||||
formula_layout.addLayout(select_btn_layout, 0, 3)
|
|
||||||
|
|
||||||
# 公式列表
|
# 强制指定标准的按钮样式,彻底解决没有边框的问题
|
||||||
self.formula_list = QListWidget()
|
btn_style = """
|
||||||
self.formula_list.setSelectionMode(QAbstractItemView.MultiSelection)
|
QPushButton { background-color: #FFFFFF; border: 1px solid #C0C0C0; border-radius: 4px; padding: 4px 16px; color: #333333; }
|
||||||
self.formula_list.setMinimumHeight(200)
|
QPushButton:hover { background-color: #F0F0F0; border: 1px solid #0078D7; }
|
||||||
self.formula_list.itemChanged.connect(self._on_item_changed)
|
QPushButton:pressed { background-color: #E0E0E0; }
|
||||||
formula_layout.addWidget(self.formula_list, 1, 0, 1, 4)
|
"""
|
||||||
|
# 把新按钮也加进循环里应用样式
|
||||||
|
for btn in [self.select_all_btn, self.deselect_all_btn, self.select_ratio_btn, self.select_conc_btn]:
|
||||||
|
btn.setStyleSheet(btn_style)
|
||||||
|
btn.setFixedHeight(28)
|
||||||
|
btn.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)
|
||||||
|
|
||||||
|
self.select_all_btn.clicked.connect(self._select_all)
|
||||||
|
self.deselect_all_btn.clicked.connect(self._deselect_all)
|
||||||
|
# 绑定新按钮的事件
|
||||||
|
self.select_ratio_btn.clicked.connect(self._select_ratio)
|
||||||
|
self.select_conc_btn.clicked.connect(self._select_conc)
|
||||||
|
|
||||||
|
toolbar_layout.addWidget(self.select_all_btn)
|
||||||
|
toolbar_layout.addWidget(self.deselect_all_btn)
|
||||||
|
toolbar_layout.addWidget(self.select_ratio_btn)
|
||||||
|
toolbar_layout.addWidget(self.select_conc_btn)
|
||||||
|
toolbar_layout.addStretch()
|
||||||
|
|
||||||
|
# 新增统计标签,直观显示公式数量
|
||||||
|
self.formula_count_label = QLabel("共 0 个公式")
|
||||||
|
self.formula_count_label.setStyleSheet("color: #666666; font-size: 13px; font-weight: bold; padding-right: 10px;")
|
||||||
|
toolbar_layout.addWidget(self.formula_count_label)
|
||||||
|
|
||||||
|
formula_layout.addLayout(toolbar_layout)
|
||||||
|
|
||||||
|
# 列表框 (使用刚刚定义的防穿透自定义类)
|
||||||
|
self.formula_list = NoScrollPassListWidget()
|
||||||
|
self.formula_list.setMinimumHeight(240)
|
||||||
|
self.formula_list.setWordWrap(True) # 开启自动换行,防止公式过长被截断
|
||||||
|
|
||||||
|
# 关键优化:取消原生选中的蓝色渐变框,放大复选框
|
||||||
|
self.formula_list.setStyleSheet("""
|
||||||
|
QListWidget {
|
||||||
|
border: 1px solid #D1D5DB; border-radius: 6px; outline: none; background-color: #FFFFFF;
|
||||||
|
}
|
||||||
|
QListWidget::item {
|
||||||
|
padding: 10px 8px; border-bottom: 1px solid #F3F4F6;
|
||||||
|
}
|
||||||
|
QListWidget::item:hover {
|
||||||
|
background-color: #F9FAFB;
|
||||||
|
}
|
||||||
|
/* 重点:彻底屏蔽原生的深蓝色选中渐变 */
|
||||||
|
QListWidget::item:selected {
|
||||||
|
background-color: transparent; color: #333333;
|
||||||
|
}
|
||||||
|
QListWidget::indicator {
|
||||||
|
width: 18px; height: 18px; margin-right: 8px;
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
# 绑定新方法:实现整行点击打勾
|
||||||
|
self.formula_list.itemSelectionChanged.connect(self._on_selection_changed)
|
||||||
|
formula_layout.addWidget(self.formula_list)
|
||||||
|
|
||||||
formula_group.setLayout(formula_layout)
|
formula_group.setLayout(formula_layout)
|
||||||
layout.addWidget(formula_group)
|
layout.addWidget(formula_group)
|
||||||
|
|
||||||
# ---- 输出设置 ----
|
# ==========================================
|
||||||
|
# 卡片 3:输出设置
|
||||||
|
# ==========================================
|
||||||
output_group = QGroupBox("输出设置")
|
output_group = QGroupBox("输出设置")
|
||||||
output_layout = QFormLayout()
|
# 【关键修正2】改成 QVBoxLayout,消除重复的"输出目录:"
|
||||||
|
output_layout = QVBoxLayout()
|
||||||
|
output_layout.setSpacing(16)
|
||||||
|
output_layout.setContentsMargins(20, 24, 20, 20)
|
||||||
|
|
||||||
self.output_dir = FileSelectWidget(
|
self.output_dir = FileSelectWidget(
|
||||||
"输出目录:",
|
"输出目录:",
|
||||||
@ -200,7 +279,7 @@ class Step10WatercolorPanel(QWidget):
|
|||||||
self.output_dir.line_edit.setPlaceholderText(
|
self.output_dir.line_edit.setPlaceholderText(
|
||||||
"留空 → 工作目录/10_WaterIndex_CSV"
|
"留空 → 工作目录/10_WaterIndex_CSV"
|
||||||
)
|
)
|
||||||
output_layout.addRow("输出目录:", self.output_dir)
|
output_layout.addWidget(self.output_dir)
|
||||||
|
|
||||||
output_group.setLayout(output_layout)
|
output_group.setLayout(output_layout)
|
||||||
layout.addWidget(output_group)
|
layout.addWidget(output_group)
|
||||||
@ -217,15 +296,33 @@ class Step10WatercolorPanel(QWidget):
|
|||||||
self.progress_label.setStyleSheet("font-size: 11px; color: #666;")
|
self.progress_label.setStyleSheet("font-size: 11px; color: #666;")
|
||||||
layout.addWidget(self.progress_label)
|
layout.addWidget(self.progress_label)
|
||||||
|
|
||||||
# ---- 启用 & 运行 ----
|
# ==========================================
|
||||||
self.enable_checkbox = QCheckBox("启用此步骤")
|
# 卡片 4:输出与执行 (与进度显示合并)
|
||||||
self.enable_checkbox.setChecked(True)
|
# ==========================================
|
||||||
layout.addWidget(self.enable_checkbox)
|
execute_group = QGroupBox("🚀 输出与执行")
|
||||||
|
execute_layout = QVBoxLayout()
|
||||||
|
execute_layout.setSpacing(16)
|
||||||
|
execute_layout.setContentsMargins(20, 24, 20, 20)
|
||||||
|
|
||||||
self.run_btn = QPushButton("▶ 执行水色指数反演")
|
# 把原本悬空的进度条放进这个卡片里,看起来更整洁
|
||||||
self.run_btn.setStyleSheet(ModernStylesheet.get_button_stylesheet('success'))
|
execute_layout.addWidget(self.progress_bar)
|
||||||
self.run_btn.clicked.connect(self.run_step)
|
execute_layout.addWidget(self.progress_label)
|
||||||
layout.addWidget(self.run_btn)
|
|
||||||
|
# 完美对齐的底部按钮栏(彻底移除了 enable_checkbox)
|
||||||
|
action_layout = QHBoxLayout()
|
||||||
|
action_layout.addStretch()
|
||||||
|
|
||||||
|
self.run_btn = QPushButton("独立运行步骤")
|
||||||
|
self.run_btn.setStyleSheet(ModernStylesheet.get_button_stylesheet('primary'))
|
||||||
|
self.run_btn.setMinimumWidth(140)
|
||||||
|
# 统一改成连接到标准的 EventBus 方法
|
||||||
|
self.run_btn.clicked.connect(self._on_run_single_clicked)
|
||||||
|
action_layout.addWidget(self.run_btn)
|
||||||
|
|
||||||
|
execute_layout.addLayout(action_layout)
|
||||||
|
execute_group.setLayout(execute_layout)
|
||||||
|
|
||||||
|
layout.addWidget(execute_group)
|
||||||
|
|
||||||
layout.addStretch()
|
layout.addStretch()
|
||||||
self.setLayout(layout)
|
self.setLayout(layout)
|
||||||
@ -244,7 +341,6 @@ class Step10WatercolorPanel(QWidget):
|
|||||||
def _load_formulas(self):
|
def _load_formulas(self):
|
||||||
"""加载 waterindex.csv 中的公式"""
|
"""加载 waterindex.csv 中的公式"""
|
||||||
if not self._waterindex_csv or not Path(self._waterindex_csv).exists():
|
if not self._waterindex_csv or not Path(self._waterindex_csv).exists():
|
||||||
self.meta_label.setText("⚠️ waterindex.csv 未找到")
|
|
||||||
return
|
return
|
||||||
|
|
||||||
import csv
|
import csv
|
||||||
@ -252,37 +348,44 @@ class Step10WatercolorPanel(QWidget):
|
|||||||
try:
|
try:
|
||||||
with open(self._waterindex_csv, 'r', encoding='utf-8-sig') as f:
|
with open(self._waterindex_csv, 'r', encoding='utf-8-sig') as f:
|
||||||
reader = csv.DictReader(f)
|
reader = csv.DictReader(f)
|
||||||
self._all_formulas = list(reader)
|
for row in reader:
|
||||||
|
# 关键修复1:强制清理 CSV 键值中的所有前后空格,防止匹配失败导致丢失
|
||||||
|
cleaned_row = {str(k).strip(): str(v).strip() for k, v in row.items() if k}
|
||||||
|
self._all_formulas.append(cleaned_row)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.meta_label.setText(f"⚠️ 加载公式失败: {e}")
|
QMessageBox.critical(self, "读取失败", f"加载公式失败: {e}")
|
||||||
return
|
return
|
||||||
|
|
||||||
# 提取所有类别
|
# 提取所有类别
|
||||||
cats = set()
|
cats = set()
|
||||||
for f in self._all_formulas:
|
for f in self._all_formulas:
|
||||||
c = f.get('Category', '').strip()
|
c = f.get('Category', '')
|
||||||
if c:
|
if c:
|
||||||
cats.add(c)
|
cats.add(c)
|
||||||
|
|
||||||
self._categories = sorted(cats)
|
self._categories = sorted(cats)
|
||||||
|
self.category_combo.blockSignals(True)
|
||||||
self.category_combo.clear()
|
self.category_combo.clear()
|
||||||
self.category_combo.addItem("全部")
|
self.category_combo.addItem("全部", "全部")
|
||||||
self.category_combo.addItems(self._categories)
|
for cat in self._categories:
|
||||||
|
display_name = CATEGORY_CHINESE_MAP.get(cat, cat)
|
||||||
|
self.category_combo.addItem(display_name, cat)
|
||||||
|
self.category_combo.blockSignals(False)
|
||||||
|
|
||||||
self._populate_list("全部")
|
# 关键修复2:不再动态渲染,而是一次性初始化所有公式
|
||||||
|
self._init_list_items()
|
||||||
|
|
||||||
def _populate_list(self, category: str):
|
def _init_list_items(self):
|
||||||
"""根据类别填充公式列表"""
|
"""一次性加载所有公式项,按照类别进行排序"""
|
||||||
|
from PyQt5.QtGui import QColor
|
||||||
self.formula_list.clear()
|
self.formula_list.clear()
|
||||||
self._formula_list_widgets.clear()
|
self._formula_list_widgets.clear()
|
||||||
|
|
||||||
formulas_to_show = (
|
# 核心优化:对 self._all_formulas 进行自定义排序
|
||||||
[f for f in self._all_formulas if f.get('Category', '') == category]
|
# 排序规则:主要按 Category (类别/物质) 排,同类别下按 Formula_Name 排
|
||||||
if category != "全部"
|
sorted_formulas = sorted(self._all_formulas, key=lambda x: (x.get('Category', ''), x.get('Formula_Name', '')))
|
||||||
else self._all_formulas
|
|
||||||
)
|
|
||||||
|
|
||||||
for f in formulas_to_show:
|
for f in sorted_formulas:
|
||||||
name = f.get('Formula_Name', '')
|
name = f.get('Formula_Name', '')
|
||||||
formula_str = f.get('Formula', '')
|
formula_str = f.get('Formula', '')
|
||||||
cat = f.get('Category', '')
|
cat = f.get('Category', '')
|
||||||
@ -291,52 +394,117 @@ class Step10WatercolorPanel(QWidget):
|
|||||||
item = QListWidgetItem()
|
item = QListWidgetItem()
|
||||||
item.setFlags(item.flags() | Qt.ItemIsUserCheckable)
|
item.setFlags(item.flags() | Qt.ItemIsUserCheckable)
|
||||||
item.setCheckState(Qt.Checked)
|
item.setCheckState(Qt.Checked)
|
||||||
|
|
||||||
|
# 存入隐式数据供后续逻辑使用
|
||||||
item.setData(Qt.UserRole, name)
|
item.setData(Qt.UserRole, name)
|
||||||
item.setText(
|
item.setData(Qt.UserRole + 1, cat)
|
||||||
f"☑ {name} [{cat}] ({ftype})\n {formula_str}"
|
item.setData(Qt.UserRole + 2, ftype) # 存入类型,供"仅比值型/仅浓度型"按钮区分
|
||||||
)
|
|
||||||
item.setToolTip(f"{name}\n{category}\n{formula_str}")
|
cat_cn = CATEGORY_CHINESE_MAP.get(cat, cat)
|
||||||
|
ftype_cn = "比值型" if ftype == 'ratio' else "浓度型"
|
||||||
|
|
||||||
|
item.setText(f"【{cat_cn}】 {name} ({ftype_cn})\n公式: {formula_str}")
|
||||||
|
item.setToolTip(f"{name}\n{cat_cn}\n{formula_str}")
|
||||||
|
|
||||||
|
bg_color = QColor("#FFFFFF") if ftype == 'ratio' else QColor("#E8F4F8")
|
||||||
|
item.setBackground(bg_color)
|
||||||
|
|
||||||
self.formula_list.addItem(item)
|
self.formula_list.addItem(item)
|
||||||
self._formula_list_widgets[name] = item
|
self._formula_list_widgets[name] = item
|
||||||
|
|
||||||
def _on_category_changed(self, category: str):
|
self.formula_count_label.setText(f"共加载 {self.formula_list.count()} 个公式")
|
||||||
self._populate_list(category)
|
|
||||||
|
def _refresh_visibility(self):
|
||||||
|
"""统一管理公式列表的可见性(综合物质类别 + 公式类型)"""
|
||||||
|
# 获取当前选中的物质类别
|
||||||
|
current_category = self.category_combo.currentData()
|
||||||
|
if not current_category:
|
||||||
|
current_category = "全部"
|
||||||
|
|
||||||
|
visible_count = 0
|
||||||
|
|
||||||
|
for i in range(self.formula_list.count()):
|
||||||
|
item = self.formula_list.item(i)
|
||||||
|
item_cat = item.data(Qt.UserRole + 1)
|
||||||
|
item_type = item.data(Qt.UserRole + 2)
|
||||||
|
|
||||||
|
# 条件1:物质类别匹配
|
||||||
|
cat_match = (current_category == "全部" or item_cat == current_category)
|
||||||
|
# 条件2:公式类型匹配
|
||||||
|
type_match = (self._current_type_filter == "all" or item_type == self._current_type_filter)
|
||||||
|
|
||||||
|
if cat_match and type_match:
|
||||||
|
item.setHidden(False)
|
||||||
|
visible_count += 1
|
||||||
|
else:
|
||||||
|
item.setHidden(True)
|
||||||
|
|
||||||
|
# 更新统计标签(如果选了特定类型,标签上体现出来)
|
||||||
|
type_str = ""
|
||||||
|
if self._current_type_filter == 'ratio':
|
||||||
|
type_str = " (仅比值型)"
|
||||||
|
elif self._current_type_filter == 'concentration':
|
||||||
|
type_str = " (仅浓度型)"
|
||||||
|
|
||||||
|
self.formula_count_label.setText(f"当前显示: {visible_count} 个{type_str}")
|
||||||
|
|
||||||
|
def _on_category_changed(self, index: int):
|
||||||
|
# 切换物质类别时,重置类型过滤器(可选:如果不重置,就能叠加筛选。这里我们选择重置,体验更好)
|
||||||
|
self._current_type_filter = "all"
|
||||||
|
self._refresh_visibility()
|
||||||
|
|
||||||
def _select_all(self):
|
def _select_all(self):
|
||||||
for item in self.formula_list.selectedItems():
|
"""恢复显示所有当前类别的公式,并全部勾选"""
|
||||||
item.setCheckState(Qt.Checked)
|
self._current_type_filter = "all"
|
||||||
# 也全选当前显示的
|
self._refresh_visibility()
|
||||||
|
|
||||||
for i in range(self.formula_list.count()):
|
for i in range(self.formula_list.count()):
|
||||||
it = self.formula_list.item(i)
|
item = self.formula_list.item(i)
|
||||||
it.setCheckState(Qt.Checked)
|
if not item.isHidden():
|
||||||
|
item.setCheckState(Qt.Checked)
|
||||||
|
|
||||||
def _deselect_all(self):
|
def _deselect_all(self):
|
||||||
|
"""保留当前显示状态,但全部取消勾选"""
|
||||||
for i in range(self.formula_list.count()):
|
for i in range(self.formula_list.count()):
|
||||||
it = self.formula_list.item(i)
|
item = self.formula_list.item(i)
|
||||||
it.setCheckState(Qt.Unchecked)
|
if not item.isHidden():
|
||||||
|
item.setCheckState(Qt.Unchecked)
|
||||||
|
|
||||||
|
def _select_ratio(self):
|
||||||
|
"""过滤出比值型并全部勾选"""
|
||||||
|
self._current_type_filter = "ratio"
|
||||||
|
self._refresh_visibility()
|
||||||
|
|
||||||
|
for i in range(self.formula_list.count()):
|
||||||
|
item = self.formula_list.item(i)
|
||||||
|
if not item.isHidden():
|
||||||
|
item.setCheckState(Qt.Checked)
|
||||||
|
|
||||||
|
def _select_conc(self):
|
||||||
|
"""过滤出浓度型并全部勾选"""
|
||||||
|
self._current_type_filter = "concentration"
|
||||||
|
self._refresh_visibility()
|
||||||
|
|
||||||
|
for i in range(self.formula_list.count()):
|
||||||
|
item = self.formula_list.item(i)
|
||||||
|
if not item.isHidden():
|
||||||
|
item.setCheckState(Qt.Checked)
|
||||||
|
|
||||||
|
def _on_selection_changed(self):
|
||||||
|
"""实现点击整行任意位置即可切换勾选状态"""
|
||||||
|
# 暂时阻塞信号,防止死循环
|
||||||
|
self.formula_list.blockSignals(True)
|
||||||
|
for item in self.formula_list.selectedItems():
|
||||||
|
# 翻转勾选状态
|
||||||
|
new_state = Qt.Unchecked if item.checkState() == Qt.Checked else Qt.Checked
|
||||||
|
item.setCheckState(new_state)
|
||||||
|
# 立即清除选中高亮状态,保持视觉干净
|
||||||
|
item.setSelected(False)
|
||||||
|
self.formula_list.blockSignals(False)
|
||||||
|
|
||||||
def _on_item_changed(self, item: QListWidgetItem):
|
def _on_item_changed(self, item: QListWidgetItem):
|
||||||
pass # 可扩展:实时统计选中数量
|
pass # 可扩展:实时统计选中数量
|
||||||
|
|
||||||
def _refresh_sampling_meta(self):
|
|
||||||
"""从 sampling_csv 路径快速 peek 数据规模(不触发公式计算)"""
|
|
||||||
path = self.sampling_csv_file.get_path().strip()
|
|
||||||
if not path:
|
|
||||||
self.meta_label.setText("未加载采样点数据")
|
|
||||||
return
|
|
||||||
if not Path(path).exists():
|
|
||||||
self.meta_label.setText("⚠️ 采样点 CSV 不存在")
|
|
||||||
return
|
|
||||||
try:
|
|
||||||
import pandas as pd
|
|
||||||
df = pd.read_csv(path, encoding="utf-8-sig", nrows=0)
|
|
||||||
n_cols = len(df.columns)
|
|
||||||
self.meta_label.setText(
|
|
||||||
f"✅ 已选采样点 CSV({n_cols} 列,完整列数将在运行时打印)"
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
self.meta_label.setText(f"⚠️ 读取失败: {e}")
|
|
||||||
|
|
||||||
def _get_selected_formula_names(self) -> List[str]:
|
def _get_selected_formula_names(self) -> List[str]:
|
||||||
names = []
|
names = []
|
||||||
for i in range(self.formula_list.count()):
|
for i in range(self.formula_list.count()):
|
||||||
@ -369,7 +537,6 @@ class Step10WatercolorPanel(QWidget):
|
|||||||
def set_config(self, config: dict):
|
def set_config(self, config: dict):
|
||||||
if config.get('sampling_csv_path'):
|
if config.get('sampling_csv_path'):
|
||||||
self.sampling_csv_file.set_path(config['sampling_csv_path'])
|
self.sampling_csv_file.set_path(config['sampling_csv_path'])
|
||||||
self._refresh_sampling_meta()
|
|
||||||
if config.get('output_dir'):
|
if config.get('output_dir'):
|
||||||
self.output_dir.set_path(config['output_dir'])
|
self.output_dir.set_path(config['output_dir'])
|
||||||
if 'selected_formulas' in config:
|
if 'selected_formulas' in config:
|
||||||
@ -425,7 +592,6 @@ class Step10WatercolorPanel(QWidget):
|
|||||||
self.work_dir or '', sampling_path
|
self.work_dir or '', sampling_path
|
||||||
).replace('\\', '/')
|
).replace('\\', '/')
|
||||||
self.sampling_csv_file.set_path(sampling_path)
|
self.sampling_csv_file.set_path(sampling_path)
|
||||||
self._refresh_sampling_meta()
|
|
||||||
|
|
||||||
# 自动填入输出目录(默认 work_dir/10_WaterIndex_CSV/)
|
# 自动填入输出目录(默认 work_dir/10_WaterIndex_CSV/)
|
||||||
if self.work_dir:
|
if self.work_dir:
|
||||||
|
|||||||
Reference in New Issue
Block a user