feat(nmNum): 预测与分析曲线只读与导出

feature/Sensitivity-20261012
lvjunjie 2 days ago
parent b9cb0431c1
commit 1cad10ffac

@ -177,6 +177,9 @@ class NM_SUB_WND_EXPORT nmSubWndMain : public iSubWndBaseFit {
const QVector<double>& pressures, const QVector<double>& durations,
const QVector<double>& rates);
void clearForecastResult();
// 只读结果曲线共用框架数据表,不开放拖动编辑。
void onReadonlyCurveDoubleClicked(ZxPlot* plot, ZxObjBase* object,
const QPointF& point, bool& processed);
void syncForecastTimeAxis(double minimum, double maximum, bool recalcTicks, bool adjustZoom);
void fitForecastViews();
void onSensitiveFilterChanged();

@ -112,6 +112,23 @@ namespace
QPointer<nmCalculationDllPebiSolverTask> s_pRunningSolverTask;
QPointer<nmSubWndMain> s_pPendingSolverWindow;
void clearResultCurveDataTables(iGuiPlot* plot)
{
// 框架弹出的表格是独立顶层窗口;刷新前清除本图的旧表,仍由框架管理其生命周期。
foreach(QWidget* window, QApplication::topLevelWidgets()) {
iGuiPlotTableHelper* table = qobject_cast<iGuiPlotTableHelper*>(window);
if(!table) continue;
for(int i = 0; i < plot->m_pPlot->getObjCount(); ++i) {
ZxObjCurve* curve = qobject_cast<ZxObjCurve*>(plot->m_pPlot->getObjByIndex(i));
if(curve && table->getMapTableViews()->contains(curve)) {
table->resetTableHelpers();
table->hide();
break;
}
}
}
}
QSet<QString> hiddenSensitiveCurves(iGuiPlot* plot)
{
// 刷新同一批次或重新打开窗口时保留本图的显示选择。
@ -1948,11 +1965,16 @@ void nmSubWndMain::onShowForecastResult(const QString& wellName, const QVector<d
iGuiPlot* plot = new iGuiPlot(true, charts);
plot->setObjectName(i == 0 ? "nmForecastRate" : "nmForecastPressure");
plot->initUI(i == 0 ? tr("Production rate") : tr("Bottomhole pressure"));
connect(plot, SIGNAL(sigDoubleClicked(ZxPlot*,ZxObjBase*,const QPointF&,bool&)),
this, SLOT(onReadonlyCurveDoubleClicked(ZxPlot*,ZxObjBase*,const QPointF&,bool&)));
plot->m_pPlotView->setSimpleMode(true);
plot->setDropEnabled(false);
plot->setAxisX(tr("Time"), "hr");
plot->setAxisY(i == 0 ? tr("Production rate") : tr("Bottomhole pressure"), i == 0 ? "m^3/D" : "MPa");
plot->setAxisY(i == 0 ? tr("Flow") : tr("Pressure"), i == 0 ? "m^3/D" : "MPa");
plot->setAxisY(i == 0 ? tr("Cumulative production") : QString(), i == 0 ? "m^3" : "", true);
// 两侧纵轴适当加宽,留出数字与名称的间距,上下图采用相同宽度保持对齐。
plot->getAxisOf(1)->setSpan(16.0);
plot->getAxisOf(2)->setSpan(16.0);
for(int axis = 0; axis < 3; ++axis) plot->setAxisXYLog(false, axis);
plot->getAxisOf(2)->setVisible(true);
// 下图右侧保留空轴位,确保两张图的时间刻度在水平方向对齐。
@ -1974,12 +1996,16 @@ void nmSubWndMain::onShowForecastResult(const QString& wellName, const QVector<d
// 压力直接取求解器结果;阶梯产量及累计量按本次输入制度精确构造。
QVector<double> rateTimes, rateValues, cumulativeTimes, cumulativeValues;
double time = 0.0, cumulative = 0.0;
double time = 0.0, cumulative = 0.0, maximumRate = 0.0;
// 零时刻先画关井基线,再接首段产量,显示开始生产时的竖直跳变。
rateTimes.append(0.0);
rateValues.append(0.0);
cumulativeTimes.append(0.0);
cumulativeValues.append(0.0);
for(int i = 0; i < durations.size(); ++i) {
rateTimes << time << time + durations[i];
rateValues << rates[i] << rates[i];
maximumRate = qMax(maximumRate, rates[i]);
time += durations[i];
cumulative += rates[i] * durations[i] / 24.0;
cumulativeTimes.append(time);
@ -2003,6 +2029,8 @@ void nmSubWndMain::onShowForecastResult(const QString& wellName, const QVector<d
window->findChild<iGuiPlot*>("nmForecastPressure")};
for(int i = 0; i < 2; ++i) {
iGuiPlot* plot = plots[i];
// 表格引用本批曲线,替换图元前关闭旧表,避免显示过期数据。
clearResultCurveDataTables(plot);
// 重复生成只替换曲线,清理旧图元和撤销记录,保留上下图窗口。
for(int j = plot->m_pPlot->getObjCount() - 1; j >= 0; --j)
if(qobject_cast<ZxObjCurve*>(plot->m_pPlot->getObjByIndex(j))) plot->m_pPlot->removeObjByIndex(j);
@ -2011,12 +2039,24 @@ void nmSubWndMain::onShowForecastResult(const QString& wellName, const QVector<d
plot->runUpdate();
plot->setReadOnly(true);
plot->freshAxisScales(true, true);
if(i == 0) {
// 产量和累计量均从零起算,顶部留少量空白;全程关井保留有效轴范围。
const double rateTop = maximumRate > 0.0 ? maximumRate : 1.0;
const double cumulativeTop = cumulative > 0.0 ? cumulative : 1.0;
VecDouble rateTicks, cumulativeTicks;
for(int tick = 0; tick <= 5; ++tick) {
rateTicks.append(rateTop * tick / 5.0);
cumulativeTicks.append(cumulativeTop * tick / 5.0);
}
plot->setAxisScaleY(0.0, rateTop * 1.05, rateTicks);
plot->setAxisScaleY2(0.0, cumulativeTop * 1.05, cumulativeTicks);
}
// 自动定标会压缩没有曲线的右轴,下图需要保留这块空白以对齐上图。
plot->getAxisOf(2)->setMinimizeAxis(false);
// 两张图都以完整预测时长为基准,缩放复原时也回到同一时间范围。
// 两张图共用时间范围,零时刻左侧留白,避免起始竖线与纵轴重叠。
VecDouble ticks;
for(int tick = 0; tick <= 5; ++tick) ticks.append(time * tick / 5.0);
plot->setAxisScaleX(0.0, time, ticks);
plot->setAxisScaleX(-time * 0.05, time, ticks);
// 时间刻度及标题只在下图显示,上图仍保留横轴数据与同步关系。
plot->getAxisOf(0)->setMinimizeAxis(i == 0);
}
@ -2029,6 +2069,27 @@ void nmSubWndMain::onShowForecastResult(const QString& wellName, const QVector<d
QTimer::singleShot(0, this, SLOT(fitForecastViews()));
}
void nmSubWndMain::onReadonlyCurveDoubleClicked(
ZxPlot* plot, ZxObjBase*, const QPointF& point, bool& processed)
{
iGuiPlot* widget = qobject_cast<iGuiPlot*>(sender());
if(!widget || !plot || processed) return;
for(int i = plot->getObjCount() - 1; i >= 0; --i) {
ZxObjCurve* curve = qobject_cast<ZxObjCurve*>(plot->getObjByIndex(i));
if(!curve || !curve->isVisible()) continue;
// 框架忽略只读曲线的命中,仅检查双击位置时临时解除,打开表格前恢复保护。
const bool readonly = curve->isReadOnly();
curve->setReadOnly(false);
int option = -1, index = -1;
const bool hit = curve->_runHitTest(point, option, index);
curve->setReadOnly(readonly);
if(!hit) continue;
processed = true;
widget->slotShowCurveData(curve);
return;
}
}
bool nmSubWndMain::eventFilter(QObject* watched, QEvent* event)
{
// 子窗口缩放、重新显示均在内部控件完成尺寸更新后再适配。
@ -2091,6 +2152,7 @@ void nmSubWndMain::onShowSensitiveResults()
updateSensitiveHistogramPlot();
QSharedPointer<nmDataSensitive::BatchResult> result = manager->getSensitiveData()->getResults();
iGuiPlot* plot = fitting->getPlotByTag(s_Tag_ChartSensitivity);
if(plot) clearResultCurveDataTables(plot);
if(result.isNull()) {
// 新模型已丢弃旧批次,同时清除图元与撤销记录,避免窗口恢复或保存后残留旧曲线。
if(plot) {
@ -2118,6 +2180,8 @@ void nmSubWndMain::onShowSensitiveResults()
tr("Failed to create sensitivity plot."));
return;
}
connect(plot, SIGNAL(sigDoubleClicked(ZxPlot*,ZxObjBase*,const QPointF&,bool&)),
this, SLOT(onReadonlyCurveDoubleClicked(ZxPlot*,ZxObjBase*,const QPointF&,bool&)), Qt::UniqueConnection);
installSensitiveShow(plot);
const QSet<QString> hidden = sender() == manager ? QSet<QString>() : hiddenSensitiveCurves(plot);
QVector<iCurveDesc> curves;
@ -2288,6 +2352,12 @@ void nmSubWndMain::updateSensitiveHistoryPlot()
iGuiPlot* plots[] = {history->m_pWxPlotP, history->m_pWxPlotF};
for(int i = 0; i < 2; ++i) {
iGuiPlot* plot = plots[i];
// 只读历史结果不进入 PF 的流动段编辑处理,双击统一打开框架数据表。
disconnect(plot, SIGNAL(sigDoubleClicked(ZxPlot*,ZxObjBase*,const QPointF&,bool&)),
history, SLOT(slotSubDoubleClicked(ZxPlot*,ZxObjBase*,const QPointF&,bool&)));
connect(plot, SIGNAL(sigDoubleClicked(ZxPlot*,ZxObjBase*,const QPointF&,bool&)),
this, SLOT(onReadonlyCurveDoubleClicked(ZxPlot*,ZxObjBase*,const QPointF&,bool&)), Qt::UniqueConnection);
clearResultCurveDataTables(plot);
const QSet<QString> hidden = sender() == manager ? QSet<QString>() : hiddenSensitiveCurves(plot);
for(int j = plot->m_pPlot->getObjCount() - 1; j >= 0; --j)
if(qobject_cast<ZxObjCurve*>(plot->m_pPlot->getObjByIndex(j))) plot->m_pPlot->removeObjByIndex(j);

Loading…
Cancel
Save