diff --git a/Include/nmNum/nmData/nmDataAnalyzeManager.h b/Include/nmNum/nmData/nmDataAnalyzeManager.h index 1bf892a..603049a 100644 --- a/Include/nmNum/nmData/nmDataAnalyzeManager.h +++ b/Include/nmNum/nmData/nmDataAnalyzeManager.h @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -348,6 +349,10 @@ public: // 初始化边界数据 nmDataOutline* createOutline(); nmDataOutline* getOutlineData(); + /** @brief 按当前数值井包围盒及四周 1500m 留量创建一次性默认矩形边界。 */ + bool createDefaultOutlineFromWells(); + /** @brief 计算当前数值分析全部有效井位置的包围盒。 */ + bool calculateNumericalWellBounds(QRectF& oBounds) const; // 移除边界数据 bool removeOutlineData(); // 返回边界副本数据 diff --git a/Include/nmNum/nmData/nmDataAxis.h b/Include/nmNum/nmData/nmDataAxis.h index 34d270a..a22d435 100644 --- a/Include/nmNum/nmData/nmDataAxis.h +++ b/Include/nmNum/nmData/nmDataAxis.h @@ -17,6 +17,11 @@ public: // 实现 nmDataBase 的抽象方法:从 RapidJSON Value 反序列化数据到当前 C++ 对象 virtual void FromJsonValue(const rapidjson::Value& jsonValue) override; + /** @brief 返回 Map 是否已经完成首次等比例坐标轴初始化。 */ + bool isInitialViewFitted() const; + /** @brief 记录 Map 的首次等比例坐标轴初始化是否完成。 */ + void setInitialViewFitted(bool bInitialViewFitted); + void setXMax(const nmDataAttribute& attr); nmDataAttribute& getXMax(); void setXMin(const nmDataAttribute& attr); @@ -37,6 +42,7 @@ public: private: + bool m_bInitialViewFitted; ///< 完成后不再因数据或窗口变化自动修改坐标轴。 nmDataAttribute m_xMax; nmDataAttribute m_xMin; nmDataAttribute m_yMax; diff --git a/Include/nmNum/nmPlot/nmGuiPlot.h b/Include/nmNum/nmPlot/nmGuiPlot.h index 9bb7cce..bd0fec8 100644 --- a/Include/nmNum/nmPlot/nmGuiPlot.h +++ b/Include/nmNum/nmPlot/nmGuiPlot.h @@ -4,6 +4,7 @@ #include "nmDefines.h" #include "nmPlot_global.h" #include +#include class nmObjBase; class nmObjPointWell; @@ -14,6 +15,7 @@ class nmDataAnalyzeManager; class nmDataWellBase; class nmPlotGraphicBinder; class QWidget; +class QResizeEvent; // 数值试井绘图载体的样例 class NM_PLOT_EXPORT nmGuiPlot : public iGuiPlot { @@ -97,6 +99,8 @@ class NM_PLOT_EXPORT nmGuiPlot : public iGuiPlot { #endif public slots: + /** @brief Map 首次显示稳定后,按内容和绘图区比例初始化一次坐标轴。 */ + void initializeAxisView(); // Obj选择状态改变 void slotObjSelChanged(bool); @@ -219,9 +223,27 @@ class NM_PLOT_EXPORT nmGuiPlot : public iGuiPlot { // 绘制Painter函数重载 virtual void paintEvent(QPaintEvent*) override; + /** @brief 首次视图尚未完成时,等待有效绘图区后再次尝试初始化。 */ + virtual void resizeEvent(QResizeEvent* pEvent) override; + + private: + /** @brief 计算边界及当前可见数值对象的数据坐标包围盒。 */ + bool calculateVisibleContentBounds(QRectF& oBounds) const; + /** @brief 以程序方式同时应用两个坐标轴范围,并写回当前 Manager。 */ + void applyAxisRange(double dXMin, double dXMax, + double dYMin, double dYMax); + /** @brief 按当前内容完成一次等比例视图计算,成功时返回 true。 */ + bool fitInitialAxisToContent(); + /** @brief 将首次视图初始化延迟到本轮布局完成后执行。 */ + void scheduleInitialAxisView(); + /** @brief 返回当前 Map 绑定的数据管理器,避免多窗口时误用全局当前对象。 */ + nmDataAnalyzeManager* dataManager() const; int m_iRegionMarkCount; //记录创建了多少个标记区域对象 nmPlotGraphicBinder* m_pGraphicBinder; // 图元-数据绑定协调器 + nmDataAnalyzeManager* m_pDataManager; // 当前 Map 借用的数据管理器,不负责销毁 + bool m_bApplyingAxisRange; // 正在由程序设置坐标轴,防止提前完成首次初始化 + bool m_bAxisInitializationPending; // 是否已排队一次首次坐标轴初始化 }; diff --git a/Src/nmNum/nmData/nmDataAnalyzeManager.cpp b/Src/nmNum/nmData/nmDataAnalyzeManager.cpp index 079a464..d7b6af6 100644 --- a/Src/nmNum/nmData/nmDataAnalyzeManager.cpp +++ b/Src/nmNum/nmData/nmDataAnalyzeManager.cpp @@ -136,6 +136,9 @@ static bool isSupportedNumericalWell(const nmDataWellBase* pWellData) eWellType == NM_WELL_MODEL::Horizontal_Fractured_Well; } +// 默认边界只在创建数值分析时计算一次,后续完全由用户编辑。 +static const double DEFAULT_OUTLINE_MARGIN_METERS = 1500.0; + // 将工程井井别转换为数值模块的稳定枚举。枚举值同时就是流量大字段索引。 static NM_WELL_CATEGORY frameworkWellCategory(const ZxDataWell* pWellData) { @@ -2679,6 +2682,81 @@ nmDataOutline *nmDataAnalyzeManager::createOutline() return m_outlineData; } +bool nmDataAnalyzeManager::calculateNumericalWellBounds(QRectF& oBounds) const +{ + bool bHasWell = false; + double dMinX = 0.0; + double dMaxX = 0.0; + double dMinY = 0.0; + double dMaxY = 0.0; + + foreach(nmDataWellBase* pWellData, m_vWellData) { + if(pWellData == nullptr) { + continue; + } + + const double dX = pWellData->getX().getValue().toDouble(); + const double dY = pWellData->getY().getValue().toDouble(); + if(!qIsFinite(dX) || !qIsFinite(dY)) { + continue; + } + + if(!bHasWell) { + dMinX = dMaxX = dX; + dMinY = dMaxY = dY; + bHasWell = true; + } else { + dMinX = qMin(dMinX, dX); + dMaxX = qMax(dMaxX, dX); + dMinY = qMin(dMinY, dY); + dMaxY = qMax(dMaxY, dY); + } + } + + if(!bHasWell) { + oBounds = QRectF(); + return false; + } + + oBounds = QRectF(QPointF(dMinX, dMinY), + QPointF(dMaxX, dMaxY)).normalized(); + return true; +} + +bool nmDataAnalyzeManager::createDefaultOutlineFromWells() +{ + if(m_outlineData != nullptr) { + return false; + } + + QRectF oWellBounds; + if(!calculateNumericalWellBounds(oWellBounds)) { + return false; + } + + nmDataOutline* pOutlineData = createOutline(); + if(pOutlineData == nullptr) { + return false; + } + + const double dLeft = oWellBounds.left() - DEFAULT_OUTLINE_MARGIN_METERS; + const double dRight = oWellBounds.right() + DEFAULT_OUTLINE_MARGIN_METERS; + const double dTop = oWellBounds.bottom() + DEFAULT_OUTLINE_MARGIN_METERS; + const double dBottom = oWellBounds.top() - DEFAULT_OUTLINE_MARGIN_METERS; + QVector vecPoints; + vecPoints << QPointF(dLeft, dTop) + << QPointF(dRight, dTop) + << QPointF(dRight, dBottom) + << QPointF(dLeft, dBottom); + + pOutlineData->setName(""); + pOutlineData->setOutlineType(NM_Rect_Outline_Type); + pOutlineData->setOutlinePoints(vecPoints); + pOutlineData->setFlowTypeList(QVector(vecPoints.size(), 0)); + notifyGeometryListChanged(); + return true; +} + nmDataOutline* nmDataAnalyzeManager::getOutlineData() { return m_outlineData; diff --git a/Src/nmNum/nmData/nmDataAxis.cpp b/Src/nmNum/nmData/nmDataAxis.cpp index 2f30a5a..a073e40 100644 --- a/Src/nmNum/nmData/nmDataAxis.cpp +++ b/Src/nmNum/nmData/nmDataAxis.cpp @@ -1,6 +1,7 @@ #include "nmDataAxis.h" nmDataAxis::nmDataAxis() + : m_bInitialViewFitted(false) { m_xMax = nmDataAttribute("AxisXmax", 2800.0, "m", UNIT_TYPE_LENGTH, QStringList(), QStringList() << "ft" << "m" << "cm" << "mm" << "in" << "0.1 in" << "mile" << "km"); m_xMin = nmDataAttribute("AxisXmin", -1100.0, "m", UNIT_TYPE_LENGTH, QStringList(), QStringList() << "ft" << "m" << "cm" << "mm" << "in" << "0.1 in" << "mile" << "km"); @@ -28,6 +29,7 @@ rapidjson::Value nmDataAxis::ToJsonValue(rapidjson::Document::AllocatorType& all axisObject.AddMember("AxisXmin", m_xMin.ToJsonValue(allocator), allocator); axisObject.AddMember("AxisYmax", m_yMax.ToJsonValue(allocator), allocator); axisObject.AddMember("AxisYmin", m_yMin.ToJsonValue(allocator), allocator); + axisObject.AddMember("InitialViewFitted", m_bInitialViewFitted, allocator); //axisObject.AddMember("MainScaleX", m_xMainScale.ToJsonValue(allocator), allocator); //axisObject.AddMember("SubScaleX", m_xSubScale.ToJsonValue(allocator), allocator); @@ -40,6 +42,13 @@ rapidjson::Value nmDataAxis::ToJsonValue(rapidjson::Document::AllocatorType& all // 从 RapidJSON Value 反序列化数据到 nmDataAxis void nmDataAxis::FromJsonValue(const rapidjson::Value& jsonValue) { + // 缺少该字段的已有成果已经保存了轴范围,加载时不得擅自覆盖。 + m_bInitialViewFitted = true; + if (jsonValue.HasMember("InitialViewFitted") && + jsonValue["InitialViewFitted"].IsBool()) { + m_bInitialViewFitted = jsonValue["InitialViewFitted"].GetBool(); + } + // 反序列化 nmDataAttribute 类型的成员 // 调用 nmDataAttribute 自身的 FromJsonValue 方法进行递归反序列化 if (jsonValue.HasMember("AxisXmax") && jsonValue["AxisXmax"].IsObject()) { @@ -69,6 +78,16 @@ void nmDataAxis::FromJsonValue(const rapidjson::Value& jsonValue) //} } +bool nmDataAxis::isInitialViewFitted() const +{ + return m_bInitialViewFitted; +} + +void nmDataAxis::setInitialViewFitted(bool bInitialViewFitted) +{ + m_bInitialViewFitted = bInitialViewFitted; +} + nmDataAttribute& nmDataAxis::getXMax() { return m_xMax; diff --git a/Src/nmNum/nmPlot/nmGuiPlot.cpp b/Src/nmNum/nmPlot/nmGuiPlot.cpp index 77df26c..37b6d99 100644 --- a/Src/nmNum/nmPlot/nmGuiPlot.cpp +++ b/Src/nmNum/nmPlot/nmGuiPlot.cpp @@ -64,10 +64,57 @@ #include +#include +#include #include +namespace { + +void includeMapPoint(const QPointF& oPoint, + bool& bHasBounds, + double& dMinX, + double& dMaxX, + double& dMinY, + double& dMaxY) +{ + if(!qIsFinite(oPoint.x()) || !qIsFinite(oPoint.y())) { + return; + } + + if(!bHasBounds) { + dMinX = dMaxX = oPoint.x(); + dMinY = dMaxY = oPoint.y(); + bHasBounds = true; + return; + } + + dMinX = qMin(dMinX, oPoint.x()); + dMaxX = qMax(dMaxX, oPoint.x()); + dMinY = qMin(dMinY, oPoint.y()); + dMaxY = qMax(dMaxY, oPoint.y()); +} + +void includeMapPoints(const QVector& vecPoints, + bool& bHasBounds, + double& dMinX, + double& dMaxX, + double& dMinY, + double& dMaxY) +{ + foreach(const QPointF& oPoint, vecPoints) { + includeMapPoint(oPoint, bHasBounds, + dMinX, dMaxX, dMinY, dMaxY); + } +} + +} + nmGuiPlot::nmGuiPlot(bool bUseBtns, QWidget *parent) : - iGuiPlot(bUseBtns, parent) + iGuiPlot(bUseBtns, parent), + m_pGraphicBinder(NULL), + m_pDataManager(nmDataAnalyzeManager::getCurrentInstance()), + m_bApplyingAxisRange(false), + m_bAxisInitializationPending(false) { m_bUseBtns = bUseBtns; @@ -86,8 +133,7 @@ nmGuiPlot::nmGuiPlot(bool bUseBtns, QWidget *parent) : m_iRegionMarkCount = 0; - m_pGraphicBinder = new nmPlotGraphicBinder( - nmDataAnalyzeManager::getCurrentInstance(), this); + m_pGraphicBinder = new nmPlotGraphicBinder(m_pDataManager, this); } @@ -131,7 +177,6 @@ void nmGuiPlot::initChartView(QString sTitle, QSize szDefault) m_pPlot = m_pPlotScene->m_pPlot; m_pPlotView->setMargins(0, 0, 0, 0); m_pPlotView->setZoomMode(ZxRenderView::eZoomFitWindow, true); - connectSignals(); initDefultGeoObj(); @@ -179,68 +224,21 @@ void nmGuiPlot::cancelActiveTools() void nmGuiPlot::initDefultGeoObj() { - // 获取边界数据,来自数据中心 - nmDataOutline* pOutlineData = nmDataAnalyzeManager::getCurrentInstance()->getOutlineData(); + nmDataAnalyzeManager* pDataManager = dataManager(); + if(pDataManager == nullptr) { + return; + } - nmDataAxis* pAxisData = nmDataAnalyzeManager::getCurrentInstance()->getAxisData(); + nmDataAxis* pAxisData = pDataManager->getAxisData(); if(pAxisData == nullptr) { pAxisData = new nmDataAxis; // 将 pAxisData 存入数据中心 - nmDataAnalyzeManager::getCurrentInstance()->setAxisData(pAxisData); - } - - // 获取 X 最大值 - nmDataAttribute xMaxAttr = pAxisData->getXMax(); - double xMaxValue = xMaxAttr.getValue().toDouble(); - m_pPlot->getMainAxisX()->setRangeMax(xMaxValue); - - // 获取 Y 最大值 - nmDataAttribute yMaxAttr = pAxisData->getYMax(); - double yMaxValue = yMaxAttr.getValue().toDouble(); - m_pPlot->getMainAxisY()->setRangeMax(yMaxValue); - - // 获取 X 最小值 - nmDataAttribute xMinAttr = pAxisData->getXMin(); - double xMinValue = xMinAttr.getValue().toDouble(); - m_pPlot->getMainAxisX()->setRangeMin(xMinValue); - - // 获取 Y 最小值 - nmDataAttribute yMinAttr = pAxisData->getYMin(); - double yMinValue = yMinAttr.getValue().toDouble(); - m_pPlot->getMainAxisY()->setRangeMin(yMinValue); - - connect(m_pPlot->getMainAxisX(), SIGNAL(sigRangeChanged(double, double, bool, bool)), - this, SLOT(onRangeXChanged(double, double, bool, bool))); - - connect(m_pPlot->getMainAxisY(), SIGNAL(sigRangeChanged(double, double, bool, bool)), - this, SLOT(onRangeYChanged(double, double, bool, bool))); - - // 数据中心无边界数据,创建默认边界数据 - if(pOutlineData == nullptr) { - pOutlineData = nmDataAnalyzeManager::getCurrentInstance()->createOutline(); - - // 设置默认边界名称 - pOutlineData->setName(""); - - // 设置边界点 - QVector vecPoints; - vecPoints << QPointF(-1000.00, 1000.00) - << QPointF(1000.00, 1000.00) - << QPointF(1000.00, -1000.00) - << QPointF(-1000.00, -1000.00); - pOutlineData->setOutlinePoints(vecPoints); - - // 设置边界类型 - pOutlineData->setOutlineType(NM_Rect_Outline_Type); - - // 设置流型列表(假设全部为0) - QVector vecFlowTypes(vecPoints.size(), 0); - pOutlineData->setFlowTypeList(vecFlowTypes); + pDataManager->setAxisData(pAxisData); } // 获取井数据,来自数据中心 - QVector vecWellDatas = nmDataAnalyzeManager::getCurrentInstance()->getWellDataList(); + QVector vecWellDatas = pDataManager->getWellDataList(); // 数据中心没有一口井数据,添加默认井数据 if(vecWellDatas.isEmpty()) { @@ -248,7 +246,7 @@ void nmGuiPlot::initDefultGeoObj() ZxDataWell* pWellData = zxCurWell; if(pWellData == nullptr) { - this->setPlotsByDataManger(nmDataAnalyzeManager::getCurrentInstance()); + this->setPlotsByDataManger(pDataManager); return; } @@ -258,8 +256,7 @@ void nmGuiPlot::initDefultGeoObj() static_cast( pWellData->getWellTypeIndex()); if(!nmIsValidWellCategory(eWellCategory)) { - this->setPlotsByDataManger( - nmDataAnalyzeManager::getCurrentInstance()); + this->setPlotsByDataManger(pDataManager); return; } @@ -327,11 +324,11 @@ void nmGuiPlot::initDefultGeoObj() if(ZxBaseUtil::isSameStr(wellClass, "VerticalWell")) { // 初始化直井默认参数 - nmDataWellBase* pWell = nmDataAnalyzeManager::getCurrentInstance()->createWell(NM_WELL_MODEL::Vertical_Well); + nmDataWellBase* pWell = pDataManager->createWell(NM_WELL_MODEL::Vertical_Well); nmDataVerticalWell* m_VerticalWell = dynamic_cast(pWell); if(m_VerticalWell == nullptr) { - this->setPlotsByDataManger(nmDataAnalyzeManager::getCurrentInstance()); + this->setPlotsByDataManger(pDataManager); return; } @@ -353,7 +350,7 @@ void nmGuiPlot::initDefultGeoObj() m_VerticalWell->setFlowPoints(vecPtsF); // TODO: 计算井的历史双对数/半对数数据 - nmDataAnalyzeManager::getCurrentInstance()->calculationLogData(m_VerticalWell, vvecHistoryPressureData, vvecHistoryLogData, vvecHistorySemiLogData); + pDataManager->calculationLogData(m_VerticalWell, vvecHistoryPressureData, vvecHistoryLogData, vvecHistorySemiLogData); // 存储历史数据到井对象 m_VerticalWell->setHistoryPressure(vvecHistoryPressureData); @@ -361,15 +358,15 @@ void nmGuiPlot::initDefultGeoObj() m_VerticalWell->setHistorySemiLog(vvecHistorySemiLogData); // 设置为当前查看的井 - nmDataAnalyzeManager::getCurrentInstance()->setCurWellData(m_VerticalWell); + pDataManager->setCurWellData(m_VerticalWell); } else if(ZxBaseUtil::isSameStr(wellClass, "VerticalFracturedWell")) { // 初始化垂直裂缝井默认参数 - nmDataWellBase* pWell = nmDataAnalyzeManager::getCurrentInstance()->createWell(NM_WELL_MODEL::Vertical_Fractured_Well); + nmDataWellBase* pWell = pDataManager->createWell(NM_WELL_MODEL::Vertical_Fractured_Well); nmDataVerticalFracturedWell* m_VFracturedWell = dynamic_cast(pWell); if(m_VFracturedWell == nullptr) { - this->setPlotsByDataManger(nmDataAnalyzeManager::getCurrentInstance()); + this->setPlotsByDataManger(pDataManager); return; } @@ -391,7 +388,7 @@ void nmGuiPlot::initDefultGeoObj() m_VFracturedWell->setFlowPoints(vecPtsF); // TODO: 计算井的历史双对数/半对数数据 - nmDataAnalyzeManager::getCurrentInstance()->calculationLogData(m_VFracturedWell, vvecHistoryPressureData, vvecHistoryLogData, vvecHistorySemiLogData); + pDataManager->calculationLogData(m_VFracturedWell, vvecHistoryPressureData, vvecHistoryLogData, vvecHistorySemiLogData); // 存储历史数据到井对象 m_VFracturedWell->setHistoryPressure(vvecHistoryPressureData); @@ -402,15 +399,15 @@ void nmGuiPlot::initDefultGeoObj() m_VFracturedWell->setFracs(); // 设置为当前查看的井 - nmDataAnalyzeManager::getCurrentInstance()->setCurWellData(m_VFracturedWell); + pDataManager->setCurWellData(m_VFracturedWell); } else if(ZxBaseUtil::isSameStr(wellClass, "HorizontalMultiFracturedWell")) { // 初始化多段压裂水平井默认参数 - nmDataWellBase* pWell = nmDataAnalyzeManager::getCurrentInstance()->createWell(NM_WELL_MODEL::Horizontal_Fractured_Well); + nmDataWellBase* pWell = pDataManager->createWell(NM_WELL_MODEL::Horizontal_Fractured_Well); nmDataHorizontalFracturedWell* m_HFracturedWell = dynamic_cast(pWell); if(m_HFracturedWell == nullptr) { - this->setPlotsByDataManger(nmDataAnalyzeManager::getCurrentInstance()); + this->setPlotsByDataManger(pDataManager); return; } @@ -432,7 +429,7 @@ void nmGuiPlot::initDefultGeoObj() m_HFracturedWell->setFlowPoints(vecPtsF); // TODO: 计算井的历史双对数/半对数数据 - nmDataAnalyzeManager::getCurrentInstance()->calculationLogData(m_HFracturedWell, vvecHistoryPressureData, vvecHistoryLogData, vvecHistorySemiLogData); + pDataManager->calculationLogData(m_HFracturedWell, vvecHistoryPressureData, vvecHistoryLogData, vvecHistorySemiLogData); // 存储历史数据到井对象 m_HFracturedWell->setHistoryPressure(vvecHistoryPressureData); @@ -443,17 +440,21 @@ void nmGuiPlot::initDefultGeoObj() m_HFracturedWell->setFracs(); // 设置为当前查看的井 - nmDataAnalyzeManager::getCurrentInstance()->setCurWellData(m_HFracturedWell); + pDataManager->setCurWellData(m_HFracturedWell); } // 重新获取添加后的井数据 - nmDataAnalyzeManager::getCurrentInstance()->appendNmWellData( - nmDataAnalyzeManager::getCurrentInstance()->getCurWellData()); - vecWellDatas = nmDataAnalyzeManager::getCurrentInstance()->getWellDataList(); + pDataManager->appendNmWellData(pDataManager->getCurWellData()); + vecWellDatas = pDataManager->getWellDataList(); + } + + // 极端兼容入口没有预先建立边界时,也按当前井位置创建一次性默认边界。 + if(pDataManager->getOutlineData() == nullptr) { + pDataManager->createDefaultOutlineFromWells(); } // 根据数据中心的数据渲染所有的图元,上面只是为了保证打开时有一口井和边界 - this->setPlotsByDataManger(nmDataAnalyzeManager::getCurrentInstance()); + this->setPlotsByDataManger(pDataManager); } void nmGuiPlot::initAxisFromData(nmDataAnalyzeManager* pDataManager) @@ -461,33 +462,39 @@ void nmGuiPlot::initAxisFromData(nmDataAnalyzeManager* pDataManager) Q_ASSERT(nullptr != pDataManager); // 0.坐标轴 // 0.1 获取坐标轴数据,来自数据中心 - nmDataAxis* pAxisData = nmDataAnalyzeManager::getCurrentInstance()->getAxisData(); + nmDataAxis* pAxisData = pDataManager->getAxisData(); Q_ASSERT(nullptr != pAxisData); // 0.2 获取 X 最大值 nmDataAttribute xMaxAttr = pAxisData->getXMax(); double xMaxValue = xMaxAttr.getValue().toDouble(); - m_pPlot->getMainAxisX()->setRangeMax(xMaxValue); // 0.3 获取 Y 最大值 nmDataAttribute yMaxAttr = pAxisData->getYMax(); double yMaxValue = yMaxAttr.getValue().toDouble(); - m_pPlot->getMainAxisY()->setRangeMax(yMaxValue); // 0.4 获取 X 最小值 nmDataAttribute xMinAttr = pAxisData->getXMin(); double xMinValue = xMinAttr.getValue().toDouble(); - m_pPlot->getMainAxisX()->setRangeMin(xMinValue); // 0.5 获取 Y 最小值 nmDataAttribute yMinAttr = pAxisData->getYMin(); double yMinValue = yMinAttr.getValue().toDouble(); - m_pPlot->getMainAxisY()->setRangeMin(yMinValue); + + if(qIsFinite(xMinValue) && qIsFinite(xMaxValue) && + qIsFinite(yMinValue) && qIsFinite(yMaxValue) && + xMaxValue > xMinValue && yMaxValue > yMinValue) { + applyAxisRange(xMinValue, xMaxValue, yMinValue, yMaxValue); + } else { + pAxisData->setInitialViewFitted(false); + } connect(m_pPlot->getMainAxisX(), SIGNAL(sigRangeChanged(double, double, bool, bool)), - this, SLOT(onRangeXChanged(double, double, bool, bool))); + this, SLOT(onRangeXChanged(double, double, bool, bool)), + Qt::UniqueConnection); connect(m_pPlot->getMainAxisY(), SIGNAL(sigRangeChanged(double, double, bool, bool)), - this, SLOT(onRangeYChanged(double, double, bool, bool))); + this, SLOT(onRangeYChanged(double, double, bool, bool)), + Qt::UniqueConnection); } void nmGuiPlot::initBoundaryObjFromData(nmDataAnalyzeManager* pDataManager) @@ -785,13 +792,17 @@ void nmGuiPlot::initRegionMarkObjsFromData(nmDataAnalyzeManager* pDataManager) void nmGuiPlot::deleteAllPlotObjs() { - QVector vecObjs = this->getAllPlots(); + if(m_pPlot == nullptr) { + return; + } - foreach(nmObjBase* pObj, vecObjs) { - // 1、移除图元 - m_pPlot->removeObjByName(pObj->getName()); - // 2、清理数据 - //pObj->removeData(); + // 这里只清理显示对象,不能调用removeData()删除DataManager中的业务数据。 + // 先解除全部绑定,再倒序删除图元,避免重名对象残留或Binder保存悬空指针。 + if(m_pGraphicBinder != nullptr) { + m_pGraphicBinder->updateDataMgr(dataManager()); + } + for(int nIndex = m_pPlot->getObjCount() - 1; nIndex >= 0; --nIndex) { + m_pPlot->removeObjByIndex(nIndex); } } @@ -1493,9 +1504,222 @@ void nmGuiPlot::deleteAllDFNPlots() } } +nmDataAnalyzeManager* nmGuiPlot::dataManager() const +{ + return m_pDataManager != nullptr + ? m_pDataManager + : nmDataAnalyzeManager::getCurrentInstance(); +} + +bool nmGuiPlot::calculateVisibleContentBounds(QRectF& oBounds) const +{ + nmDataAnalyzeManager* pDataManager = dataManager(); + if(pDataManager == nullptr) { + return false; + } + + bool bHasBounds = false; + double dMinX = 0.0; + double dMaxX = 0.0; + double dMinY = 0.0; + double dMaxY = 0.0; + + nmDataOutline* pOutlineData = pDataManager->getOutlineData(); + if(pOutlineData != nullptr && pOutlineData->getPlotVisible()) { + if(pOutlineData->getOutlineType() == NM_Round_Outline_Type) { + const QPointF oCenter = pOutlineData->getCenter(); + const double dRadius = qAbs(pOutlineData->getRadius()); + includeMapPoint(QPointF(oCenter.x() - dRadius, + oCenter.y() - dRadius), + bHasBounds, dMinX, dMaxX, dMinY, dMaxY); + includeMapPoint(QPointF(oCenter.x() + dRadius, + oCenter.y() + dRadius), + bHasBounds, dMinX, dMaxX, dMinY, dMaxY); + } else { + includeMapPoints(pOutlineData->getOutlinePoints(), + bHasBounds, dMinX, dMaxX, dMinY, dMaxY); + } + } + + const QVector vecWells = pDataManager->getWellDataList(); + foreach(nmDataWellBase* pWellData, vecWells) { + if(pWellData != nullptr && pWellData->getPlotVisible()) { + includeMapPoint(QPointF( + pWellData->getX().getValue().toDouble(), + pWellData->getY().getValue().toDouble()), + bHasBounds, dMinX, dMaxX, dMinY, dMaxY); + } + } + + const QVector vecFaults = pDataManager->getFaultDataList(); + foreach(nmDataFault* pFaultData, vecFaults) { + if(pFaultData != nullptr && pFaultData->getPlotVisible()) { + includeMapPoints(pFaultData->getFaultPoints(), + bHasBounds, dMinX, dMaxX, dMinY, dMaxY); + } + } + + const QVector vecFractures = + pDataManager->getFractureDataList(); + foreach(nmDataFracture* pFractureData, vecFractures) { + if(pFractureData != nullptr && pFractureData->getPlotVisible()) { + includeMapPoints(pFractureData->getFracturePoints(), + bHasBounds, dMinX, dMaxX, dMinY, dMaxY); + } + } + + const QVector vecRegions = pDataManager->getRegionDataList(); + foreach(nmDataRegion* pRegionData, vecRegions) { + if(pRegionData != nullptr && pRegionData->getPlotVisible()) { + includeMapPoints(pRegionData->getVecPts(), + bHasBounds, dMinX, dMaxX, dMinY, dMaxY); + } + } + + const QVector vecRegionMarks = + pDataManager->getRegionMarkDataList(); + foreach(nmDataRegionMark* pRegionMarkData, vecRegionMarks) { + if(pRegionMarkData != nullptr && pRegionMarkData->getPlotVisible()) { + includeMapPoint(pRegionMarkData->getPtPos(), + bHasBounds, dMinX, dMaxX, dMinY, dMaxY); + } + } + + if(!bHasBounds) { + return false; + } + + oBounds = QRectF(QPointF(dMinX, dMinY), + QPointF(dMaxX, dMaxY)).normalized(); + return true; +} + +void nmGuiPlot::applyAxisRange(double dXMin, double dXMax, + double dYMin, double dYMax) +{ + if(m_pPlot == nullptr || + !qIsFinite(dXMin) || !qIsFinite(dXMax) || + !qIsFinite(dYMin) || !qIsFinite(dYMax) || + dXMax <= dXMin || dYMax <= dYMin) { + return; + } + + const bool bOldApplying = m_bApplyingAxisRange; + m_bApplyingAxisRange = true; + m_pPlot->getMainAxisX()->setRangeMinMax(dXMin, dXMax, true, false); + m_pPlot->getMainAxisY()->setRangeMinMax(dYMin, dYMax, true, false); + + nmDataAnalyzeManager* pDataManager = dataManager(); + nmDataAxis* pAxisData = pDataManager != nullptr + ? pDataManager->getAxisData() : nullptr; + if(pAxisData != nullptr) { + pAxisData->getXMin().setValue(dXMin); + pAxisData->getXMax().setValue(dXMax); + pAxisData->getYMin().setValue(dYMin); + pAxisData->getYMax().setValue(dYMax); + } + m_bApplyingAxisRange = bOldApplying; +} + +bool nmGuiPlot::fitInitialAxisToContent() +{ + if(m_pPlot == nullptr) { + return false; + } + + QRectF oBounds; + if(!calculateVisibleContentBounds(oBounds)) { + return false; + } + + QRectF oPlotRect = m_pPlot->getInnerRectF(); + if(oPlotRect.width() <= 0.0 || oPlotRect.height() <= 0.0) { + return false; + } + + double dCenterX = (oBounds.left() + oBounds.right()) * 0.5; + double dCenterY = (oBounds.top() + oBounds.bottom()) * 0.5; + double dRangeX = oBounds.width(); + double dRangeY = oBounds.height(); + + // 退化为点或直线时补足基础跨度,避免产生零范围坐标轴。 + if(dRangeX <= 0.0) { + dRangeX = 1500.0; + } + if(dRangeY <= 0.0) { + dRangeY = 1500.0; + } + + // 内容四周各增加最大边长的 5%,边界本身不因此发生变化。 + const double dPadding = qMax(dRangeX, dRangeY) * 0.05; + dRangeX += dPadding * 2.0; + dRangeY += dPadding * 2.0; + + const double dViewRatio = oPlotRect.width() / oPlotRect.height(); + const double dContentRatio = dRangeX / dRangeY; + if(dContentRatio < dViewRatio) { + dRangeX = dRangeY * dViewRatio; + } else { + dRangeY = dRangeX / dViewRatio; + } + + applyAxisRange(dCenterX - dRangeX * 0.5, + dCenterX + dRangeX * 0.5, + dCenterY - dRangeY * 0.5, + dCenterY + dRangeY * 0.5); + return true; +} + +void nmGuiPlot::scheduleInitialAxisView() +{ + if(m_bAxisInitializationPending) { + return; + } + + m_bAxisInitializationPending = true; + QTimer::singleShot(0, this, SLOT(initializeAxisView())); +} + +void nmGuiPlot::initializeAxisView() +{ + m_bAxisInitializationPending = false; + nmDataAnalyzeManager* pDataManager = dataManager(); + nmDataAxis* pAxisData = pDataManager != nullptr + ? pDataManager->getAxisData() : nullptr; + if(pAxisData == nullptr || pAxisData->isInitialViewFitted()) { + return; + } + + // 只有真实绘图区和图元均准备好后才封闭初始化,失败时等待下一次显示尺寸变化。 + if(fitInitialAxisToContent()) { + pAxisData->setInitialViewFitted(true); + } +} + +void nmGuiPlot::resizeEvent(QResizeEvent* pEvent) +{ + nmDataAnalyzeManager* pDataManager = dataManager(); + nmDataAxis* pAxisData = pDataManager != nullptr + ? pDataManager->getAxisData() : nullptr; + const bool bWaitingForInitialView = + pAxisData != nullptr && !pAxisData->isInitialViewFitted(); + const bool bOldApplying = m_bApplyingAxisRange; + if(bWaitingForInitialView) { + // 布局阶段可能触发轴信号,不能把它误认为用户已经设置了范围。 + m_bApplyingAxisRange = true; + } + iGuiPlot::resizeEvent(pEvent); + m_bApplyingAxisRange = bOldApplying; + if(bWaitingForInitialView) { + scheduleInitialAxisView(); + } +} + void nmGuiPlot::onRangeXChanged(double dMin, double dMax, bool bRecalTicks, bool bAdjustRectZoomRatio) { - nmDataAxis* pAxisData = nmDataAnalyzeManager::getCurrentInstance()->getAxisData(); + nmDataAnalyzeManager* pDataManager = dataManager(); + nmDataAxis* pAxisData = pDataManager != nullptr + ? pDataManager->getAxisData() : nullptr; if(pAxisData == nullptr) { return; @@ -1506,11 +1730,18 @@ void nmGuiPlot::onRangeXChanged(double dMin, double dMax, bool bRecalTicks, bool // 获取 X 最小值 pAxisData->getXMin().setValue(dMin); + + if(!m_bApplyingAxisRange) { + // 任一用户轴操作都终止首次初始化,后续范围完全由用户控制。 + pAxisData->setInitialViewFitted(true); + } } void nmGuiPlot::onRangeYChanged(double dMin, double dMax, bool bRecalTicks, bool bAdjustRectZoomRatio) { - nmDataAxis* pAxisData = nmDataAnalyzeManager::getCurrentInstance()->getAxisData(); + nmDataAnalyzeManager* pDataManager = dataManager(); + nmDataAxis* pAxisData = pDataManager != nullptr + ? pDataManager->getAxisData() : nullptr; if(pAxisData == nullptr) { return; @@ -1521,6 +1752,10 @@ void nmGuiPlot::onRangeYChanged(double dMin, double dMax, bool bRecalTicks, bool // 获取 Y 最小值 pAxisData->getYMin().setValue(dMin); + + if(!m_bApplyingAxisRange) { + pAxisData->setInitialViewFitted(true); + } } // ============================== 可见性设置方法实现 ============================== @@ -1762,6 +1997,25 @@ void nmGuiPlot::finishMeasure() bool nmGuiPlot::setPlotsByDataManger(nmDataAnalyzeManager* pDataManager) { + if(pDataManager == nullptr || m_pPlot == nullptr) { + return false; + } + + // 本函数按DataManager完整重建Map,重复调用也只能保留一套图元。 + // 清理过程不删除业务数据,因此同一分析刷新和跨分析切换可使用同一路径。 + deleteAllPlotObjs(); + if(m_pGraphicBinder != nullptr) { + m_pGraphicBinder->updateDataMgr(pDataManager); + } + m_pDataManager = pDataManager; + nmDataAxis* pAxisData = pDataManager->getAxisData(); + const bool bWaitingForInitialView = + pAxisData != nullptr && !pAxisData->isInitialViewFitted(); + const bool bOldApplying = m_bApplyingAxisRange; + if(bWaitingForInitialView) { + // 图元创建过程可能发出轴范围信号,首次适配完成前统一按内部更新处理。 + m_bApplyingAxisRange = true; + } // 0.坐标轴 this->initAxisFromData(pDataManager); // 1.边界 @@ -1776,6 +2030,12 @@ bool nmGuiPlot::setPlotsByDataManger(nmDataAnalyzeManager* pDataManager) this->initRegionObjsFromData(pDataManager); // 6.区域标记 this->initRegionMarkObjsFromData(pDataManager); + m_bApplyingAxisRange = bOldApplying; + + if(pAxisData != nullptr && !pAxisData->isInitialViewFitted()) { + // 等本轮布局和坐标轴内边距稳定后只初始化一次。 + scheduleInitialAxisView(); + } return true; } diff --git a/Src/nmNum/nmSubWnd/nmSubWndMain.cpp b/Src/nmNum/nmSubWnd/nmSubWndMain.cpp index f1b04b8..6798e82 100644 --- a/Src/nmNum/nmSubWnd/nmSubWndMain.cpp +++ b/Src/nmNum/nmSubWnd/nmSubWndMain.cpp @@ -2893,14 +2893,6 @@ void nmSubWndMain::updateMapByDataManager(nmDataAnalyzeManager* pDataManager) // 更新对应的Plot成员变量 pDataManager->setPlot(m_pWxPlot); - m_pWxPlot->deleteAllPlotObjs(); - - // 切换分析时更新 Binder 绑定的 DataManager, - // 确保用户新画断层时添加到正确的 DataManager - if(m_pWxPlot->graphicBinder()) { - m_pWxPlot->graphicBinder()->updateDataMgr(pDataManager); - } - - // 根据数据中心内容绘制新图元 + // 完整清理、Binder切换和快照刷新均由Map重建入口统一完成。 m_pWxPlot->setPlotsByDataManger(pDataManager); } diff --git a/Src/nmNum/nmSubWnd/nmSubWndUtils.cpp b/Src/nmNum/nmSubWnd/nmSubWndUtils.cpp index e7ab2eb..a04ce78 100644 --- a/Src/nmNum/nmSubWnd/nmSubWndUtils.cpp +++ b/Src/nmNum/nmSubWnd/nmSubWndUtils.cpp @@ -518,7 +518,7 @@ bool nmSubWndUtils::fillNmDockWxs(iSubWnd* pSubWnd) if(pAxisData == nullptr) { pAxisData = new nmDataAxis; // 将 pAxisData 存入数据中心 - nmDataAnalyzeManager::getCurrentInstance()->setAxisData(pAxisData); + pDataManager->setAxisData(pAxisData); } // 读取解析解的参数 @@ -659,28 +659,11 @@ bool nmSubWndUtils::fillNmDockWxs(iSubWnd* pSubWnd) pOutlineData->setOutlinePoints(vecPoints); } } else { - // 如果解析解这里没有设置边界,数值解设置默认边界 - // 创建默认矩形边界数据对象 - nmDataOutline* pDefaultOutlineData = pDataManager->createOutline(); - if(pDefaultOutlineData == nullptr) { + // 解析解未提供边界时,按当前数值井位置创建一次性默认边界。 + if(!pDataManager->createDefaultOutlineFromWells()) { return showNmDockInitializationError( tr("The default numerical boundary could not be created.")); } - Q_ASSERT(pDefaultOutlineData); - // 默认名称留空,由界面根据边界类型进行翻译显示。 - pDefaultOutlineData->setName(""); - // 设置边界点 - QVector vecPoints; - vecPoints << QPointF(-1000.00, 1000.00) - << QPointF(1000.00, 1000.00) - << QPointF(1000.00, -1000.00) - << QPointF(-1000.00, -1000.00); - pDefaultOutlineData->setOutlinePoints(vecPoints); - // 设置边界类型 - pDefaultOutlineData->setOutlineType(NM_Rect_Outline_Type); - // 设置流型列表(假设全部为0) - QVector vecFlowTypes(vecPoints.size(), 0); - pDefaultOutlineData->setFlowTypeList(vecFlowTypes); } // 1.6 创建时间步设置相关数据