diff --git a/Include/nmNum/nmSubWnd/nmSubWndMain.h b/Include/nmNum/nmSubWnd/nmSubWndMain.h index 97096a14..1134376d 100644 --- a/Include/nmNum/nmSubWnd/nmSubWndMain.h +++ b/Include/nmNum/nmSubWnd/nmSubWndMain.h @@ -177,6 +177,9 @@ class NM_SUB_WND_EXPORT nmSubWndMain : public iSubWndBaseFit { const QVector& pressures, const QVector& durations, const QVector& 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(); diff --git a/Src/nmNum/nmSubWnd/nmSubWndMain.cpp b/Src/nmNum/nmSubWnd/nmSubWndMain.cpp index 39735bbc..ec4e73e2 100644 --- a/Src/nmNum/nmSubWnd/nmSubWndMain.cpp +++ b/Src/nmNum/nmSubWnd/nmSubWndMain.cpp @@ -112,6 +112,23 @@ namespace QPointer s_pRunningSolverTask; QPointer s_pPendingSolverWindow; +void clearResultCurveDataTables(iGuiPlot* plot) +{ + // 框架弹出的表格是独立顶层窗口;刷新前清除本图的旧表,仍由框架管理其生命周期。 + foreach(QWidget* window, QApplication::topLevelWidgets()) { + iGuiPlotTableHelper* table = qobject_cast(window); + if(!table) continue; + for(int i = 0; i < plot->m_pPlot->getObjCount(); ++i) { + ZxObjCurve* curve = qobject_cast(plot->m_pPlot->getObjByIndex(i)); + if(curve && table->getMapTableViews()->contains(curve)) { + table->resetTableHelpers(); + table->hide(); + break; + } + } + } +} + QSet hiddenSensitiveCurves(iGuiPlot* plot) { // 刷新同一批次或重新打开窗口时保留本图的显示选择。 @@ -1948,11 +1965,16 @@ void nmSubWndMain::onShowForecastResult(const QString& wellName, const QVectorsetObjectName(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 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 QVectorfindChild("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(plot->m_pPlot->getObjByIndex(j))) plot->m_pPlot->removeObjByIndex(j); @@ -2011,12 +2039,24 @@ void nmSubWndMain::onShowForecastResult(const QString& wellName, const QVectorrunUpdate(); 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(sender()); + if(!widget || !plot || processed) return; + for(int i = plot->getObjCount() - 1; i >= 0; --i) { + ZxObjCurve* curve = qobject_cast(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 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 hidden = sender() == manager ? QSet() : hiddenSensitiveCurves(plot); QVector 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 hidden = sender() == manager ? QSet() : hiddenSensitiveCurves(plot); for(int j = plot->m_pPlot->getObjCount() - 1; j >= 0; --j) if(qobject_cast(plot->m_pPlot->getObjByIndex(j))) plot->m_pPlot->removeObjByIndex(j);