diff --git a/Bin/Config/Lang/cn/nmNum_cn.qm b/Bin/Config/Lang/cn/nmNum_cn.qm
index 630ca98..00947bd 100644
Binary files a/Bin/Config/Lang/cn/nmNum_cn.qm and b/Bin/Config/Lang/cn/nmNum_cn.qm differ
diff --git a/Bin/Config/Lang/cn/nmNum_cn.ts b/Bin/Config/Lang/cn/nmNum_cn.ts
index 7db0df8..d3fc99e 100644
--- a/Bin/Config/Lang/cn/nmNum_cn.ts
+++ b/Bin/Config/Lang/cn/nmNum_cn.ts
@@ -5944,8 +5944,8 @@ Average pressure in contour: %2 MPa
流量
- m3/d
-
+ m^3/D
+ m^3/D
Date Time
@@ -6577,6 +6577,22 @@ Average pressure in contour: %2 MPa
Perforation parameters射孔参数
Fracture parameters裂缝参数
Perforation name射孔名称
+ Basic基础
+ Data数据
+ Well parameters井参数
+ Pressure and flow压力流量
+
+
+ nmWellPressureFlowPage
+ No pressure data无压力数据
+ No flow data无流量数据
+ No pressure or flow data无压力和流量数据
+ Pressure压力
+ Flow流量
+ Time时间
+ hh
+ MPaMPa
+ m³/dm³/d
nmReservoirParameterPage
diff --git a/Include/nmNum/nmSubWxs/nmWellPressureFlowPage.h b/Include/nmNum/nmSubWxs/nmWellPressureFlowPage.h
new file mode 100644
index 0000000..c3c66ac
--- /dev/null
+++ b/Include/nmNum/nmSubWxs/nmWellPressureFlowPage.h
@@ -0,0 +1,132 @@
+#pragma once
+
+#include "nmSubWxs_global.h"
+
+#include
+#include
+#include
+#include
+
+class iUnitGroup;
+class iUnitItem;
+class nmDataWellBase;
+class nmWxPressFlowChartWidget;
+class QSplitter;
+class QStackedWidget;
+class QTableWidget;
+class ZxTableHeaderViewUnit;
+
+/**
+ * @brief 显示当前井工作副本中的只读压力和流量数据。
+ * @note 页面不拥有井对象,只保存以h、m³/d和MPa为基准的数据副本。
+ */
+class NM_SUB_WXS_EXPORT nmWellPressureFlowPage : public QWidget
+{
+ Q_OBJECT
+
+public:
+ /** @brief 创建只读压力流量页面。 */
+ explicit nmWellPressureFlowPage(QWidget* pParent = nullptr);
+
+ /**
+ * @brief 绑定当前井工作副本,不立即读取数据。
+ * @param pWell 井工作副本,不转移所有权。
+ */
+ void setWell(nmDataWellBase* pWell);
+
+ /** @brief 从已绑定井重新读取原始数据并刷新全部视图。 */
+ void refreshData();
+
+ /** @brief 解除井绑定并清空全部显示数据。 */
+ void clearData();
+
+private slots:
+ /** @brief 从原始副本重新生成指定单位的表格和曲线。 */
+ void slotUnitChanged(int nColumn,
+ const iUnitItem* pSourceUnit,
+ const iUnitItem* pDestinationUnit);
+
+ /** @brief 将表格当前行同步为两张曲线的高亮流量段。 */
+ void slotFlowRowChanged(int nCurrentRow,
+ int nCurrentColumn,
+ int nPreviousRow,
+ int nPreviousColumn);
+
+ /** @brief 将流量曲线点击时间映射为表格行选择。 */
+ void slotFlowChartPointClicked(double dTimePoint);
+
+private:
+ /** @brief 创建左右分栏、上下图表和只读流量表格。 */
+ void createUi();
+
+ /** @brief 重新获取框架单位组并同步双行表头。 */
+ void initializeUnitSupport();
+
+ /** @brief 将表头恢复为当前已成功应用的单位。 */
+ void updateUnitHeader();
+
+ /** @brief 由基准副本生成表格显示点和目标单位精度。 */
+ bool buildFlowTableData(
+ const QString& sTimeUnit,
+ const QString& sFlowUnit,
+ QVector& vecFlowPoints,
+ int& nTimeDigit,
+ int& nFlowDigit) const;
+
+ /** @brief 使用指定单位组转换一个值。 */
+ bool convertValue(iUnitGroup* pUnitGroup,
+ const QString& sBaseUnit,
+ const QString& sDestinationUnit,
+ double dSourceValue,
+ double& dDestinationValue,
+ int* pDestinationDigit = nullptr) const;
+
+ /** @brief 刷新表格、图表、空状态和选中段。 */
+ void updateViews();
+
+ /** @brief 从原始流量持续时间生成基准单位阶梯曲线点。 */
+ QVector createFlowChartPoints() const;
+
+ /** @brief 刷新流量时序表并恢复有效选中行。 */
+ void updateFlowTable();
+
+ /** @brief 按原始压力和流量数据的时间并集同步图表范围。 */
+ void updateXAxisRange(
+ const QVector& vecFlowChartPoints);
+
+ /** @brief 根据共享流量段索引刷新两张曲线高亮。 */
+ void updateSelectedFlowSegment();
+
+ /** @brief 统一应用表格与曲线共享的零基流量点索引。 */
+ void applyFlowSegmentSelection(int nSegmentIndex);
+
+ /** @brief 按表格当前时间单位查找点击时间所属的流量点。 */
+ int findFlowSegmentByTableTime(double dTimePoint) const;
+
+ /** @brief 清空数据副本和视图,但保留当前单位及井绑定。 */
+ void resetDataViews();
+
+ nmDataWellBase* m_pWell; ///< 当前工作副本,不拥有。
+ QStackedWidget* m_pContentStack;
+ QSplitter* m_pHorizontalSplitter;
+ QSplitter* m_pChartSplitter;
+ QStackedWidget* m_pPressureChartStack;
+ QStackedWidget* m_pFlowChartStack;
+ QStackedWidget* m_pFlowTableStack;
+ nmWxPressFlowChartWidget* m_pPressureChart;
+ nmWxPressFlowChartWidget* m_pFlowChart;
+ QTableWidget* m_pFlowTable;
+ ZxTableHeaderViewUnit* m_pFlowHeader;
+ iUnitGroup* m_pTimeUnitGroup; ///< 框架拥有。
+ iUnitGroup* m_pFlowUnitGroup; ///< 框架拥有。
+ QVector m_vecRawPressurePoints;
+ QVector m_vecRawFlowPoints;
+ QVector m_vecTableFlowPoints;
+ QString m_sTimeBaseUnit;
+ QString m_sFlowBaseUnit;
+ QString m_sTimeUnit;
+ QString m_sFlowUnit;
+ int m_nTimeDigit;
+ int m_nFlowDigit;
+ int m_nSelectedFlowSegment;
+};
diff --git a/Include/nmNum/nmSubWxs/nmWxPressFlowChartWidget.h b/Include/nmNum/nmSubWxs/nmWxPressFlowChartWidget.h
index ed92300..bbb48ca 100644
--- a/Include/nmNum/nmSubWxs/nmWxPressFlowChartWidget.h
+++ b/Include/nmNum/nmSubWxs/nmWxPressFlowChartWidget.h
@@ -1,5 +1,4 @@
-#ifndef NMWXPRESSFLOWCHARTWIDGET_H
-#define NMWXPRESSFLOWCHARTWIDGET_H
+#pragma once
#include
#include
@@ -89,6 +88,27 @@ class nmWxPressFlowChartWidget : public QWidget
public:
explicit nmWxPressFlowChartWidget(QWidget *parent = 0);
+ /** @brief 设置图表是否为只读模式。 */
+ void setReadOnly(bool bReadOnly);
+
+ /** @brief 设置是否允许点击绘图区发送时间选择信号。 */
+ void setTimeSelectionEnabled(bool bEnabled);
+
+ /** @brief 设置流动段编辑使用的井,不转移所有权。 */
+ void setCurrentWellData(nmDataWellBase* pWellData);
+
+ /** @brief 固定X轴显示范围,用于多图时间轴对齐。 */
+ void setXAxisRange(double dMinimum, double dMaximum);
+
+ /** @brief 恢复根据图表自身数据计算X轴范围。 */
+ void clearXAxisRange();
+
+ /** @brief 高亮指定时间范围,零持续时间显示为竖线。 */
+ void setHighlightedTimeRange(double dStartTime, double dEndTime);
+
+ /** @brief 清除只读时间范围高亮。 */
+ void clearHighlightedTimeRange();
+
// 设置数据
void setData(const QVector& data);
void setTitle(const QString& title);
@@ -133,6 +153,7 @@ private:
void drawLineData(QPainter& painter); // 绘制连续线条
void drawStepData(QPainter& painter); // 绘制阶梯图
void drawLabels(QPainter& painter);
+ void drawHighlightedTimeRange(QPainter& painter);
void drawSelectedFlowSegments(QPainter& painter);
int findBoundaryAtPosition(const QPoint& pos, double tolerance = 5.0) const;
@@ -174,8 +195,17 @@ private:
// 流动段数据副本
nmDataPerforation m_perforationData;
- // 当前井数据
- nmDataWellBase* m_currentWell;
+ bool m_bReadOnly;
+ bool m_bTimeSelectionEnabled;
+ bool m_bFixedXAxisRange;
+ double m_dFixedMinimumX;
+ double m_dFixedMaximumX;
+ bool m_bHighlightTimeRange;
+ double m_dHighlightStartTime;
+ double m_dHighlightEndTime;
+
+ // 当前井仅供旧窗口吸附流动段边界使用,不拥有所有权。
+ nmDataWellBase* m_pCurrentWell;
// 拖动相关的成员变量
bool m_isDraggingBoundary; // 是否正在拖动分界线
@@ -188,5 +218,3 @@ private:
int m_externalDragBoundaryIndex;
double m_externalDragBoundaryTime;
};
-
-#endif // NMWXPRESSFLOWCHARTWIDGET_H
diff --git a/Include/nmNum/nmSubWxs/nmWxWellEditor.h b/Include/nmNum/nmSubWxs/nmWxWellEditor.h
index b0a1463..fe0a7af 100644
--- a/Include/nmNum/nmSubWxs/nmWxWellEditor.h
+++ b/Include/nmNum/nmSubWxs/nmWxWellEditor.h
@@ -9,16 +9,28 @@ class nmDataWellBase;
class nmWellEditorController;
class nmWellEditorSession;
class nmWellParameterPanel;
+class nmWellPressureFlowPage;
class nmWxWellboreTrajectoryDisplay;
+class QButtonGroup;
class QCheckBox;
class QComboBox;
class QDialogButtonBox;
+class QFrame;
+class QHBoxLayout;
class QKeyEvent;
class QLabel;
class QLineEdit;
class QSplitter;
+class QStackedWidget;
class QToolButton;
+enum nmWellEditorPage
+{
+ NM_WELL_EDITOR_PAGE_PARAMETERS = 0,
+ NM_WELL_EDITOR_PAGE_PRESSURE_FLOW,
+ NM_WELL_EDITOR_PAGE_COUNT
+};
+
/**
* @brief 数值试井井参数编辑器。
* @note 所有输入只修改会话副本,确认后由调用者接管最终副本。
@@ -76,6 +88,9 @@ protected:
virtual void keyPressEvent(QKeyEvent* pEvent);
private slots:
+ /** @brief 切换顶部井编辑页面。 */
+ void slotCategoryButtonClicked(int nPage);
+
/** @brief 保存当前井型输入并切换到独立井型缓存。 */
void slotWellTypeChanged(int nIndex);
@@ -98,9 +113,29 @@ private slots:
void slotPanelValidationMessageChanged(const QString& sMessage);
private:
- /** @brief 创建左侧参数区、右侧轨迹区和底部命令区。 */
+ /** @brief 创建顶部导航、页面堆栈和底部命令区。 */
void createUi();
+ /** @brief 创建顶部分组导航。 */
+ QWidget* createCategoryBar();
+
+ /** @brief 创建一个带底部分组标题的ribbon区域。 */
+ QWidget* createRibbonGroup(const QString& sCaption,
+ QWidget* pParent,
+ QHBoxLayout*& pCommandLayout);
+
+ /** @brief 创建顶部导航分组的竖向分隔线。 */
+ QFrame* createRibbonSeparator(QWidget* pParent);
+
+ /** @brief 创建一个页面切换命令按钮。 */
+ QToolButton* createCategoryButton(int nPage,
+ const QString& sText,
+ const QString& sIconName,
+ QWidget* pParent);
+
+ /** @brief 创建顶部帮助命令按钮。 */
+ QToolButton* createHelpButton(QWidget* pParent);
+
/** @brief 创建固定显示的井号、井名、井型和时间变表皮信息区。 */
QWidget* createWellInformationWidget(QWidget* pParent);
@@ -110,6 +145,9 @@ private:
/** @brief 从当前工作副本回填时间变表皮控件。 */
void reloadTimeDependentSkinControls(nmDataWellBase* pWell);
+ /** @brief 从当前会话工作副本刷新只读压力流量页。 */
+ void reloadPressureFlowPage();
+
/** @brief 为当前井型工作副本重新创建轨迹交互控制器。 */
void createController();
@@ -127,6 +165,9 @@ private:
void updateAcceptState();
nmWellEditorSession* m_pSession; ///< 对话框独占编辑会话。
+ QButtonGroup* m_pCategoryButtonGroup; ///< 顶部页面命令与稳定页面ID映射。
+ QStackedWidget* m_pStackedWidget; ///< 井参数和压力流量页面堆栈。
+ nmWellPressureFlowPage* m_pPressureFlowPage; ///< 只读压力流量数据页。
nmWellParameterPanel* m_pParameterPanel; ///< 左侧单列参数分组。
nmWxWellboreTrajectoryDisplay* m_pTrajectoryDisplay; ///< 右侧轨迹视图。
nmWellEditorController* m_pWellEditorController; ///< 轨迹交互控制器。
@@ -139,6 +180,7 @@ private:
QDialogButtonBox* m_pButtonBox; ///< 确定和取消按钮区。
QLabel* m_pStatusLabel; ///< 当前校验错误。
QSplitter* m_pMainSplitter; ///< 参数与轨迹分割器。
+ int m_nCurrentPage; ///< 当前显示页面。
QString m_sPanelValidationMessage; ///< 参数面板错误。
bool m_bInitialized; ///< 编辑器是否初始化完成。
bool m_bChangingWellType; ///< 程序同步井型标记。
diff --git a/Src/nmNum/nmSubWxs/nmWellPressureFlowPage.cpp b/Src/nmNum/nmSubWxs/nmWellPressureFlowPage.cpp
new file mode 100644
index 0000000..4512cbd
--- /dev/null
+++ b/Src/nmNum/nmSubWxs/nmWellPressureFlowPage.cpp
@@ -0,0 +1,772 @@
+#include "nmWellPressureFlowPage.h"
+
+#include "iUnitGroup.h"
+#include "iUnitHelper.h"
+#include "iUnitItem.h"
+#include "nmDataWellBase.h"
+#include "nmWxPressFlowChartWidget.h"
+#include "ZxBaseUtil.h"
+#include "ZxTableHeaderViewUnit.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace
+{
+QString preferredTimeUnit()
+{
+ return QLatin1String("h");
+}
+
+QString preferredFlowUnit()
+{
+ return QString::fromUtf8("m\xC2\xB3/d");
+}
+
+iUnitGroup* resolveUnitGroup(const QStringList& listCandidates,
+ QString& sResolvedUnit)
+{
+ for (int nIndex = 0; nIndex < listCandidates.size(); ++nIndex)
+ {
+ const QString& sCandidate = listCandidates[nIndex];
+ iUnitGroup* pUnitGroup =
+ iUnitHelper::getUnitGroupByUnit(sCandidate);
+ if (pUnitGroup == nullptr)
+ {
+ continue;
+ }
+
+ int nUnitIndex = pUnitGroup->indexOf(sCandidate);
+ QStringList listUnitNames = pUnitGroup->getAllUnitNames();
+ if (nUnitIndex < 0 || nUnitIndex >= listUnitNames.size())
+ {
+ continue;
+ }
+
+ sResolvedUnit = listUnitNames[nUnitIndex];
+ return pUnitGroup;
+ }
+
+ sResolvedUnit = listCandidates.isEmpty()
+ ? QString() : listCandidates.first();
+ return nullptr;
+}
+
+QLabel* createEmptyLabel(const QString& sText, QWidget* pParent)
+{
+ QLabel* pLabel = new QLabel(sText, pParent);
+ pLabel->setAlignment(Qt::AlignCenter);
+ QPalette oPalette = pLabel->palette();
+ oPalette.setColor(QPalette::WindowText,
+ pParent->palette().color(QPalette::Mid));
+ pLabel->setPalette(oPalette);
+ return pLabel;
+}
+
+QString displayValueText(double dValue, int nDigit)
+{
+ if (nDigit <= 0)
+ {
+ return QString::number(dValue, 'f', 0);
+ }
+ return ZxBaseUtil::getValidStr(dValue, nDigit);
+}
+}
+
+nmWellPressureFlowPage::nmWellPressureFlowPage(QWidget* pParent)
+ : QWidget(pParent),
+ m_pWell(nullptr),
+ m_pContentStack(nullptr),
+ m_pHorizontalSplitter(nullptr),
+ m_pChartSplitter(nullptr),
+ m_pPressureChartStack(nullptr),
+ m_pFlowChartStack(nullptr),
+ m_pFlowTableStack(nullptr),
+ m_pPressureChart(nullptr),
+ m_pFlowChart(nullptr),
+ m_pFlowTable(nullptr),
+ m_pFlowHeader(nullptr),
+ m_pTimeUnitGroup(nullptr),
+ m_pFlowUnitGroup(nullptr),
+ m_sTimeBaseUnit(preferredTimeUnit()),
+ m_sFlowBaseUnit(preferredFlowUnit()),
+ m_sTimeUnit(preferredTimeUnit()),
+ m_sFlowUnit(preferredFlowUnit()),
+ m_nTimeDigit(6),
+ m_nFlowDigit(6),
+ m_nSelectedFlowSegment(-1)
+{
+ createUi();
+ initializeUnitSupport();
+ resetDataViews();
+}
+
+void nmWellPressureFlowPage::setWell(nmDataWellBase* pWell)
+{
+ if (m_pWell == pWell)
+ {
+ return;
+ }
+
+ m_pWell = pWell;
+ resetDataViews();
+}
+
+void nmWellPressureFlowPage::refreshData()
+{
+ if (m_pWell == nullptr)
+ {
+ resetDataViews();
+ return;
+ }
+
+ // 第一步:井数据只复制到页面基准副本,后续显示换算不会回写井对象。
+ QVector vecRawPressurePoints =
+ m_pWell->getPressurePoints();
+ QVector vecRawFlowPoints = m_pWell->getFlowPoints();
+
+ // 第二步:单位服务可能晚于页面构造完成,再刷新一次可用单位组。
+ initializeUnitSupport();
+
+ m_vecRawPressurePoints = vecRawPressurePoints;
+ m_vecRawFlowPoints = vecRawFlowPoints;
+
+ QVector vecTableFlowPoints;
+ int nTimeDigit = m_nTimeDigit;
+ int nFlowDigit = m_nFlowDigit;
+ if (!buildFlowTableData(m_sTimeUnit,
+ m_sFlowUnit,
+ vecTableFlowPoints,
+ nTimeDigit,
+ nFlowDigit))
+ {
+ // 当前显示单位不可用时回退基准单位,禁止继续展示旧井快照。
+ m_sTimeUnit = m_sTimeBaseUnit;
+ m_sFlowUnit = m_sFlowBaseUnit;
+ if (!buildFlowTableData(m_sTimeUnit,
+ m_sFlowUnit,
+ vecTableFlowPoints,
+ nTimeDigit,
+ nFlowDigit))
+ {
+ resetDataViews();
+ updateUnitHeader();
+ return;
+ }
+ }
+
+ // 第三步:换算与精度读取全部成功后再提交,曲线始终读取原始数据。
+ m_vecTableFlowPoints = vecTableFlowPoints;
+ m_nTimeDigit = nTimeDigit;
+ m_nFlowDigit = nFlowDigit;
+ updateUnitHeader();
+ updateViews();
+}
+
+void nmWellPressureFlowPage::clearData()
+{
+ m_pWell = nullptr;
+ resetDataViews();
+}
+
+void nmWellPressureFlowPage::slotUnitChanged(
+ int nColumn,
+ const iUnitItem* pSourceUnit,
+ const iUnitItem* pDestinationUnit)
+{
+ Q_UNUSED(pSourceUnit);
+ if (pDestinationUnit == nullptr ||
+ (nColumn != 0 && nColumn != 1))
+ {
+ updateUnitHeader();
+ return;
+ }
+
+ QString sTimeUnit = m_sTimeUnit;
+ QString sFlowUnit = m_sFlowUnit;
+ if (nColumn == 0)
+ {
+ if (m_pTimeUnitGroup == nullptr)
+ {
+ updateUnitHeader();
+ return;
+ }
+ sTimeUnit = pDestinationUnit->m_sName;
+ }
+ else
+ {
+ if (m_pFlowUnitGroup == nullptr)
+ {
+ updateUnitHeader();
+ return;
+ }
+ sFlowUnit = pDestinationUnit->m_sName;
+ }
+
+ // 每次都从基准副本换算,禁止使用当前显示值继续换算。
+ QVector vecTableFlowPoints;
+ int nTimeDigit = m_nTimeDigit;
+ int nFlowDigit = m_nFlowDigit;
+ if (!buildFlowTableData(sTimeUnit,
+ sFlowUnit,
+ vecTableFlowPoints,
+ nTimeDigit,
+ nFlowDigit))
+ {
+ updateUnitHeader();
+ return;
+ }
+
+ m_sTimeUnit = sTimeUnit;
+ m_sFlowUnit = sFlowUnit;
+ m_vecTableFlowPoints = vecTableFlowPoints;
+ m_nTimeDigit = nTimeDigit;
+ m_nFlowDigit = nFlowDigit;
+ updateUnitHeader();
+ updateFlowTable();
+}
+
+void nmWellPressureFlowPage::slotFlowRowChanged(
+ int nCurrentRow,
+ int nCurrentColumn,
+ int nPreviousRow,
+ int nPreviousColumn)
+{
+ Q_UNUSED(nCurrentColumn);
+ Q_UNUSED(nPreviousRow);
+ Q_UNUSED(nPreviousColumn);
+
+ applyFlowSegmentSelection(nCurrentRow);
+}
+
+void nmWellPressureFlowPage::slotFlowChartPointClicked(double dTimePoint)
+{
+ // 曲线保持基准单位,点击时间先换算为表格当前单位再定位行。
+ double dTableTimePoint = 0.0;
+ if (!convertValue(m_pTimeUnitGroup,
+ m_sTimeBaseUnit,
+ m_sTimeUnit,
+ dTimePoint,
+ dTableTimePoint))
+ {
+ applyFlowSegmentSelection(-1);
+ return;
+ }
+
+ applyFlowSegmentSelection(
+ findFlowSegmentByTableTime(dTableTimePoint));
+}
+
+void nmWellPressureFlowPage::createUi()
+{
+ QVBoxLayout* pMainLayout = new QVBoxLayout(this);
+ pMainLayout->setContentsMargins(8, 6, 8, 8);
+ pMainLayout->setSpacing(0);
+
+ m_pContentStack = new QStackedWidget(this);
+ m_pHorizontalSplitter = new QSplitter(
+ Qt::Horizontal, m_pContentStack);
+ m_pHorizontalSplitter->setChildrenCollapsible(false);
+
+ // 左侧上下两图共享时间范围,但各自保留独立空状态。
+ m_pChartSplitter = new QSplitter(
+ Qt::Vertical, m_pHorizontalSplitter);
+ m_pChartSplitter->setChildrenCollapsible(false);
+
+ m_pPressureChartStack = new QStackedWidget(m_pChartSplitter);
+ m_pPressureChart = new nmWxPressFlowChartWidget(
+ m_pPressureChartStack);
+ m_pPressureChart->setReadOnly(true);
+ m_pPressureChart->setTimeSelectionEnabled(false);
+ m_pPressureChart->setAxisLabels(tr("Time"), tr("Pressure"));
+ m_pPressureChart->setAxisUnits(
+ m_sTimeBaseUnit, tr("MPa"));
+ m_pPressureChart->setLineColor(QColor(50, 200, 50));
+ m_pPressureChart->setChartType(CHART_TYPE_LINE);
+ m_pPressureChartStack->addWidget(m_pPressureChart);
+ m_pPressureChartStack->addWidget(createEmptyLabel(
+ tr("No pressure data"), m_pPressureChartStack));
+
+ m_pFlowChartStack = new QStackedWidget(m_pChartSplitter);
+ m_pFlowChart = new nmWxPressFlowChartWidget(m_pFlowChartStack);
+ m_pFlowChart->setReadOnly(true);
+ m_pFlowChart->setTimeSelectionEnabled(true);
+ m_pFlowChart->setAxisLabels(tr("Time"), tr("Flow"));
+ m_pFlowChart->setAxisUnits(m_sTimeBaseUnit, m_sFlowBaseUnit);
+ m_pFlowChart->setLineColor(QColor(255, 0, 0));
+ m_pFlowChart->setChartType(CHART_TYPE_STEP);
+ m_pFlowChartStack->addWidget(m_pFlowChart);
+ m_pFlowChartStack->addWidget(createEmptyLabel(
+ tr("No flow data"), m_pFlowChartStack));
+
+ m_pChartSplitter->addWidget(m_pPressureChartStack);
+ m_pChartSplitter->addWidget(m_pFlowChartStack);
+ m_pChartSplitter->setStretchFactor(0, 1);
+ m_pChartSplitter->setStretchFactor(1, 1);
+
+ // 右侧仅显示原始流量段对应的时间和流量,不提供任何编辑入口。
+ m_pFlowTableStack = new QStackedWidget(m_pHorizontalSplitter);
+ m_pFlowTable = new QTableWidget(m_pFlowTableStack);
+ m_pFlowTable->setColumnCount(2);
+ m_pFlowTable->setEditTriggers(QAbstractItemView::NoEditTriggers);
+ m_pFlowTable->setSelectionBehavior(QAbstractItemView::SelectRows);
+ m_pFlowTable->setSelectionMode(QAbstractItemView::SingleSelection);
+ m_pFlowTable->setSortingEnabled(false);
+ m_pFlowTable->setDragEnabled(false);
+ m_pFlowTable->setAcceptDrops(false);
+ m_pFlowTable->setDropIndicatorShown(false);
+ m_pFlowTable->setAlternatingRowColors(true);
+ m_pFlowTable->setStyleSheet(
+ "QTableWidget {"
+ " background-color: palette(base);"
+ " alternate-background-color: rgb(232, 232, 232);"
+ " gridline-color: palette(midlight);"
+ " border: 1px solid palette(mid);"
+ "}"
+ "QHeaderView {"
+ " background-color: palette(button);"
+ " color: palette(button-text);"
+ "}"
+ "QHeaderView::section {"
+ " background-color: palette(button);"
+ " color: palette(button-text);"
+ " border: none;"
+ " border-right: 1px solid palette(mid);"
+ " border-bottom: 1px solid palette(mid);"
+ "}"
+ "QTableCornerButton::section {"
+ " background-color: palette(base);"
+ " border: none;"
+ " border-right: 1px solid palette(mid);"
+ " border-bottom: 1px solid palette(mid);"
+ "}");
+
+ m_pFlowHeader = new ZxTableHeaderViewUnit(m_pFlowTable);
+ m_pFlowTable->setHorizontalHeader(m_pFlowHeader);
+ m_pFlowTable->horizontalHeader()->setMovable(false);
+ m_pFlowTable->horizontalHeader()->setResizeMode(QHeaderView::Stretch);
+ m_pFlowTable->verticalHeader()->setMovable(false);
+ m_pFlowTable->verticalHeader()->setDefaultAlignment(Qt::AlignCenter);
+ m_pFlowTable->verticalHeader()->setMinimumWidth(18);
+
+ m_pFlowTableStack->addWidget(m_pFlowTable);
+ m_pFlowTableStack->addWidget(createEmptyLabel(
+ tr("No flow data"), m_pFlowTableStack));
+
+ m_pHorizontalSplitter->addWidget(m_pChartSplitter);
+ m_pHorizontalSplitter->addWidget(m_pFlowTableStack);
+ m_pHorizontalSplitter->setStretchFactor(0, 7);
+ m_pHorizontalSplitter->setStretchFactor(1, 3);
+ QList listHorizontalSizes;
+ listHorizontalSizes << 700 << 300;
+ m_pHorizontalSplitter->setSizes(listHorizontalSizes);
+
+ m_pContentStack->addWidget(m_pHorizontalSplitter);
+ m_pContentStack->addWidget(createEmptyLabel(
+ tr("No pressure or flow data"), m_pContentStack));
+ pMainLayout->addWidget(m_pContentStack, 1);
+
+ connect(m_pFlowHeader,
+ SIGNAL(sigCbxValueChanged(int,const iUnitItem*,const iUnitItem*)),
+ this,
+ SLOT(slotUnitChanged(int,const iUnitItem*,const iUnitItem*)));
+ connect(m_pFlowTable,
+ SIGNAL(currentCellChanged(int,int,int,int)),
+ this,
+ SLOT(slotFlowRowChanged(int,int,int,int)));
+ connect(m_pFlowChart,
+ SIGNAL(chartPointClicked(double)),
+ this,
+ SLOT(slotFlowChartPointClicked(double)));
+}
+
+void nmWellPressureFlowPage::initializeUnitSupport()
+{
+ QStringList listTimeCandidates;
+ listTimeCandidates << preferredTimeUnit() << QLatin1String("hr");
+ QStringList listFlowCandidates;
+ listFlowCandidates << preferredFlowUnit()
+ << QLatin1String("m^3/d")
+ << QLatin1String("m^3/D");
+
+ // 优先使用当前单位包的h、m³/d;旧英文单位包自动兼容hr、m^3/d。
+ m_pTimeUnitGroup = resolveUnitGroup(
+ listTimeCandidates, m_sTimeBaseUnit);
+ m_pFlowUnitGroup = resolveUnitGroup(
+ listFlowCandidates, m_sFlowBaseUnit);
+
+ // 单位组缺失或当前单位不再存在时回退基准单位,入口由框架表头禁用。
+ if (m_pTimeUnitGroup == nullptr ||
+ m_pTimeUnitGroup->indexOf(m_sTimeUnit) < 0)
+ {
+ m_sTimeUnit = m_sTimeBaseUnit;
+ }
+ if (m_pFlowUnitGroup == nullptr ||
+ m_pFlowUnitGroup->indexOf(m_sFlowUnit) < 0)
+ {
+ m_sFlowUnit = m_sFlowBaseUnit;
+ }
+ updateUnitHeader();
+}
+
+void nmWellPressureFlowPage::updateUnitHeader()
+{
+ if (m_pFlowHeader == nullptr)
+ {
+ return;
+ }
+
+ QStringList listTitles;
+ listTitles << tr("Time") << tr("Flow");
+ QStringList listUnits;
+ listUnits << m_sTimeUnit << m_sFlowUnit;
+ m_pFlowHeader->setHeaderData(listTitles, listUnits);
+}
+
+bool nmWellPressureFlowPage::buildFlowTableData(
+ const QString& sTimeUnit,
+ const QString& sFlowUnit,
+ QVector& vecFlowPoints,
+ int& nTimeDigit,
+ int& nFlowDigit) const
+{
+ vecFlowPoints = m_vecRawFlowPoints;
+
+ // 空数据也必须验证目标单位,避免表头切换后留下不可用单位状态。
+ double dConvertedZero = 0.0;
+ if (!convertValue(m_pTimeUnitGroup,
+ m_sTimeBaseUnit,
+ sTimeUnit,
+ 0.0,
+ dConvertedZero,
+ &nTimeDigit) ||
+ !convertValue(m_pFlowUnitGroup,
+ m_sFlowBaseUnit,
+ sFlowUnit,
+ 0.0,
+ dConvertedZero,
+ &nFlowDigit))
+ {
+ return false;
+ }
+
+ // 表格每次从原始副本换算,禁止使用当前显示值继续换算。
+ for (int nIndex = 0; nIndex < vecFlowPoints.size(); ++nIndex)
+ {
+ double dDuration = 0.0;
+ double dFlow = 0.0;
+ if (!convertValue(m_pTimeUnitGroup,
+ m_sTimeBaseUnit,
+ sTimeUnit,
+ m_vecRawFlowPoints[nIndex].x(),
+ dDuration) ||
+ !convertValue(m_pFlowUnitGroup,
+ m_sFlowBaseUnit,
+ sFlowUnit,
+ m_vecRawFlowPoints[nIndex].y(),
+ dFlow))
+ {
+ return false;
+ }
+ vecFlowPoints[nIndex] = QPointF(dDuration, dFlow);
+ }
+ return true;
+}
+
+bool nmWellPressureFlowPage::convertValue(
+ iUnitGroup* pUnitGroup,
+ const QString& sBaseUnit,
+ const QString& sDestinationUnit,
+ double dSourceValue,
+ double& dDestinationValue,
+ int* pDestinationDigit) const
+{
+ int nDigit = 6;
+ if (pUnitGroup != nullptr)
+ {
+ iUnitItem* pUnitItem =
+ pUnitGroup->getUnitItem(sDestinationUnit);
+ if (pUnitItem != nullptr && pUnitItem->m_nDigit >= 0)
+ {
+ nDigit = pUnitItem->m_nDigit;
+ }
+ }
+
+ if (sDestinationUnit == sBaseUnit)
+ {
+ dDestinationValue = dSourceValue;
+ if (pDestinationDigit != nullptr)
+ {
+ *pDestinationDigit = nDigit;
+ }
+ return true;
+ }
+ if (pUnitGroup == nullptr ||
+ pUnitGroup->indexOf(sBaseUnit) < 0 ||
+ pUnitGroup->indexOf(sDestinationUnit) < 0)
+ {
+ return false;
+ }
+
+ int nFallbackDigit = nDigit;
+ bool bConverted = pUnitGroup->convert(sBaseUnit,
+ dSourceValue,
+ sDestinationUnit,
+ dDestinationValue,
+ nDigit);
+ if (bConverted && pDestinationDigit != nullptr)
+ {
+ *pDestinationDigit = nDigit >= 0
+ ? nDigit : nFallbackDigit;
+ }
+ return bConverted;
+}
+
+void nmWellPressureFlowPage::updateViews()
+{
+ QVector vecFlowChartPoints = createFlowChartPoints();
+ m_pPressureChart->setAxisUnits(
+ m_sTimeBaseUnit, tr("MPa"));
+ m_pFlowChart->setAxisUnits(m_sTimeBaseUnit, m_sFlowBaseUnit);
+ m_pPressureChart->setData(m_vecRawPressurePoints);
+ m_pFlowChart->setData(vecFlowChartPoints);
+ updateXAxisRange(vecFlowChartPoints);
+ updateFlowTable();
+
+ bool bPressureEmpty = m_vecRawPressurePoints.isEmpty();
+ bool bFlowEmpty = m_vecRawFlowPoints.isEmpty();
+ m_pPressureChartStack->setCurrentIndex(bPressureEmpty ? 1 : 0);
+ m_pFlowChartStack->setCurrentIndex(bFlowEmpty ? 1 : 0);
+ m_pFlowTableStack->setCurrentIndex(bFlowEmpty ? 1 : 0);
+ m_pContentStack->setCurrentIndex(
+ bPressureEmpty && bFlowEmpty ? 1 : 0);
+ updateSelectedFlowSegment();
+}
+
+QVector nmWellPressureFlowPage::createFlowChartPoints() const
+{
+ QVector vecChartPoints;
+ double dTotalTime = 0.0;
+ for (int nIndex = 0;
+ nIndex < m_vecRawFlowPoints.size();
+ ++nIndex)
+ {
+ const QPointF& oSegment = m_vecRawFlowPoints[nIndex];
+ if (nIndex == 0)
+ {
+ vecChartPoints.append(QPointF(0.0, oSegment.y()));
+ }
+
+ dTotalTime += oSegment.x();
+ vecChartPoints.append(QPointF(dTotalTime, oSegment.y()));
+ if (nIndex < m_vecRawFlowPoints.size() - 1 &&
+ qAbs(m_vecRawFlowPoints[nIndex + 1].y() -
+ oSegment.y()) > 1e-6)
+ {
+ vecChartPoints.append(QPointF(
+ dTotalTime,
+ m_vecRawFlowPoints[nIndex + 1].y()));
+ }
+ }
+ return vecChartPoints;
+}
+
+void nmWellPressureFlowPage::updateFlowTable()
+{
+ bool bSignalsWereBlocked = m_pFlowTable->blockSignals(true);
+ m_pFlowTable->clearContents();
+ m_pFlowTable->setRowCount(m_vecTableFlowPoints.size());
+
+ // 纵向表头显示Qt默认的1~N序号,内部仍使用零基流量点索引。
+ for (int nIndex = 0;
+ nIndex < m_vecTableFlowPoints.size();
+ ++nIndex)
+ {
+ QTableWidgetItem* pTimeItem = new QTableWidgetItem(
+ displayValueText(m_vecTableFlowPoints[nIndex].x(),
+ m_nTimeDigit));
+ pTimeItem->setTextAlignment(Qt::AlignCenter);
+ m_pFlowTable->setItem(nIndex, 0, pTimeItem);
+
+ QTableWidgetItem* pFlowItem = new QTableWidgetItem(
+ displayValueText(m_vecTableFlowPoints[nIndex].y(),
+ m_nFlowDigit));
+ pFlowItem->setTextAlignment(Qt::AlignCenter);
+ m_pFlowTable->setItem(nIndex, 1, pFlowItem);
+ }
+
+ if (m_nSelectedFlowSegment >= 0 &&
+ m_nSelectedFlowSegment < m_pFlowTable->rowCount())
+ {
+ m_pFlowTable->setCurrentCell(m_nSelectedFlowSegment, 0);
+ m_pFlowTable->selectRow(m_nSelectedFlowSegment);
+ }
+ else
+ {
+ m_nSelectedFlowSegment = -1;
+ m_pFlowTable->setCurrentCell(-1, -1);
+ m_pFlowTable->clearSelection();
+ }
+ m_pFlowTable->blockSignals(bSignalsWereBlocked);
+}
+
+void nmWellPressureFlowPage::updateXAxisRange(
+ const QVector& vecFlowChartPoints)
+{
+ bool bHasRange = false;
+ double dMinimum = 0.0;
+ double dMaximum = 0.0;
+
+ for (int nIndex = 0;
+ nIndex < m_vecRawPressurePoints.size();
+ ++nIndex)
+ {
+ double dTime = m_vecRawPressurePoints[nIndex].x();
+ dMinimum = bHasRange ? qMin(dMinimum, dTime) : dTime;
+ dMaximum = bHasRange ? qMax(dMaximum, dTime) : dTime;
+ bHasRange = true;
+ }
+ for (int nIndex = 0; nIndex < vecFlowChartPoints.size(); ++nIndex)
+ {
+ double dTime = vecFlowChartPoints[nIndex].x();
+ dMinimum = bHasRange ? qMin(dMinimum, dTime) : dTime;
+ dMaximum = bHasRange ? qMax(dMaximum, dTime) : dTime;
+ bHasRange = true;
+ }
+
+ if (bHasRange)
+ {
+ m_pPressureChart->setXAxisRange(dMinimum, dMaximum);
+ m_pFlowChart->setXAxisRange(dMinimum, dMaximum);
+ }
+ else
+ {
+ m_pPressureChart->clearXAxisRange();
+ m_pFlowChart->clearXAxisRange();
+ }
+}
+
+void nmWellPressureFlowPage::updateSelectedFlowSegment()
+{
+ if (m_nSelectedFlowSegment < 0 ||
+ m_nSelectedFlowSegment >= m_vecRawFlowPoints.size())
+ {
+ m_pPressureChart->clearHighlightedTimeRange();
+ m_pFlowChart->clearHighlightedTimeRange();
+ return;
+ }
+
+ // 流量点X值是持续时间,段区间必须从第0段开始逐段累加。
+ double dStartTime = 0.0;
+ for (int nIndex = 0;
+ nIndex < m_nSelectedFlowSegment;
+ ++nIndex)
+ {
+ dStartTime += m_vecRawFlowPoints[nIndex].x();
+ }
+ double dEndTime = dStartTime +
+ m_vecRawFlowPoints[m_nSelectedFlowSegment].x();
+
+ m_pPressureChart->setHighlightedTimeRange(
+ dStartTime, dEndTime);
+ m_pFlowChart->setHighlightedTimeRange(dStartTime, dEndTime);
+}
+
+void nmWellPressureFlowPage::applyFlowSegmentSelection(int nSegmentIndex)
+{
+ if (nSegmentIndex < 0 ||
+ nSegmentIndex >= m_vecRawFlowPoints.size() ||
+ nSegmentIndex >= m_pFlowTable->rowCount())
+ {
+ nSegmentIndex = -1;
+ }
+
+ m_nSelectedFlowSegment = nSegmentIndex;
+ bool bSignalsWereBlocked = m_pFlowTable->blockSignals(true);
+ if (m_nSelectedFlowSegment >= 0)
+ {
+ m_pFlowTable->setCurrentCell(m_nSelectedFlowSegment, 0);
+ m_pFlowTable->selectRow(m_nSelectedFlowSegment);
+ QTableWidgetItem* pItem = m_pFlowTable->item(
+ m_nSelectedFlowSegment, 0);
+ if (pItem != nullptr)
+ {
+ m_pFlowTable->scrollToItem(
+ pItem, QAbstractItemView::EnsureVisible);
+ }
+ }
+ else
+ {
+ m_pFlowTable->setCurrentCell(-1, -1);
+ m_pFlowTable->clearSelection();
+ }
+ m_pFlowTable->blockSignals(bSignalsWereBlocked);
+ updateSelectedFlowSegment();
+}
+
+int nmWellPressureFlowPage::findFlowSegmentByTableTime(
+ double dTimePoint) const
+{
+ double dTotalTime = 0.0;
+ for (int nIndex = 0;
+ nIndex < m_vecTableFlowPoints.size();
+ ++nIndex)
+ {
+ dTotalTime += qMax(0.0, m_vecTableFlowPoints[nIndex].x());
+ }
+
+ const double dTolerance = qMax(1.0, qAbs(dTotalTime)) * 1e-9;
+ if (dTimePoint < 0.0 ||
+ dTimePoint > dTotalTime + dTolerance)
+ {
+ return -1;
+ }
+
+ // 零时长点不占区间;分界点继续查找后一有效段。
+ double dStartTime = 0.0;
+ int nLastValidSegment = -1;
+ for (int nIndex = 0;
+ nIndex < m_vecTableFlowPoints.size();
+ ++nIndex)
+ {
+ double dDuration = m_vecTableFlowPoints[nIndex].x();
+ if (dDuration <= dTolerance)
+ {
+ continue;
+ }
+
+ nLastValidSegment = nIndex;
+ double dEndTime = dStartTime + dDuration;
+ if (dTimePoint < dEndTime - dTolerance)
+ {
+ return nIndex;
+ }
+ dStartTime = dEndTime;
+ }
+
+ // 最终结束点没有后一段,归入最后一个有效流量段。
+ if (nLastValidSegment >= 0 &&
+ qAbs(dTimePoint - dTotalTime) <= dTolerance)
+ {
+ return nLastValidSegment;
+ }
+ return -1;
+}
+
+void nmWellPressureFlowPage::resetDataViews()
+{
+ m_vecRawPressurePoints.clear();
+ m_vecRawFlowPoints.clear();
+ m_vecTableFlowPoints.clear();
+ m_nSelectedFlowSegment = -1;
+ updateViews();
+}
diff --git a/Src/nmNum/nmSubWxs/nmWxPressFlowChartWidget.cpp b/Src/nmNum/nmSubWxs/nmWxPressFlowChartWidget.cpp
index 897f5ac..bdfae42 100644
--- a/Src/nmNum/nmSubWxs/nmWxPressFlowChartWidget.cpp
+++ b/Src/nmNum/nmSubWxs/nmWxPressFlowChartWidget.cpp
@@ -1,6 +1,4 @@
-#include "nmWxPressFlowChartWidget.h"
-
-#include "nmDataAnalyzeManager.h"
+#include "nmWxPressFlowChartWidget.h"
#include
#include
@@ -15,11 +13,20 @@ nmWxPressFlowChartWidget::nmWxPressFlowChartWidget(QWidget *parent)
, m_chartType(CHART_TYPE_LINE) // 默认为连续线条
, m_minX(0), m_maxX(1)
, m_minY(0), m_maxY(1)
- , m_marginLeft(45)
+ , m_marginLeft(52)
, m_marginRight(10)
, m_marginTop(2)
, m_marginBottom(40)
, m_showXAxisLabels(true) // 默认显示X轴标签
+ , m_bReadOnly(false)
+ , m_bTimeSelectionEnabled(true)
+ , m_bFixedXAxisRange(false)
+ , m_dFixedMinimumX(0.0)
+ , m_dFixedMaximumX(1.0)
+ , m_bHighlightTimeRange(false)
+ , m_dHighlightStartTime(0.0)
+ , m_dHighlightEndTime(0.0)
+ , m_pCurrentWell(nullptr)
, m_isDraggingBoundary(false)
, m_draggingBoundaryIndex(-1)
, m_draggingBoundaryTime(0.0)
@@ -28,12 +35,83 @@ nmWxPressFlowChartWidget::nmWxPressFlowChartWidget(QWidget *parent)
, m_externalDragBoundaryIndex(-1)
, m_externalDragBoundaryTime(0.0)
{
- m_currentWell = nmDataAnalyzeManager::getCurrentInstance()->getCurWellData();
setMinimumSize(300, 200);
setMouseTracking(true);
setFocusPolicy(Qt::StrongFocus);
}
+void nmWxPressFlowChartWidget::setReadOnly(bool bReadOnly)
+{
+ m_bReadOnly = bReadOnly;
+ if (m_bReadOnly) {
+ m_isDraggingBoundary = false;
+ m_draggingBoundaryIndex = -1;
+ m_draggingBoundaryTime = 0.0;
+ m_externalDragActive = false;
+ m_externalDragBoundaryIndex = -1;
+ m_externalDragBoundaryTime = 0.0;
+ setCursor(Qt::ArrowCursor);
+ }
+ update();
+}
+
+void nmWxPressFlowChartWidget::setTimeSelectionEnabled(bool bEnabled)
+{
+ m_bTimeSelectionEnabled = bEnabled;
+}
+
+void nmWxPressFlowChartWidget::setCurrentWellData(
+ nmDataWellBase* pWellData)
+{
+ m_pCurrentWell = pWellData;
+}
+
+void nmWxPressFlowChartWidget::setXAxisRange(double dMinimum,
+ double dMaximum)
+{
+ if (dMaximum < dMinimum) {
+ qSwap(dMinimum, dMaximum);
+ }
+ if (qAbs(dMaximum - dMinimum) < 1e-6) {
+ dMinimum -= 0.5;
+ dMaximum += 0.5;
+ }
+
+ m_bFixedXAxisRange = true;
+ m_dFixedMinimumX = dMinimum;
+ m_dFixedMaximumX = dMaximum;
+ calculateDataRange();
+ update();
+}
+
+void nmWxPressFlowChartWidget::clearXAxisRange()
+{
+ m_bFixedXAxisRange = false;
+ calculateDataRange();
+ update();
+}
+
+void nmWxPressFlowChartWidget::setHighlightedTimeRange(
+ double dStartTime,
+ double dEndTime)
+{
+ if (dEndTime < dStartTime) {
+ qSwap(dStartTime, dEndTime);
+ }
+ m_bHighlightTimeRange = true;
+ m_dHighlightStartTime = dStartTime;
+ m_dHighlightEndTime = dEndTime;
+ update();
+}
+
+void nmWxPressFlowChartWidget::clearHighlightedTimeRange()
+{
+ m_bHighlightTimeRange = false;
+ m_dHighlightStartTime = 0.0;
+ m_dHighlightEndTime = 0.0;
+ update();
+}
+
void nmWxPressFlowChartWidget::setData(const QVector& data)
{
m_data = data;
@@ -82,6 +160,7 @@ void nmWxPressFlowChartWidget::setShowXAxisLabels(bool show)
void nmWxPressFlowChartWidget::clearData()
{
m_data.clear();
+ calculateDataRange();
update();
}
@@ -97,8 +176,11 @@ void nmWxPressFlowChartWidget::paintEvent(QPaintEvent *event)
drawBackground(painter);
drawGrid(painter);
+ drawHighlightedTimeRange(painter);
drawAxes(painter);
- drawSelectedFlowSegments(painter); // 绘制选中的流动段
+ if (!m_bReadOnly) {
+ drawSelectedFlowSegments(painter); // 绘制选中的流动段
+ }
drawData(painter);
drawLabels(painter);
}
@@ -109,6 +191,44 @@ void nmWxPressFlowChartWidget::drawBackground(QPainter& painter)
painter.fillRect(m_plotArea, Qt::white);
}
+void nmWxPressFlowChartWidget::drawHighlightedTimeRange(
+ QPainter& painter)
+{
+ const double dXRange = m_maxX - m_minX;
+ if (!m_bHighlightTimeRange || dXRange <= 0.0 ||
+ m_plotArea.width() <= 0 || m_plotArea.height() <= 0) {
+ return;
+ }
+
+ double dStartTime = qMax(m_minX,
+ qMin(m_maxX, m_dHighlightStartTime));
+ double dEndTime = qMax(m_minX,
+ qMin(m_maxX, m_dHighlightEndTime));
+ double dStartX = m_plotArea.left() +
+ (dStartTime - m_minX) / dXRange * m_plotArea.width();
+ double dEndX = m_plotArea.left() +
+ (dEndTime - m_minX) / dXRange * m_plotArea.width();
+
+ painter.save();
+ QColor oBorderColor(45, 105, 175, 190);
+ if (qAbs(dEndX - dStartX) < 1.0) {
+ painter.setPen(QPen(oBorderColor, 2));
+ painter.drawLine(QPointF(dStartX, m_plotArea.top()),
+ QPointF(dStartX, m_plotArea.bottom()));
+ } else {
+ QRectF oHighlightRect(
+ QPointF(qMin(dStartX, dEndX), m_plotArea.top()),
+ QPointF(qMax(dStartX, dEndX), m_plotArea.bottom()));
+ painter.fillRect(oHighlightRect, QColor(70, 135, 200, 45));
+ painter.setPen(QPen(oBorderColor, 1));
+ painter.drawLine(oHighlightRect.topLeft(),
+ oHighlightRect.bottomLeft());
+ painter.drawLine(oHighlightRect.topRight(),
+ oHighlightRect.bottomRight());
+ }
+ painter.restore();
+}
+
double nmWxPressFlowChartWidget::calculateTickInterval(double range, bool isYAxisForPressure)
{
double roughInterval = range / 4.0; // 目标是5个主刻度
@@ -315,7 +435,9 @@ void nmWxPressFlowChartWidget::drawData(QPainter& painter)
void nmWxPressFlowChartWidget::drawLineData(QPainter& painter)
{
- if (m_data.size() < 2) return;
+ if (m_data.isEmpty()) {
+ return;
+ }
// 绘制连接线
painter.setPen(QPen(m_lineColor, 2));
@@ -481,7 +603,8 @@ void nmWxPressFlowChartWidget::drawSelectedFlowSegments(QPainter& painter)
void nmWxPressFlowChartWidget::calculateDataRange()
{
if (m_data.isEmpty()) {
- m_minX = 0; m_maxX = 1;
+ m_minX = m_bFixedXAxisRange ? m_dFixedMinimumX : 0.0;
+ m_maxX = m_bFixedXAxisRange ? m_dFixedMaximumX : 1.0;
m_minY = 0; m_maxY = 1;
return;
}
@@ -496,12 +619,18 @@ void nmWxPressFlowChartWidget::calculateDataRange()
m_maxY = qMax(m_maxY, point.y());
}
- // 添加一些边距
- double xMargin = (m_maxX - m_minX) * 0.02;
+ // 固定范围由调用方统一决定,默认模式继续保留原有横向边距。
+ double xMargin = m_bFixedXAxisRange
+ ? 0.0 : (m_maxX - m_minX) * 0.02;
double yMargin = (m_maxY - m_minY) * 0.1;
- m_minX -= xMargin;
- m_maxX += xMargin;
+ if (m_bFixedXAxisRange) {
+ m_minX = m_dFixedMinimumX;
+ m_maxX = m_dFixedMaximumX;
+ } else {
+ m_minX -= xMargin;
+ m_maxX += xMargin;
+ }
m_minY -= yMargin;
m_maxY += yMargin;
@@ -534,6 +663,12 @@ QPointF nmWxPressFlowChartWidget::screenToData(const QPointF& screenPoint)
void nmWxPressFlowChartWidget::mouseMoveEvent(QMouseEvent *event)
{
+ if (m_bReadOnly) {
+ setCursor(Qt::ArrowCursor);
+ QWidget::mouseMoveEvent(event);
+ return;
+ }
+
if(m_isDraggingBoundary && m_draggingBoundaryIndex >= 0) {
QPointF dataPoint = screenToData(event->pos());
double newTime = dataPoint.x();
@@ -589,6 +724,12 @@ void nmWxPressFlowChartWidget::mouseMoveEvent(QMouseEvent *event)
void nmWxPressFlowChartWidget::mouseReleaseEvent(QMouseEvent *event)
{
+ if (m_bReadOnly) {
+ setCursor(Qt::ArrowCursor);
+ QWidget::mouseReleaseEvent(event);
+ return;
+ }
+
if(m_isDraggingBoundary && event->button() == Qt::LeftButton) {
m_isDraggingBoundary = false;
setCursor(Qt::ArrowCursor);
@@ -665,11 +806,11 @@ bool nmWxPressFlowChartWidget::isBoundaryDraggable(int boundaryIndex) const
// 将时间吸附到最近的流量阶梯边界
double nmWxPressFlowChartWidget::snapToNearestFlowStep(double time) const
{
- if(!m_snapToRateChanges) {
+ if(!m_snapToRateChanges || m_pCurrentWell == nullptr) {
return time; // 未勾选对齐,直接返回
}
- return m_currentWell->findSmartFlowStepBoundary(time);
+ return m_pCurrentWell->findSmartFlowStepBoundary(time);
}
// 检查是否应该合并段
@@ -706,6 +847,19 @@ bool nmWxPressFlowChartWidget::shouldMergeSegments(double newBoundaryTime, int b
void nmWxPressFlowChartWidget::mousePressEvent(QMouseEvent *event)
{
+ if (m_bReadOnly) {
+ setCursor(Qt::ArrowCursor);
+ if (m_bTimeSelectionEnabled &&
+ event->button() == Qt::LeftButton &&
+ m_plotArea.contains(event->pos())) {
+ const QPointF dataPoint = screenToData(event->pos());
+ emit chartPointClicked(dataPoint.x());
+ return;
+ }
+ QWidget::mousePressEvent(event);
+ return;
+ }
+
if (event->button() == Qt::LeftButton && m_plotArea.contains(event->pos())) {
// 首先检查是否点击在分界线附近
int boundaryIndex = findBoundaryAtPosition(event->pos());
@@ -729,7 +883,9 @@ void nmWxPressFlowChartWidget::mousePressEvent(QMouseEvent *event)
QPointF dataPoint = screenToData(event->pos());
double clickTime = dataPoint.x();
- emit chartPointClicked(clickTime);
+ if (m_bTimeSelectionEnabled) {
+ emit chartPointClicked(clickTime);
+ }
//int segmentIndex = nmDataAnalyzeManager::getCurrentInstance()->findFlowSegmentByTime(clickTime);
int segmentIndex = m_perforationData.findFlowSegmentByTime(clickTime);
@@ -759,6 +915,10 @@ void nmWxPressFlowChartWidget::setSnapToRateChanges(bool snap) {
void nmWxPressFlowChartWidget::setExternalDragState(bool isDragging, int boundaryIndex, double boundaryTime)
{
+ if (m_bReadOnly) {
+ return;
+ }
+
m_externalDragActive = isDragging;
m_externalDragBoundaryIndex = boundaryIndex;
m_externalDragBoundaryTime = boundaryTime;
diff --git a/Src/nmNum/nmSubWxs/nmWxReservoirPropertiesEditor.cpp b/Src/nmNum/nmSubWxs/nmWxReservoirPropertiesEditor.cpp
index fe0bd13..899d14d 100644
--- a/Src/nmNum/nmSubWxs/nmWxReservoirPropertiesEditor.cpp
+++ b/Src/nmNum/nmSubWxs/nmWxReservoirPropertiesEditor.cpp
@@ -251,7 +251,7 @@ QToolButton* nmWxReservoirPropertiesEditor::createCategoryButton(
QToolButton* pButton = new QToolButton(pParent);
pButton->setText(sText);
pButton->setIcon(zxLoadIcon(sIconName));
- pButton->setIconSize(QSize(25, 25));
+ pButton->setIconSize(QSize(28, 28));
pButton->setToolButtonStyle(Qt::ToolButtonTextUnderIcon);
// 分类按钮仅执行页面切换命令,不保留持续选中状态。
pButton->setCheckable(false);
@@ -267,7 +267,7 @@ QToolButton* nmWxReservoirPropertiesEditor::createHelpButton(
QToolButton* pButton = new QToolButton(pParent);
pButton->setText(tr("Help"));
pButton->setIcon(zxLoadIcon("Help"));
- pButton->setIconSize(QSize(25, 25));
+ pButton->setIconSize(QSize(28, 28));
pButton->setToolButtonStyle(Qt::ToolButtonTextUnderIcon);
pButton->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
connect(pButton, SIGNAL(clicked()), this, SLOT(slotHelp()));
diff --git a/Src/nmNum/nmSubWxs/nmWxTimeDependentSkin.cpp b/Src/nmNum/nmSubWxs/nmWxTimeDependentSkin.cpp
index e76dc8f..bc54d9b 100644
--- a/Src/nmNum/nmSubWxs/nmWxTimeDependentSkin.cpp
+++ b/Src/nmNum/nmSubWxs/nmWxTimeDependentSkin.cpp
@@ -1,4 +1,4 @@
-#include "nmWxTimeDependentSkin.h"
+#include "nmWxTimeDependentSkin.h"
#include "nmWxPerforationClosing.h"
#include "nmWxPressFlowChartWidget.h"
#include "nmWxChartWidget.h"
@@ -238,6 +238,8 @@ void nmWxTimeDependentSkin::reloadFromDataSource(nmDataWellBase* pWellData)
: nmDataAnalyzeManager::getCurrentInstance()->getCurWellData();
if (!m_currentWell) return;
+ if (m_pPressureChart) m_pPressureChart->setCurrentWellData(m_currentWell);
+ if (m_pFlowRateChart) m_pFlowRateChart->setCurrentWellData(m_currentWell);
// 2) 重新抓取并处理数据
pressurePoints = m_currentWell->getPressurePoints();
@@ -359,6 +361,7 @@ void nmWxTimeDependentSkin::initUI()
// 创建压力图表
m_pPressureChart = new nmWxPressFlowChartWidget(m_pChartSplitter);
+ m_pPressureChart->setCurrentWellData(m_currentWell);
m_pPressureChart->setAxisLabels(tr(""), tr("Pressure"));
m_pPressureChart->setAxisUnits(tr("hr"), tr("MPa"));
m_pPressureChart->setLineColor(QColor(50, 200, 50)); // 绿色
@@ -368,8 +371,9 @@ void nmWxTimeDependentSkin::initUI()
// 创建流量图表
m_pFlowRateChart = new nmWxPressFlowChartWidget(m_pChartSplitter);
+ m_pFlowRateChart->setCurrentWellData(m_currentWell);
m_pFlowRateChart->setAxisLabels(tr("Time"), tr("Flow"));
- m_pFlowRateChart->setAxisUnits(tr("hr"), tr("m3/d"));
+ m_pFlowRateChart->setAxisUnits(tr("hr"), tr("m^3/D"));
m_pFlowRateChart->setLineColor(QColor(255, 0, 0)); // 红色
m_pFlowRateChart->setChartType(CHART_TYPE_STEP);
m_pFlowRateChart->setMinimumHeight(150); // 设置最小高度防止过度压缩
diff --git a/Src/nmNum/nmSubWxs/nmWxWellEditor.cpp b/Src/nmNum/nmSubWxs/nmWxWellEditor.cpp
index 123bd96..d4705a6 100644
--- a/Src/nmNum/nmSubWxs/nmWxWellEditor.cpp
+++ b/Src/nmNum/nmSubWxs/nmWxWellEditor.cpp
@@ -9,15 +9,18 @@
#include "nmWellEditorController.h"
#include "nmWellEditorSession.h"
#include "nmWellParameterPanel.h"
+#include "nmWellPressureFlowPage.h"
#include "nmWxTimeDependentSkin.h"
#include "nmWxWellboreTrajectoryDisplay.h"
#include "ZxBaseUtil.h"
#include
+#include
#include
#include
#include
#include
+#include
#include
#include
#include
@@ -31,6 +34,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -39,6 +43,9 @@ nmWxWellEditor::nmWxWellEditor(const nmWellEditContext& oContext,
QWidget* pParent)
: iDlgBase(pParent),
m_pSession(new nmWellEditorSession(oContext)),
+ m_pCategoryButtonGroup(nullptr),
+ m_pStackedWidget(nullptr),
+ m_pPressureFlowPage(nullptr),
m_pParameterPanel(nullptr),
m_pTrajectoryDisplay(nullptr),
m_pWellEditorController(nullptr),
@@ -51,6 +58,7 @@ nmWxWellEditor::nmWxWellEditor(const nmWellEditContext& oContext,
m_pButtonBox(nullptr),
m_pStatusLabel(nullptr),
m_pMainSplitter(nullptr),
+ m_nCurrentPage(NM_WELL_EDITOR_PAGE_PARAMETERS),
m_sPanelValidationMessage(),
m_bInitialized(false),
m_bChangingWellType(false)
@@ -69,7 +77,9 @@ nmWxWellEditor::~nmWxWellEditor()
// 界面和控制器仍引用工作副本,必须在会话释放井对象前销毁。
delete m_pWellEditorController;
m_pWellEditorController = nullptr;
- delete m_pMainSplitter;
+ delete m_pStackedWidget;
+ m_pStackedWidget = nullptr;
+ m_pPressureFlowPage = nullptr;
m_pMainSplitter = nullptr;
m_pParameterPanel = nullptr;
m_pTrajectoryDisplay = nullptr;
@@ -111,6 +121,10 @@ bool nmWxWellEditor::initialize(QString& sError)
createController();
m_bInitialized = true;
+ m_pPressureFlowPage->setWell(pWell);
+ m_pStackedWidget->setCurrentIndex(
+ NM_WELL_EDITOR_PAGE_PARAMETERS);
+ m_nCurrentPage = NM_WELL_EDITOR_PAGE_PARAMETERS;
updateAcceptState();
sError.clear();
return true;
@@ -277,9 +291,32 @@ void nmWxWellEditor::slotWellTypeChanged(int nIndex)
}
m_pTrajectoryDisplay->setCurrentWellboreData(pWell);
createController();
+ m_pPressureFlowPage->setWell(pWell);
updateAcceptState();
}
+void nmWxWellEditor::slotCategoryButtonClicked(int nPage)
+{
+ if (!m_bInitialized || m_pStackedWidget == nullptr ||
+ nPage < 0 || nPage >= NM_WELL_EDITOR_PAGE_COUNT)
+ {
+ return;
+ }
+
+ if (nPage == m_nCurrentPage)
+ {
+ return;
+ }
+
+ // 查看页只读取当前工作副本,不提交或校验参数页中的临时输入。
+ if (nPage == NM_WELL_EDITOR_PAGE_PRESSURE_FLOW)
+ {
+ reloadPressureFlowPage();
+ }
+ m_pStackedWidget->setCurrentIndex(nPage);
+ m_nCurrentPage = nPage;
+}
+
void nmWxWellEditor::slotWellNameEdited(const QString& sWellName)
{
if (m_pSession != nullptr)
@@ -312,6 +349,18 @@ void nmWxWellEditor::reloadTimeDependentSkinControls(
m_pTimeDependentButton->setEnabled(bEnabled);
}
+void nmWxWellEditor::reloadPressureFlowPage()
+{
+ if (m_pPressureFlowPage == nullptr || m_pSession == nullptr)
+ {
+ return;
+ }
+
+ nmDataWellBase* pWell = m_pSession->currentWell();
+ m_pPressureFlowPage->setWell(pWell);
+ m_pPressureFlowPage->refreshData();
+}
+
void nmWxWellEditor::slotEditTimeDependentSkin()
{
if (!m_bInitialized || m_pSession->currentWell() == nullptr)
@@ -384,10 +433,18 @@ void nmWxWellEditor::createUi()
{
QVBoxLayout* pMainLayout = new QVBoxLayout(this);
pMainLayout->setContentsMargins(12, 10, 12, 0);
- pMainLayout->setSpacing(8);
+ pMainLayout->setSpacing(0);
+ pMainLayout->addWidget(createCategoryBar());
+
+ m_pStackedWidget = new QStackedWidget(this);
+
+ // 第一步:井参数页完整保留原有参数区、轨迹视图和分割比例。
+ QWidget* pParameterPage = new QWidget(m_pStackedWidget);
+ QVBoxLayout* pPageLayout = new QVBoxLayout(pParameterPage);
+ pPageLayout->setContentsMargins(0, 6, 0, 0);
+ pPageLayout->setSpacing(8);
- // 第一步:左侧固定井信息并滚动参数分组,右侧保持常驻轨迹视图。
- m_pMainSplitter = new QSplitter(Qt::Horizontal, this);
+ m_pMainSplitter = new QSplitter(Qt::Horizontal, pParameterPage);
m_pMainSplitter->setChildrenCollapsible(false);
QWidget* pParameterContainer = new QWidget(m_pMainSplitter);
@@ -423,8 +480,17 @@ void nmWxWellEditor::createUi()
QList listSizes;
listSizes << 410 << 730;
m_pMainSplitter->setSizes(listSizes);
- // 第三步:底部命令区跨越两列,并集中连接编辑状态信号。
- pMainLayout->addWidget(m_pMainSplitter, 1);
+ pPageLayout->addWidget(m_pMainSplitter, 1);
+
+ // 第二步:只读压力流量页与参数页常驻同一堆栈,切换时不重建控件。
+ m_pPressureFlowPage = new nmWellPressureFlowPage(m_pStackedWidget);
+ m_pStackedWidget->addWidget(pParameterPage);
+ m_pStackedWidget->addWidget(m_pPressureFlowPage);
+ m_pStackedWidget->setCurrentIndex(
+ NM_WELL_EDITOR_PAGE_PARAMETERS);
+ pMainLayout->addWidget(m_pStackedWidget, 1);
+
+ // 第三步:底部命令区在两个页面间共享,并集中连接编辑状态信号。
pMainLayout->addWidget(createFooter());
connect(m_pWellNameLineEdit, SIGNAL(textChanged(QString)),
@@ -438,6 +504,128 @@ void nmWxWellEditor::createUi()
this, SLOT(slotPanelValidationMessageChanged(QString)));
}
+QWidget* nmWxWellEditor::createCategoryBar()
+{
+ QGroupBox* pCategoryBar = new QGroupBox(this);
+ QHBoxLayout* pBarLayout = new QHBoxLayout(pCategoryBar);
+ pBarLayout->setContentsMargins(3, 3, 3, 3);
+ pBarLayout->setSpacing(0);
+
+ m_pCategoryButtonGroup = new QButtonGroup(this);
+
+ QHBoxLayout* pBasicLayout = nullptr;
+ QWidget* pBasicGroup = createRibbonGroup(
+ tr("Basic"), pCategoryBar, pBasicLayout);
+ pBasicLayout->insertWidget(0, createCategoryButton(
+ NM_WELL_EDITOR_PAGE_PARAMETERS,
+ tr("Well parameters"),
+ "NmWell",
+ pBasicGroup));
+ pBarLayout->addWidget(pBasicGroup);
+ pBarLayout->addWidget(createRibbonSeparator(pCategoryBar));
+
+ QHBoxLayout* pDataLayout = nullptr;
+ QWidget* pDataGroup = createRibbonGroup(
+ tr("Data"), pCategoryBar, pDataLayout);
+ pDataLayout->insertWidget(0, createCategoryButton(
+ NM_WELL_EDITOR_PAGE_PRESSURE_FLOW,
+ tr("Pressure and flow"),
+ "tAllData",
+ pDataGroup));
+ pBarLayout->addWidget(pDataGroup);
+ pBarLayout->addWidget(createRibbonSeparator(pCategoryBar));
+
+ QHBoxLayout* pHelpLayout = nullptr;
+ QWidget* pHelpGroup = createRibbonGroup(
+ tr("Help"), pCategoryBar, pHelpLayout);
+ pHelpLayout->insertWidget(0, createHelpButton(pHelpGroup));
+ pBarLayout->addWidget(pHelpGroup);
+ pBarLayout->addStretch(1);
+
+ connect(m_pCategoryButtonGroup, SIGNAL(buttonClicked(int)),
+ this, SLOT(slotCategoryButtonClicked(int)));
+ return pCategoryBar;
+}
+
+QWidget* nmWxWellEditor::createRibbonGroup(
+ const QString& sCaption,
+ QWidget* pParent,
+ QHBoxLayout*& pCommandLayout)
+{
+ QWidget* pGroupWidget = new QWidget(pParent);
+ QVBoxLayout* pGroupLayout = new QVBoxLayout(pGroupWidget);
+ pGroupLayout->setContentsMargins(11, 2, 11, 1);
+ pGroupLayout->setSpacing(4);
+
+ QWidget* pCommandWidget = new QWidget(pGroupWidget);
+ pCommandLayout = new QHBoxLayout(pCommandWidget);
+ pCommandLayout->setContentsMargins(0, 0, 0, 0);
+ pCommandLayout->setSpacing(6);
+ pCommandLayout->addStretch();
+ pGroupLayout->addWidget(pCommandWidget, 1);
+
+ QLabel* pCaptionLabel = new QLabel(sCaption, pGroupWidget);
+ pCaptionLabel->setAlignment(Qt::AlignCenter);
+ pCaptionLabel->setFixedHeight(16);
+ QFont oCaptionFont = pCaptionLabel->font();
+ oCaptionFont.setUnderline(true);
+ pCaptionLabel->setFont(oCaptionFont);
+ QPalette oCaptionPalette = pCaptionLabel->palette();
+ oCaptionPalette.setColor(
+ QPalette::WindowText,
+ pGroupWidget->palette().color(QPalette::Mid));
+ pCaptionLabel->setPalette(oCaptionPalette);
+ pGroupLayout->addWidget(pCaptionLabel);
+ return pGroupWidget;
+}
+
+QFrame* nmWxWellEditor::createRibbonSeparator(QWidget* pParent)
+{
+ QFrame* pSeparator = new QFrame(pParent);
+ pSeparator->setFrameShape(QFrame::VLine);
+ pSeparator->setFrameShadow(QFrame::Plain);
+ pSeparator->setLineWidth(1);
+ QPalette oSeparatorPalette = pSeparator->palette();
+ oSeparatorPalette.setColor(
+ QPalette::WindowText,
+ pParent->palette().color(QPalette::Mid));
+ pSeparator->setPalette(oSeparatorPalette);
+ pSeparator->setSizePolicy(
+ QSizePolicy::Fixed, QSizePolicy::Expanding);
+ return pSeparator;
+}
+
+QToolButton* nmWxWellEditor::createCategoryButton(
+ int nPage,
+ const QString& sText,
+ const QString& sIconName,
+ QWidget* pParent)
+{
+ QToolButton* pButton = new QToolButton(pParent);
+ pButton->setText(sText);
+ pButton->setIcon(zxLoadIcon(sIconName));
+ pButton->setIconSize(QSize(28, 28));
+ pButton->setToolButtonStyle(Qt::ToolButtonTextUnderIcon);
+ pButton->setCheckable(false);
+ pButton->setSizePolicy(
+ QSizePolicy::Preferred, QSizePolicy::Fixed);
+ m_pCategoryButtonGroup->addButton(pButton, nPage);
+ return pButton;
+}
+
+QToolButton* nmWxWellEditor::createHelpButton(QWidget* pParent)
+{
+ QToolButton* pButton = new QToolButton(pParent);
+ pButton->setText(tr("Help"));
+ pButton->setIcon(zxLoadIcon("Help"));
+ pButton->setIconSize(QSize(28, 28));
+ pButton->setToolButtonStyle(Qt::ToolButtonTextUnderIcon);
+ pButton->setSizePolicy(
+ QSizePolicy::Preferred, QSizePolicy::Fixed);
+ connect(pButton, SIGNAL(clicked()), this, SLOT(slotHelp()));
+ return pButton;
+}
+
QWidget* nmWxWellEditor::createWellInformationWidget(QWidget* pParent)
{
QGroupBox* pInformationGroup = new QGroupBox(