You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
nmWTAI-Platform/Include/nmNum/nmSubWxs/nmWxPressFlowChartWidget.h

193 lines
5.7 KiB
C

#ifndef NMWXPRESSFLOWCHARTWIDGET_H
#define NMWXPRESSFLOWCHARTWIDGET_H
#include <QWidget>
#include <QVector>
#include <QPointF>
#include <QPainter>
#include <QMouseEvent>
#include <QRect>
#include "nmDataPerforation.h"
#include "nmDataWellBase.h"
enum ChartType {
CHART_TYPE_LINE, // 连续线条(用于压力)
CHART_TYPE_STEP // 阶梯图(用于流量)
};
namespace {
struct AxisTicks {
QVector<double> majors;
QVector<double> minors;
double mainInterval;
double minorInterval;
AxisTicks() : mainInterval(0.0), minorInterval(0.0) {}
};
static inline bool approxEqual(double a, double b, double eps) {
return qAbs(a - b) <= eps;
}
// 生成 [mn, mx] 的主/次刻度
AxisTicks makeTicks(double mn, double mx, double mainInterval, double minorInterval) {
AxisTicks t;
if (mx <= mn || mainInterval <= 0.0) return t;
t.mainInterval = mainInterval;
t.minorInterval = (minorInterval > 0.0 ? minorInterval : 0.0);
const double startMajor = floor(mn / mainInterval) * mainInterval;
double v;
for (v = startMajor; v <= mx + mainInterval * 0.5; v += mainInterval) {
if (v >= mn - 1e-9 && v <= mx + 1e-9) t.majors.append(v);
}
if (t.minorInterval > 0.0) {
const double startMinor = floor(mn / t.minorInterval) * t.minorInterval;
for (v = startMinor; v <= mx + t.minorInterval * 0.5; v += t.minorInterval) {
if (v < mn - 1e-9 || v > mx + 1e-9) continue;
bool isMajor = false;
for (int i = 0; i < t.majors.size(); ++i) {
if (approxEqual(t.majors[i], v, 1e-9)) { isMajor = true; break; }
}
if (!isMajor) t.minors.append(v);
}
}
return t;
}
// 轴标签格式化
QString formatTickLabel(double v, bool preferInt, bool isPressureY) {
if (preferInt) {
const double iv = qRound(v);
if (qAbs(v - iv) < 1e-6) return QString::number((int)iv);
return QString::number(v, 'f', 1);
}
if (isPressureY) {
double twice = qRound(v * 2.0) / 2.0; // 允许 .5
if (qAbs(v - twice) < 1e-6) {
double iv = qRound(twice);
if (qAbs(twice - iv) < 1e-6) return QString::number((int)iv);
return QString::number(twice, 'f', 1);
}
return QString::number(v, 'f', 1);
}
const double iv = qRound(v);
if (qAbs(v - iv) < 1e-6) return QString::number((int)iv);
return QString::number(v, 'f', 1);
}
}
class nmWxPressFlowChartWidget : public QWidget
{
Q_OBJECT
public:
explicit nmWxPressFlowChartWidget(QWidget *parent = 0);
// 设置数据
void setData(const QVector<QPointF>& data);
void setTitle(const QString& title);
void setAxisLabels(const QString& xLabel, const QString& yLabel);
void setAxisUnits(const QString& xUnit, const QString& yUnit);
void setShowXAxisLabels(bool show);
void setLineColor(const QColor& color);
void setChartType(ChartType type);
double calculateTickInterval(double range, bool isYAxisForPressure);
// 清空数据
void clearData();
void setFlowSegmentData(const nmDataPerforation& perforationData);
// 设置外部拖动状态(当另一个图表正在拖动时调用)
void setExternalDragState(bool isDragging, int boundaryIndex, double boundaryTime);
// 清除外部拖动状态
void clearExternalDragState();
public slots:
void setSnapToRateChanges(bool snap);
signals:
void flowSegmentClicked(int segmentIndex);
void chartPointClicked(double timePoint);
void boundaryMoved(int boundaryIndex, double newTime);
void segmentsMerged(int segmentIndex);
void boundaryMovedFinal(int segmentIndex, double newTime);
protected:
void paintEvent(QPaintEvent *event);
void mousePressEvent(QMouseEvent *event);
void mouseMoveEvent(QMouseEvent *event);
void wheelEvent(QWheelEvent *event);
void mouseReleaseEvent(QMouseEvent *event) override;
private:
void drawBackground(QPainter& painter);
void drawGrid(QPainter& painter);
void drawAxes(QPainter& painter);
void drawData(QPainter& painter);
void drawLineData(QPainter& painter); // 绘制连续线条
void drawStepData(QPainter& painter); // 绘制阶梯图
void drawLabels(QPainter& painter);
void drawSelectedFlowSegments(QPainter& painter);
int findBoundaryAtPosition(const QPoint& pos, double tolerance = 5.0) const;
bool isBoundaryDraggable(int boundaryIndex) const;
double snapToNearestFlowStep(double time) const;
bool shouldMergeSegments(double newBoundaryTime, int boundaryIndex) const;
void updateLocalBoundaryPosition(int boundaryIndex, double newTime);
void calculateDataRange();
QPointF dataToScreen(const QPointF& dataPoint);
QPointF screenToData(const QPointF& screenPoint);
private:
QVector<QPointF> m_data;
QString m_title;
QString m_xLabel;
QString m_yLabel;
QString m_xUnit;
QString m_yUnit;
QColor m_lineColor;
ChartType m_chartType;
// 数据范围
double m_minX, m_maxX;
double m_minY, m_maxY;
// 绘图区域
QRect m_plotArea;
int m_marginLeft;
int m_marginRight;
int m_marginTop;
int m_marginBottom;
// 交互
QPoint m_lastMousePos;
bool m_showXAxisLabels;
// 流动段数据副本
nmDataPerforation m_perforationData;
// 当前井数据
nmDataWellBase* m_currentWell;
// 拖动相关的成员变量
bool m_isDraggingBoundary; // 是否正在拖动分界线
int m_draggingBoundaryIndex; // 正在拖动的分界线索引 (-1表示不在拖动)
double m_draggingBoundaryTime; // 拖动过程中的临时时间位置
bool m_snapToRateChanges; // 是否对齐到流量变化点
// 新增成员变量:跟踪外部拖动状态
bool m_externalDragActive;
int m_externalDragBoundaryIndex;
double m_externalDragBoundaryTime;
};
#endif // NMWXPRESSFLOWCHARTWIDGET_H