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.
193 lines
5.7 KiB
C
193 lines
5.7 KiB
C
|
3 weeks ago
|
#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
|