feat(nmNum): 增加拟合参数结果展示

- 内存保留每组最优参数,结果表增加拟合参数列。
- 直方图和散点图支持切换拟合参数。
- 基准点使用求解快照,误差轴保留阈值筛选。
feature/Sensitivity-20261012
lvjunjie 7 days ago
parent 67101e5747
commit 2384ddb76a

@ -149,7 +149,7 @@ class NMCALCULATION_EXPORT nmCalculationDllPebiSolverTask : public QThread {
/** @brief 复用当前独立副本串行计算,保留进度、停止状态及每组失败原因。 */
bool executeSensitivityBatch();
/** @brief 固定本组采样参数,在独立输入上拟合并生成最终曲线。 */
bool improveSensitivityRun();
bool improveSensitivityRun(QVector<double>& fittedParameters);
/** @brief 执行当前唯一的 PEBI 求解模式。 */
bool execute();
/** @brief 成对释放构造时登记的 DataManager 后台使用权。 */

@ -106,6 +106,7 @@ public:
RunResult() : m_nRunIndex(0), m_dPressureRmse(-1.0), m_bSucceeded(false), m_bCancelled(false), m_bVisible(true) {}
quint64 m_nRunIndex;
QVector<double> m_vecParameters;
QVector<double> m_vecFittedParameters; // 本组 LM 最优值,与批次拟合参数顺序一致,仅存内存。
QVector<QVector<double> > m_vecPressure;
QVector<QVector<double> > m_vecLogLog;
QVector<QVector<double> > m_vecSemiLog;
@ -118,9 +119,10 @@ public:
struct NM_DATA_EXPORT BatchResult
{
BatchResult() : m_calcType(CALC_DETERMINISTIC), m_dFilterThreshold(-1.0), m_bCompleted(false), m_bStopped(false) {}
BatchResult() : m_calcType(CALC_DETERMINISTIC), m_dFilterThreshold(-1.0), m_nHistogramVariable(-1), m_bCompleted(false), m_bStopped(false) {}
CalculationType m_calcType;
QList<VariableSampling> m_variables; // 求解时冻结的配置,独立于之后的参数编辑。
QList<nmDataAttribute> m_fitParameters; // 名称、基准单位和求解快照中的模型值,仅存内存。
QString m_sWellCode;
QString m_sWellName;
QString m_sAxisXName;
@ -132,6 +134,8 @@ public:
std::vector<RunResult> m_runs;
// 散点筛选阈值只随当前批次保存在内存中,负值表示尚未筛选。
double m_dFilterThreshold;
int m_nHistogramVariable; // -1 为误差,其余为拟合参数下标,仅存内存。
bool statisticValue(const RunResult& run, int variable, double& value) const;
bool filteredParameterBounds(int index, double& minimum, double& maximum) const;
bool m_bCompleted;
bool m_bStopped;

@ -173,7 +173,7 @@ class NM_SUB_WND_EXPORT nmSubWndMain : public iSubWndBaseFit {
/** @brief 刷新主界面的误差直方图,修改分箱数时复用当前批次结果。 */
void updateSensitiveHistogramPlot();
void onSensitiveHistogramOptions();
void onSensitiveHistogramBinsChanged(int bins);
void onSensitiveHistogramSettingsChanged(int bins, int variable);
void onSensitiveScatter();
/** @brief 当前分析的建网提交结束后,恢复待启动或待捕获的求解流程。 */
void onPebiGridGenerationFinished(quint64 nGridInputRevision,

@ -35,12 +35,13 @@ public:
nmWxSensitivityHistogramOptions(const QSharedPointer<nmDataSensitive::BatchResult>& results,
int bins, QWidget* parent);
signals:
void binsChanged(int bins);
void settingsChanged(int bins, int variable);
private slots:
void onApply();
void onAccept();
private:
QSpinBox* m_bins;
QComboBox* m_variable;
};
class NM_SUB_WXS_EXPORT nmWxSensitivityScatter : public iDlgBase
@ -65,6 +66,7 @@ private:
double m_defaultThreshold;
bool m_draggingThreshold;
QComboBox* m_parameter;
QComboBox* m_ordinate;
double m_threshold;
QLabel* m_legend;
QAction* m_resultsInPlot;

@ -28,6 +28,8 @@
#include "nmDataPvtParaForPebi.h"
#include <QDebug>
#include <QCoreApplication>
#include <limits>
#include <QHash>
#include <QMutexLocker>
#include <QScopedPointer>
@ -991,6 +993,29 @@ nmCalculationDllPebiSolverTask* nmCalculationDllPebiSolverTask::createSensitivit
task->m_pSensitivityResult->m_sWellCode = task->m_sAutoFitTargetWellCode;
if(m_pDataManager != nullptr) {
const QSharedPointer<const nmPebiResultSnapshot> solved = m_pDataManager->getPebiResultSnapshot();
// 拟合参数的蓝色基准点来自原模型,不使用设置窗口中可编辑的拟合初值。
if(improveSettings && !solved.isNull()) {
const nmDataAutomaticFitting& fit = improveSettings->parameters;
const bool selected[] = { fit.getPermeabilitySelected(), fit.getSkinSelected(),
fit.getWellboreStorageSelected(), fit.getPorositySelected(),
fit.getFractureConductivitySelected(), fit.getFractureHalfLengthSelected() };
const char* names[] = { "Permeability", "Skin", "Wellbore storage", "Porosity",
"Fracture conductivity", "Fracture half length" };
const char* units[] = { "mD", "", "m^3/MPa", "", "mD.m", "m" };
for(int i = 0; i < solved->getWellCount(); ++i) {
const nmPebiResultWellSnapshot* well = solved->getWellAt(i);
if(well->m_sWellName != improveSettings->targetWell) continue;
double halfLength = std::numeric_limits<double>::quiet_NaN();
if(selected[5]) getSolvedFractureHalfLength(solved->getSensitivityInput(), well->m_sWellCode, halfLength);
const double values[] = { m_pInputSnapshot->m_oStaticPropertyInput.m_dReferencePermeabilityMilliDarcy,
well->m_dSkin, well->m_dWellboreStorage,
m_pInputSnapshot->m_oStaticPropertyInput.m_dReferencePorosity, well->m_dDfc, halfLength };
for(int j = 0; j < 6; ++j) if(selected[j])
task->m_pSensitivityResult->m_fitParameters.append(nmDataAttribute(
QCoreApplication::translate("nmWxAutomaticFitting", names[j]), values[j], units[j]));
break;
}
}
for(int i = 0; !solved.isNull() && i < solved->getWellCount(); ++i) {
const nmPebiResultWellSnapshot* well = solved->getWellAt(i);
if(well->m_sWellCode != task->m_sAutoFitTargetWellCode) continue;
@ -1065,7 +1090,7 @@ bool nmCalculationDllPebiSolverTask::getSolvedFractureHalfLength(
return false;
}
bool nmCalculationDllPebiSolverTask::improveSensitivityRun()
bool nmCalculationDllPebiSolverTask::improveSensitivityRun(QVector<double>& fittedParameters)
{
// 采样值已经写入本任务;候选只覆盖选中的拟合参数,不触碰管理器和正式结果。
const nmSensitivityFitSettings& settings = *m_pSensitivityFitSettings;
@ -1172,6 +1197,8 @@ bool nmCalculationDllPebiSolverTask::improveSensitivityRun()
return fail(error.isEmpty() ? tr("No valid fitting result was obtained.") : error);
}
evaluate(best);
// 最优参数与最终曲线一同返回,失败组不留下可显示的拟合值。
if(m_lastRunSucceeded) fittedParameters = best;
return m_lastRunSucceeded;
}
@ -1210,7 +1237,7 @@ bool nmCalculationDllPebiSolverTask::executeSensitivityBatch()
calculateSingleRun();
result.m_bSucceeded = m_lastRunSucceeded;
} else {
result.m_bSucceeded = improveSensitivityRun();
result.m_bSucceeded = improveSensitivityRun(result.m_vecFittedParameters);
}
if(result.m_bSucceeded) {
result.m_vecPressure = m_autoFitResultPressure;
@ -1235,6 +1262,7 @@ bool nmCalculationDllPebiSolverTask::executeSensitivityBatch()
result.m_vecLogLog.clear();
result.m_vecSemiLog.clear();
result.m_dPressureRmse = -1.0;
result.m_vecFittedParameters.clear();
} else if(!result.m_bSucceeded) {
++failed;
}

@ -5,6 +5,19 @@
#include <QDateTime>
#include <QtCore/qnumeric.h>
bool nmDataSensitive::BatchResult::statisticValue(const RunResult& run, int variable, double& value) const
{
// 表格、直方图和散点图共用有效性判断,拟合参数不依赖压力误差是否可用。
if(!run.m_bSucceeded || run.m_bCancelled) return false;
if(variable == -1) {
value = run.m_dPressureRmse;
return qIsFinite(value) && value >= 0.0;
}
if(variable < 0 || variable >= m_fitParameters.size() || variable >= run.m_vecFittedParameters.size()) return false;
value = run.m_vecFittedParameters[variable];
return qIsFinite(value);
}
bool nmDataSensitive::BatchResult::filteredParameterBounds(int index, double& minimum, double& maximum) const
{
// 所有参数共用同一批误差严格小于阈值的样本,不受曲线可见性影响。

@ -2231,32 +2231,37 @@ void nmSubWndMain::updateSensitiveHistogramPlot()
}
iGuiPlot* plot = window->widget()->findChild<iGuiPlot*>();
const int binCount = window->widget()->property("nmHistogramBins").toInt();
QVector<double> errors;
// 拟合参数与误差共用分箱绘制,轴名称和单位随本批选择同步切换。
const int variable = batch->m_nHistogramVariable;
plot->setAxisX(variable < 0 ? tr("Goodness of fit") : batch->m_fitParameters[variable].getName(),
variable < 0 ? "MPa" : batch->m_fitParameters[variable].getUnit());
QVector<double> values;
for(size_t i = 0; i < batch->m_runs.size(); ++i) {
const nmDataSensitive::RunResult& run = batch->m_runs[i];
// 显示勾选不改变统计总体,失败、取消或无有效误差的运行不参与统计。
if(run.m_bSucceeded && !run.m_bCancelled && qIsFinite(run.m_dPressureRmse) && run.m_dPressureRmse >= 0.0)
errors.append(run.m_dPressureRmse);
// 显示勾选不改变统计总体,失败、取消或无有效统计值的运行不参与统计。
double value;
if(batch->statisticValue(run, variable, value)) values.append(value);
}
double minimum = 0.0, maximum = 1.0;
if(!errors.isEmpty()) {
minimum = maximum = errors.first();
for(int i = 1; i < errors.size(); ++i) {
minimum = qMin(minimum, errors[i]);
maximum = qMax(maximum, errors[i]);
if(!values.isEmpty()) {
minimum = maximum = values.first();
for(int i = 1; i < values.size(); ++i) {
minimum = qMin(minimum, values[i]);
maximum = qMax(maximum, values[i]);
}
// 单个结果或全部相同的误差也需要非零横轴范围。
// 单个结果或全部相同的统计值也需要非零横轴范围。
if(minimum == maximum) {
const double padding = qMax(1e-6, maximum * 0.05);
minimum = qMax(0.0, minimum - padding);
const double padding = qMax(1e-6, qAbs(maximum) * 0.05);
minimum -= padding;
if(variable < 0) minimum = qMax(0.0, minimum);
maximum += padding;
}
}
const double width = (maximum - minimum) / binCount;
QVector<int> counts(binCount, 0);
for(int i = 0; i < errors.size(); ++i) {
// 等宽分箱,左闭右开,最大误差归入最后一箱。
const int index = qMin(binCount - 1, int((errors[i] - minimum) / width));
for(int i = 0; i < values.size(); ++i) {
// 等宽分箱,左闭右开,最大值归入最后一箱。
const int index = qMin(binCount - 1, int((values[i] - minimum) / width));
++counts[index];
}
for(int i = plot->m_pPlot->getObjCount() - 1; i >= 0; --i)
@ -2264,7 +2269,7 @@ void nmSubWndMain::updateSensitiveHistogramPlot()
plot->clearUndoStack();
QVector<iCurveDesc> curves;
int largestCount = 0;
if(!errors.isEmpty()) {
if(!values.isEmpty()) {
QVector<QPointF> bars;
iCurveDesc cumulative;
cumulative.m_sCurveName = tr("Cumulative probability");
@ -2281,7 +2286,7 @@ void nmSubWndMain::updateSensitiveHistogramPlot()
largestCount = qMax(largestCount, counts[i]);
total += counts[i];
cumulative.m_vecX.append(center);
cumulative.m_vecY.append(100.0 * total / errors.size());
cumulative.m_vecY.append(100.0 * total / values.size());
}
// 普通曲线入口不支持柱状图,直接使用框架柱状图元,累计概率仍走已有曲线入口。
nmSensitivityHistogramBars* histogram = new nmSensitivityHistogramBars(width);
@ -2334,7 +2339,7 @@ void nmSubWndMain::onSensitiveHistogramOptions()
nmWxSensitivityHistogramOptions* dialog = window->findChild<nmWxSensitivityHistogramOptions*>();
if(!dialog) {
dialog = new nmWxSensitivityHistogramOptions(batch, window->widget()->property("nmHistogramBins").toInt(), window->widget());
connect(dialog, SIGNAL(binsChanged(int)), this, SLOT(onSensitiveHistogramBinsChanged(int)));
connect(dialog, SIGNAL(settingsChanged(int,int)), this, SLOT(onSensitiveHistogramSettingsChanged(int,int)));
connect(manager, SIGNAL(sigSensitiveResultsChanged()), dialog, SLOT(reject()));
}
dialog->show();
@ -2342,9 +2347,14 @@ void nmSubWndMain::onSensitiveHistogramOptions()
dialog->activateWindow();
}
void nmSubWndMain::onSensitiveHistogramBinsChanged(int bins)
void nmSubWndMain::onSensitiveHistogramSettingsChanged(int bins, int variable)
{
// 分箱设置仅影响本图,散点图继续使用未分箱的逐组误差。
// 直方图变量仅存于本批内存结果,散点图独立选择自己的纵轴。
nmDataAnalyzeManager* manager = m_pResultRefreshDataManager.data();
if(!manager || !manager->getSensitiveData()) return;
const QSharedPointer<nmDataSensitive::BatchResult> batch = manager->getSensitiveData()->getResults();
if(batch.isNull()) return;
batch->m_nHistogramVariable = variable;
iSubWndFitting* fitting = m_pResultRefreshFitting.data();
if(!fitting || !fitting->m_pMdiArea) return;
QMdiSubWindow* window = fitting->m_pMdiArea->findChild<QMdiSubWindow*>("nmSensitivityHistogram");

@ -246,15 +246,22 @@ public:
int columnCount(const QModelIndex& parent = QModelIndex()) const override
{
return parent.isValid() ? 0 : 3;
return parent.isValid() ? 0 : 3 + (m_statistics ? m_results->m_fitParameters.size() : 0);
}
QVariant headerData(int section, Qt::Orientation orientation, int role) const override
{
// 表格仅保留显示选择、运行编号与冻结参数。
if(orientation != Qt::Horizontal || role != Qt::DisplayRole) return QVariant();
if(m_statistics) return (QStringList() << "#" << nmWxSensitive::tr("Realization")
if(m_statistics) {
if(section >= 3 && section - 3 < m_results->m_fitParameters.size()) {
const nmDataAttribute& parameter = m_results->m_fitParameters[section - 3];
return parameter.getName() + (parameter.getUnit().isEmpty() ? QString()
: " [" + parameter.getUnit() + "]");
}
return (QStringList() << "#" << nmWxSensitive::tr("Realization")
<< nmWxSensitive::tr("Goodness of fit [MPa]")).value(section);
}
const QStringList titles = QStringList() << nmWxSensitive::tr("Show") << "#"
<< nmWxSensitive::tr("Parameters");
return titles.value(section);
@ -270,7 +277,8 @@ public:
if(role != Qt::DisplayRole && role != Qt::ToolTipRole) return QVariant();
if(index.column() == 0) return QString::number(run.m_nRunIndex + 1);
if(index.column() == 1) return sensitivityRunParameters(*m_results, run);
return hasSensitivityError(run) ? QString::number(run.m_dPressureRmse, 'g', 6)
double value;
return m_results->statisticValue(run, index.column() - 3, value) ? QString::number(value, 'g', 6)
: run.m_bCancelled ? nmWxSensitive::tr("Cancelled")
: !run.m_bSucceeded ? nmWxSensitive::tr("Failed") : nmWxSensitive::tr("N/A");
}
@ -420,7 +428,7 @@ nmWxSensitivityHistogramOptions::nmWxSensitivityHistogramOptions(
const QSharedPointer<nmDataSensitive::BatchResult>& results, int bins, QWidget* parent)
: iDlgBase(parent)
{
// 复用运行表格的参数展示,普通 Monte-Carlo 的统计变量只有误差。
// 复用运行表格;第四种在误差后追加拟合参数,选择变量后按原有 Apply/OK 流程生效。
setAttribute(Qt::WA_DeleteOnClose);
setObjectName("nmHistogramOptions");
setWindowTitle(tr("Histogram options"));
@ -434,9 +442,12 @@ nmWxSensitivityHistogramOptions::nmWxSensitivityHistogramOptions(
options->addWidget(m_bins);
options->addStretch();
options->addWidget(new QLabel(tr("Variable:"), this));
QComboBox* variable = new QComboBox(this);
variable->addItem(tr("Goodness of fit"));
options->addWidget(variable);
m_variable = new QComboBox(this);
m_variable->addItem(tr("Goodness of fit"), -1);
for(int i = 0; i < results->m_fitParameters.size(); ++i)
m_variable->addItem(results->m_fitParameters[i].getName(), i);
m_variable->setCurrentIndex(m_variable->findData(results->m_nHistogramVariable));
options->addWidget(m_variable);
layout->addLayout(options);
QTableView* table = new QTableView(this);
table->setStyleSheet("QTableView { background-color: white; }");
@ -447,7 +458,8 @@ nmWxSensitivityHistogramOptions::nmWxSensitivityHistogramOptions(
table->verticalHeader()->hide();
table->horizontalHeader()->setResizeMode(0, QHeaderView::ResizeToContents);
table->horizontalHeader()->setResizeMode(1, QHeaderView::Stretch);
table->horizontalHeader()->setResizeMode(2, QHeaderView::ResizeToContents);
for(int i = 2; i < table->model()->columnCount(); ++i)
table->horizontalHeader()->setResizeMode(i, QHeaderView::ResizeToContents);
layout->addWidget(table);
QHBoxLayout* buttons = new QHBoxLayout;
buttons->addStretch();
@ -465,8 +477,8 @@ nmWxSensitivityHistogramOptions::nmWxSensitivityHistogramOptions(
void nmWxSensitivityHistogramOptions::onApply()
{
// 只提交分箱数,取消不会撤销已经应用的设置。
emit binsChanged(m_bins->value());
// 分箱数与横轴变量一起提交,取消不会撤销已经应用的设置。
emit settingsChanged(m_bins->value(), m_variable->itemData(m_variable->currentIndex()).toInt());
}
void nmWxSensitivityHistogramOptions::onAccept()
@ -496,9 +508,11 @@ nmWxSensitivityScatter::nmWxSensitivityScatter(
options->addWidget(m_parameter, 1);
options->addSpacing(20);
options->addWidget(new QLabel(tr("Y Axis:"), this));
QComboBox* ordinate = new QComboBox(this);
ordinate->addItem(tr("Goodness of fit"));
options->addWidget(ordinate, 1);
m_ordinate = new QComboBox(this);
m_ordinate->addItem(tr("Goodness of fit"), -1);
for(int i = 0; i < results->m_fitParameters.size(); ++i)
m_ordinate->addItem(results->m_fitParameters[i].getName(), i);
options->addWidget(m_ordinate, 1);
for(size_t i = 0; i < results->m_runs.size(); ++i)
if(hasSensitivityError(results->m_runs[i])) m_defaultThreshold = qMax(m_defaultThreshold, results->m_runs[i].m_dPressureRmse);
if(qIsFinite(baseError) && baseError >= 0.0) m_defaultThreshold = baseError;
@ -526,13 +540,19 @@ nmWxSensitivityScatter::nmWxSensitivityScatter(
updateLegend();
m_plot->m_pPlotView->viewport()->installEventFilter(this);
connect(m_parameter, SIGNAL(currentIndexChanged(int)), this, SLOT(updateParameter()));
connect(m_ordinate, SIGNAL(currentIndexChanged(int)), this, SLOT(updateParameter()));
connect(m_plot->m_pPlotView, SIGNAL(sigAdjustContextMenu(QMenu*)), this, SLOT(adjustContextMenu(QMenu*)));
updateParameter();
}
void nmWxSensitivityScatter::updateParameter()
{
// 切换横轴时重建参数坐标,阈值在各参数之间共用。
// 两轴切换都从本批结果重建坐标;拟合参数轴不使用误差阈值。
m_draggingThreshold = false;
const int ordinate = m_ordinate->itemData(m_ordinate->currentIndex()).toInt();
const bool errorAxis = ordinate == -1;
const double modelY = errorAxis ? m_baseError : m_results->m_fitParameters[ordinate].getValue().toDouble();
m_plot->setAxisY(m_ordinate->currentText(), errorAxis ? "MPa" : m_results->m_fitParameters[ordinate].getUnit());
m_points.clear();
m_runRows.clear();
if(m_parameter->currentIndex() < 0) {
@ -545,17 +565,19 @@ void nmWxSensitivityScatter::updateParameter()
m_plot->setAxisX(variable.m_varName, unit);
double modelValue = variable.m_modelValue.getValue().toDouble();
const bool hasModel = convertSamplingValue(variable.m_varName, modelValue,
samplingBaseUnit(variable.m_varName), unit, modelValue) && m_baseError >= 0.0 && qIsFinite(m_baseError);
samplingBaseUnit(variable.m_varName), unit, modelValue) && qIsFinite(modelY) && (!errorAxis || modelY >= 0.0);
for(size_t i = 0; i < m_results->m_runs.size(); ++i) {
const nmDataSensitive::RunResult& run = m_results->m_runs[i];
if(!hasSensitivityError(run) || index >= run.m_vecParameters.size()) continue;
double ordinateValue;
if(!m_results->statisticValue(run, ordinate, ordinateValue) || index >= run.m_vecParameters.size()) continue;
double value = run.m_vecParameters[index];
if(!convertSamplingValue(variable.m_varName, value, samplingBaseUnit(variable.m_varName), unit, value)) continue;
m_points.append(QPointF(value, run.m_dPressureRmse));
m_points.append(QPointF(value, ordinateValue));
m_runRows.append(int(i));
}
QVector<iCurveDesc> curves;
for(int i = 0; i < 4; ++i) {
if(!errorAxis && (i == 1 || i == 3)) continue;
iCurveDesc curve;
curve.m_sCurveName = i == 0 ? tr("sensitivity data") : i == 1 ? tr("Below threshold")
: i == 2 ? tr("reference") : tr("Threshold");
@ -570,7 +592,7 @@ void nmWxSensitivityScatter::updateParameter()
}
if(i == 2 && hasModel) {
curve.m_vecX.append(modelValue);
curve.m_vecY.append(m_baseError);
curve.m_vecY.append(modelY);
}
if(i == 3) {
curve.m_bLineVisible = true;
@ -589,30 +611,41 @@ void nmWxSensitivityScatter::updateParameter()
m_plot->runUpdate();
m_plot->setReadOnly(true);
double left = hasModel ? modelValue : m_points.isEmpty() ? 0.0 : m_points.first().x();
double right = left, top = hasModel ? m_baseError : 0.0;
double right = left;
double bottom = hasModel ? modelY : m_points.isEmpty() ? 0.0 : m_points.first().y();
double top = bottom;
for(int i = 0; i < m_points.size(); ++i) {
left = qMin(left, m_points[i].x());
right = qMax(right, m_points[i].x());
bottom = qMin(bottom, m_points[i].y());
top = qMax(top, m_points[i].y());
}
const double padding = qMax(1e-6, (right > left ? right - left : qMax(1.0, qAbs(left))) * 0.05);
left -= padding;
right += padding;
top = qMax(1e-6, qMax(top, m_threshold) * 1.1);
if(errorAxis) {
bottom = 0.0;
top = qMax(1e-6, qMax(top, m_threshold) * 1.1);
} else {
const double margin = qMax(1e-6, (top > bottom ? top - bottom : qMax(1.0, qAbs(top))) * 0.05);
bottom -= margin;
top += margin;
}
VecDouble ticksX, ticksY;
for(int i = 0; i <= 5; ++i) {
ticksX.append(left + i * (right - left) / 5.0);
ticksY.append(i * top / 5.0);
ticksY.append(bottom + i * (top - bottom) / 5.0);
}
m_plot->setAxisScaleX(left, right, ticksX);
m_plot->setAxisScaleY(0.0, top, ticksY);
updateThreshold();
m_plot->setAxisScaleY(bottom, top, ticksY);
updateLegend();
if(errorAxis) updateThreshold();
}
void nmWxSensitivityScatter::updateThreshold()
{
// 阈值以下的点填黑,并同步图例中的阈值显示。
if(m_parameter->currentIndex() < 0) return;
if(m_parameter->currentIndex() < 0 || m_ordinate->currentIndex() != 0) return;
// 拖动时只更新图内反馈,松开后再保存阈值并刷新结果面板。
if(!m_draggingThreshold && m_results->m_dFilterThreshold != m_threshold) {
m_results->m_dFilterThreshold = m_threshold;
@ -669,6 +702,7 @@ void nmWxSensitivityScatter::resetThreshold()
void nmWxSensitivityScatter::adjustContextMenu(QMenu* menu)
{
// 复用绘图右键菜单,保留框架的缩放、复制与导出入口。
if(m_ordinate->currentIndex() != 0) return;
menu->addAction(m_resultsInPlot);
menu->addAction(tr("Reset line"), this, SLOT(resetThreshold()));
}
@ -678,7 +712,7 @@ void nmWxSensitivityScatter::updateLegend()
// 勾选后在原有两项图例下追加阈值,拖线和重置时实时更新。
QString text = "<table cellspacing='2'><tr><td>&#9633;</td><td>" + tr("sensitivity data")
+ "</td></tr><tr><td style='color:blue'>&#9632;</td><td>" + tr("reference") + "</td></tr></table>";
if(m_resultsInPlot->isChecked())
if(m_ordinate->currentIndex() == 0 && m_resultsInPlot->isChecked())
text += "<div>" + tr("Threshold: %1 MPa").arg(m_threshold, 0, 'g', 6) + "</div>";
m_legend->setText(text);
m_legend->adjustSize();
@ -693,7 +727,8 @@ bool nmWxSensitivityScatter::eventFilter(QObject* object, QEvent* event)
if(event->type() == QEvent::Paint)
m_legend->move(view->mapFromScene(m_plot->m_pPlot->getInnerRectF().topLeft()).toPoint() + QPoint(8, 8));
// 只接管阈值线附近的左键拖动,其余缩放、右键及导出仍由框架处理。
if(event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseMove || event->type() == QEvent::MouseButtonRelease) {
if(m_ordinate->currentIndex() == 0 && (event->type() == QEvent::MouseButtonPress ||
event->type() == QEvent::MouseMove || event->type() == QEvent::MouseButtonRelease)) {
QMouseEvent* mouse = static_cast<QMouseEvent*>(event);
const QPointF scene = view->mapToScene(QPointF(mouse->pos()));
const double lineY = view->mapFromScene(QPointF(scene.x(), axisY->getPosForValue(m_threshold))).y();
@ -713,9 +748,11 @@ bool nmWxSensitivityScatter::eventFilter(QObject* object, QEvent* event)
const QPointF position = view->mapFromScene(QPointF(m_plot->getAxisOf(0)->getPosForValue(m_points[i].x()), axisY->getPosForValue(m_points[i].y())));
if(qAbs(position.x() - help->pos().x()) > 6.0 || qAbs(position.y() - help->pos().y()) > 6.0) continue;
const nmDataSensitive::RunResult& run = m_results->m_runs[m_runRows[i]];
QToolTip::showText(help->globalPos(), tr("Run %1\n%2\nGoodness of fit: %3 MPa")
.arg(run.m_nRunIndex + 1).arg(sensitivityRunParameters(*m_results, run))
.arg(run.m_dPressureRmse, 0, 'g', 6), view->viewport());
const int ordinate = m_ordinate->itemData(m_ordinate->currentIndex()).toInt();
const QString unit = ordinate < 0 ? "MPa" : m_results->m_fitParameters[ordinate].getUnit();
QToolTip::showText(help->globalPos(), tr("Run %1\n%2").arg(run.m_nRunIndex + 1)
.arg(sensitivityRunParameters(*m_results, run)) + "\n" + m_ordinate->currentText()
+ ": " + QString::number(m_points[i].y(), 'g', 6) + " " + unit, view->viewport());
return true;
}
QToolTip::hideText();

Loading…
Cancel
Save