|
|
#include "nmWxPressFlowChartWidget.h"
|
|
|
|
|
|
#include "nmDataAnalyzeManager.h"
|
|
|
|
|
|
#include <QPainter>
|
|
|
#include <QMouseEvent>
|
|
|
#include <QWheelEvent>
|
|
|
#include <QFont>
|
|
|
#include <QFontMetrics>
|
|
|
#include <cmath>
|
|
|
|
|
|
nmWxPressFlowChartWidget::nmWxPressFlowChartWidget(QWidget *parent)
|
|
|
: QWidget(parent)
|
|
|
, m_lineColor(Qt::blue)
|
|
|
, m_chartType(CHART_TYPE_LINE) // 默认为连续线条
|
|
|
, m_minX(0), m_maxX(1)
|
|
|
, m_minY(0), m_maxY(1)
|
|
|
, m_marginLeft(45)
|
|
|
, m_marginRight(10)
|
|
|
, m_marginTop(2)
|
|
|
, m_marginBottom(40)
|
|
|
, m_showXAxisLabels(true) // 默认显示X轴标签
|
|
|
, m_isDraggingBoundary(false)
|
|
|
, m_draggingBoundaryIndex(-1)
|
|
|
, m_draggingBoundaryTime(0.0)
|
|
|
, m_snapToRateChanges(true)
|
|
|
, m_externalDragActive(false)
|
|
|
, m_externalDragBoundaryIndex(-1)
|
|
|
, m_externalDragBoundaryTime(0.0)
|
|
|
{
|
|
|
m_currentWell = nmDataAnalyzeManager::getCurrentInstance()->getCurWellData();
|
|
|
setMinimumSize(300, 200);
|
|
|
setMouseTracking(true);
|
|
|
setFocusPolicy(Qt::StrongFocus);
|
|
|
}
|
|
|
|
|
|
void nmWxPressFlowChartWidget::setData(const QVector<QPointF>& data)
|
|
|
{
|
|
|
m_data = data;
|
|
|
calculateDataRange();
|
|
|
update();
|
|
|
}
|
|
|
|
|
|
void nmWxPressFlowChartWidget::setAxisLabels(const QString& xLabel, const QString& yLabel)
|
|
|
{
|
|
|
m_xLabel = xLabel;
|
|
|
m_yLabel = yLabel;
|
|
|
update();
|
|
|
}
|
|
|
|
|
|
void nmWxPressFlowChartWidget::setAxisUnits(const QString& xUnit, const QString& yUnit)
|
|
|
{
|
|
|
m_xUnit = xUnit;
|
|
|
m_yUnit = yUnit;
|
|
|
update();
|
|
|
}
|
|
|
|
|
|
void nmWxPressFlowChartWidget::setLineColor(const QColor& color)
|
|
|
{
|
|
|
m_lineColor = color;
|
|
|
update();
|
|
|
}
|
|
|
|
|
|
void nmWxPressFlowChartWidget::setChartType(ChartType type)
|
|
|
{
|
|
|
m_chartType = type;
|
|
|
update();
|
|
|
}
|
|
|
|
|
|
void nmWxPressFlowChartWidget::setShowXAxisLabels(bool show)
|
|
|
{
|
|
|
m_showXAxisLabels = show;
|
|
|
// 如果不显示X轴标签,减少底部边距
|
|
|
if (!show) {
|
|
|
m_marginBottom = 2;
|
|
|
} else {
|
|
|
m_marginBottom = 40;
|
|
|
}
|
|
|
update();
|
|
|
}
|
|
|
|
|
|
void nmWxPressFlowChartWidget::clearData()
|
|
|
{
|
|
|
m_data.clear();
|
|
|
update();
|
|
|
}
|
|
|
|
|
|
void nmWxPressFlowChartWidget::paintEvent(QPaintEvent *event)
|
|
|
{
|
|
|
QPainter painter(this);
|
|
|
painter.setRenderHint(QPainter::Antialiasing, true);
|
|
|
|
|
|
// 计算绘图区域
|
|
|
m_plotArea = QRect(m_marginLeft, m_marginTop,
|
|
|
width() - m_marginLeft - m_marginRight,
|
|
|
height() - m_marginTop - m_marginBottom);
|
|
|
|
|
|
drawBackground(painter);
|
|
|
drawGrid(painter);
|
|
|
drawAxes(painter);
|
|
|
drawSelectedFlowSegments(painter); // 绘制选中的流动段
|
|
|
drawData(painter);
|
|
|
drawLabels(painter);
|
|
|
}
|
|
|
|
|
|
void nmWxPressFlowChartWidget::drawBackground(QPainter& painter)
|
|
|
{
|
|
|
painter.fillRect(rect(), Qt::white);
|
|
|
painter.fillRect(m_plotArea, Qt::white);
|
|
|
}
|
|
|
|
|
|
double nmWxPressFlowChartWidget::calculateTickInterval(double range, bool isYAxisForPressure)
|
|
|
{
|
|
|
double roughInterval = range / 4.0; // 目标是5个主刻度
|
|
|
|
|
|
if (isYAxisForPressure) {
|
|
|
// 压力图Y轴:优先使用整数或0.5的倍数
|
|
|
// 计算数量级
|
|
|
double magnitude = pow(10.0, floor(log10(roughInterval)));
|
|
|
double normalized = roughInterval / magnitude;
|
|
|
|
|
|
// 选择合适的系数,允许0.5的倍数
|
|
|
double factor;
|
|
|
if (normalized <= 1.0) {
|
|
|
factor = 1.0;
|
|
|
} else if (normalized <= 2.0) {
|
|
|
factor = 2.0;
|
|
|
} else if (normalized <= 2.5) {
|
|
|
factor = 2.5; // 允许2.5倍数
|
|
|
} else if (normalized <= 5.0) {
|
|
|
factor = 5.0;
|
|
|
} else {
|
|
|
factor = 10.0;
|
|
|
}
|
|
|
|
|
|
return factor * magnitude;
|
|
|
} else {
|
|
|
// X轴和流量图Y轴:使用10的倍数
|
|
|
// 计算数量级
|
|
|
double magnitude = pow(10.0, floor(log10(roughInterval)));
|
|
|
double normalized = roughInterval / magnitude;
|
|
|
|
|
|
// 选择合适的系数:1, 2, 5, 10
|
|
|
double factor;
|
|
|
if (normalized <= 1.0) {
|
|
|
factor = 1.0;
|
|
|
} else if (normalized <= 2.0) {
|
|
|
factor = 2.0;
|
|
|
} else if (normalized <= 5.0) {
|
|
|
factor = 5.0;
|
|
|
} else {
|
|
|
factor = 10.0;
|
|
|
}
|
|
|
|
|
|
return factor * magnitude;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void nmWxPressFlowChartWidget::drawGrid(QPainter& painter)
|
|
|
{
|
|
|
const QColor majorGridColor(206, 206, 206);
|
|
|
const QColor minorGridColor(236, 236, 236);
|
|
|
|
|
|
const double xRange = m_maxX - m_minX;
|
|
|
const double yRange = m_maxY - m_minY;
|
|
|
if (xRange <= 0.0 || yRange <= 0.0) return;
|
|
|
|
|
|
const double sx = m_plotArea.width() / xRange;
|
|
|
const double sy = m_plotArea.height() / yRange;
|
|
|
|
|
|
// X:主刻度;次刻度 = 主/2
|
|
|
const double xMain = calculateTickInterval(xRange, false);
|
|
|
const double xMinor = xMain / 2.0;
|
|
|
|
|
|
// Y:沿用你原逻辑(压力图:原主/2 作为新主;次刻度 = 新主/2)
|
|
|
const bool isPressureChart = (m_lineColor == QColor(50, 200, 50));
|
|
|
const double yOrigMain = calculateTickInterval(yRange, isPressureChart);
|
|
|
const double yMain = yOrigMain / 2.0;
|
|
|
const double yMinor = yMain / 2.0;
|
|
|
|
|
|
const AxisTicks xt = makeTicks(m_minX, m_maxX, xMain, xMinor);
|
|
|
const AxisTicks yt = makeTicks(m_minY, m_maxY, yMain, yMinor);
|
|
|
|
|
|
painter.save();
|
|
|
painter.setRenderHint(QPainter::Antialiasing, true);
|
|
|
|
|
|
int i;
|
|
|
// 垂直网格:先画次刻度
|
|
|
painter.setPen(QPen(minorGridColor, 1));
|
|
|
for (i = 0; i < xt.minors.size(); ++i) {
|
|
|
double vx = xt.minors[i];
|
|
|
double x = m_plotArea.left() + (vx - m_minX) * sx + 0.5;
|
|
|
painter.drawLine(QPointF(x, m_plotArea.top()), QPointF(x, m_plotArea.bottom()));
|
|
|
}
|
|
|
painter.setPen(QPen(majorGridColor, 1));
|
|
|
for (i = 0; i < xt.majors.size(); ++i) {
|
|
|
double vx = xt.majors[i];
|
|
|
double x = m_plotArea.left() + (vx - m_minX) * sx + 0.5;
|
|
|
painter.drawLine(QPointF(x, m_plotArea.top()), QPointF(x, m_plotArea.bottom()));
|
|
|
}
|
|
|
|
|
|
// 水平网格:先画次刻度
|
|
|
painter.setPen(QPen(minorGridColor, 1));
|
|
|
for (i = 0; i < yt.minors.size(); ++i) {
|
|
|
double vy = yt.minors[i];
|
|
|
double y = m_plotArea.bottom() - (vy - m_minY) * sy + 0.5;
|
|
|
painter.drawLine(QPointF(m_plotArea.left(), y), QPointF(m_plotArea.right(), y));
|
|
|
}
|
|
|
painter.setPen(QPen(majorGridColor, 1));
|
|
|
for (i = 0; i < yt.majors.size(); ++i) {
|
|
|
double vy = yt.majors[i];
|
|
|
double y = m_plotArea.bottom() - (vy - m_minY) * sy + 0.5;
|
|
|
painter.drawLine(QPointF(m_plotArea.left(), y), QPointF(m_plotArea.right(), y));
|
|
|
}
|
|
|
|
|
|
painter.restore();
|
|
|
}
|
|
|
|
|
|
void nmWxPressFlowChartWidget::drawAxes(QPainter& painter)
|
|
|
{
|
|
|
// 边框/轴线(0.5像素对齐,锐利)
|
|
|
painter.save();
|
|
|
painter.setRenderHint(QPainter::Antialiasing, true);
|
|
|
painter.setPen(QPen(Qt::black, 1));
|
|
|
|
|
|
const double L = m_plotArea.left() + 0.5;
|
|
|
const double R = m_plotArea.right() + 0.5;
|
|
|
const double T = m_plotArea.top() + 0.5;
|
|
|
const double B = m_plotArea.bottom() + 0.5;
|
|
|
|
|
|
painter.drawLine(QPointF(L, B), QPointF(R, B)); // X轴(底边)
|
|
|
painter.drawLine(QPointF(L, B), QPointF(L, T)); // Y轴(左边)
|
|
|
painter.drawLine(QPointF(L, T), QPointF(R, T)); // 顶边
|
|
|
painter.drawLine(QPointF(R, T), QPointF(R, B)); // 右边
|
|
|
painter.restore();
|
|
|
|
|
|
const double xRange = m_maxX - m_minX;
|
|
|
const double yRange = m_maxY - m_minY;
|
|
|
if (xRange <= 0.0 || yRange <= 0.0) return;
|
|
|
|
|
|
const double sx = m_plotArea.width() / xRange;
|
|
|
const double sy = m_plotArea.height() / yRange;
|
|
|
|
|
|
const QColor majorGridColor(206, 206, 206);
|
|
|
const bool isPressureChart = (m_lineColor == QColor(50, 200, 50));
|
|
|
|
|
|
const double xMain = calculateTickInterval(xRange, false);
|
|
|
const AxisTicks xt = makeTicks(m_minX, m_maxX, xMain, 0.0);
|
|
|
|
|
|
const double yOrigMain = calculateTickInterval(yRange, isPressureChart);
|
|
|
const double yMain = yOrigMain / 2.0; // 新主刻度
|
|
|
const AxisTicks yt = makeTicks(m_minY, m_maxY, yMain, 0.0);
|
|
|
|
|
|
QFont font("Arial", 8);
|
|
|
painter.setFont(font);
|
|
|
|
|
|
// X轴主刻度与标签
|
|
|
painter.save();
|
|
|
painter.setPen(QPen(majorGridColor, 1));
|
|
|
int i;
|
|
|
for (i = 0; i < xt.majors.size(); ++i) {
|
|
|
double vx = xt.majors[i];
|
|
|
double x = m_plotArea.left() + (vx - m_minX) * sx + 0.5;
|
|
|
|
|
|
// 刻度线
|
|
|
painter.drawLine(QPointF(x, m_plotArea.bottom() + 0.5),
|
|
|
QPointF(x, m_plotArea.bottom() + 8.5));
|
|
|
|
|
|
// 标签
|
|
|
painter.setPen(Qt::black);
|
|
|
const bool preferInt = (xMain >= 1.0);
|
|
|
const QString label = formatTickLabel(vx, preferInt, false);
|
|
|
const QRect tr = painter.fontMetrics().boundingRect(label);
|
|
|
painter.drawText((int)(x - tr.width()/2),
|
|
|
m_plotArea.bottom() + 20,
|
|
|
label);
|
|
|
painter.setPen(QPen(majorGridColor, 1));
|
|
|
}
|
|
|
painter.restore();
|
|
|
|
|
|
// Y轴主刻度与标签
|
|
|
painter.save();
|
|
|
painter.setPen(QPen(majorGridColor, 1));
|
|
|
for (i = 0; i < yt.majors.size(); ++i) {
|
|
|
double vy = yt.majors[i];
|
|
|
double y = m_plotArea.bottom() - (vy - m_minY) * sy + 0.5;
|
|
|
|
|
|
// 刻度线(仅主刻度)
|
|
|
painter.drawLine(QPointF(m_plotArea.left() - 8.5, y),
|
|
|
QPointF(m_plotArea.left() + 0.5, y));
|
|
|
|
|
|
// 标签
|
|
|
painter.setPen(Qt::black);
|
|
|
const QString label = formatTickLabel(vy, false, isPressureChart);
|
|
|
const QRect tr = painter.fontMetrics().boundingRect(label);
|
|
|
painter.drawText(m_plotArea.left() - tr.width() - 10,
|
|
|
(int)(y + tr.height()/2),
|
|
|
label);
|
|
|
painter.setPen(QPen(majorGridColor, 1));
|
|
|
}
|
|
|
painter.restore();
|
|
|
}
|
|
|
|
|
|
void nmWxPressFlowChartWidget::drawData(QPainter& painter)
|
|
|
{
|
|
|
if (m_data.size() < 1) return;
|
|
|
|
|
|
// 根据图表类型选择绘制方法
|
|
|
if (m_chartType == CHART_TYPE_STEP) {
|
|
|
drawStepData(painter);
|
|
|
} else {
|
|
|
drawLineData(painter);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void nmWxPressFlowChartWidget::drawLineData(QPainter& painter)
|
|
|
{
|
|
|
if (m_data.size() < 2) return;
|
|
|
|
|
|
// 绘制连接线
|
|
|
painter.setPen(QPen(m_lineColor, 2));
|
|
|
for (int i = 0; i < m_data.size() - 1; ++i) {
|
|
|
QPointF p1 = dataToScreen(m_data[i]);
|
|
|
QPointF p2 = dataToScreen(m_data[i + 1]);
|
|
|
painter.drawLine(p1, p2);
|
|
|
}
|
|
|
|
|
|
// 绘制+号标记
|
|
|
QPen finePen(m_lineColor.darker(110), 1); // 1像素宽度
|
|
|
painter.setPen(finePen);
|
|
|
|
|
|
int markerSize = 2;
|
|
|
for (int i = 0; i < m_data.size(); ++i) {
|
|
|
QPointF p = dataToScreen(m_data[i]);
|
|
|
|
|
|
// 使用浮点坐标确保精确绘制
|
|
|
painter.drawLine(QPointF(p.x() - markerSize, p.y()),
|
|
|
QPointF(p.x() + markerSize, p.y()));
|
|
|
painter.drawLine(QPointF(p.x(), p.y() - markerSize),
|
|
|
QPointF(p.x(), p.y() + markerSize));
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void nmWxPressFlowChartWidget::drawStepData(QPainter& painter)
|
|
|
{
|
|
|
if (m_data.size() < 2) return;
|
|
|
|
|
|
// 设置线条样式
|
|
|
painter.setPen(QPen(m_lineColor, 2));
|
|
|
painter.setRenderHint(QPainter::Antialiasing, true);
|
|
|
|
|
|
// 只绘制阶梯状线条,不绘制任何标记点
|
|
|
for (int i = 0; i < m_data.size() - 1; ++i) {
|
|
|
QPointF p1 = dataToScreen(m_data[i]);
|
|
|
QPointF p2 = dataToScreen(m_data[i + 1]);
|
|
|
painter.drawLine(p1, p2);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void nmWxPressFlowChartWidget::drawLabels(QPainter& painter)
|
|
|
{
|
|
|
QFont titleFont("Arial", 12, QFont::Bold);
|
|
|
QFont labelFont("Arial", 10);
|
|
|
|
|
|
// 绘制标题
|
|
|
if (!m_title.isEmpty()) {
|
|
|
painter.save();
|
|
|
painter.setFont(titleFont);
|
|
|
painter.setPen(Qt::black);
|
|
|
QRect titleRect = painter.fontMetrics().boundingRect(m_title);
|
|
|
int titleX = (width() - titleRect.width()) / 2;
|
|
|
painter.drawText(titleX, 20, m_title);
|
|
|
painter.restore();
|
|
|
}
|
|
|
|
|
|
// X轴标签
|
|
|
if (!m_xLabel.isEmpty()) {
|
|
|
painter.save();
|
|
|
painter.setFont(labelFont);
|
|
|
painter.setPen(Qt::black);
|
|
|
QString xLabelText = m_xLabel;
|
|
|
if (!m_xUnit.isEmpty()) {
|
|
|
xLabelText += " (" + m_xUnit + ")";
|
|
|
}
|
|
|
QRect xLabelRect = painter.fontMetrics().boundingRect(xLabelText);
|
|
|
int xLabelX = (width() - xLabelRect.width()) / 2;
|
|
|
painter.drawText(xLabelX, height() - 10, xLabelText);
|
|
|
painter.restore();
|
|
|
}
|
|
|
|
|
|
// Y轴标签(旋转90度)
|
|
|
if (!m_yLabel.isEmpty()) {
|
|
|
painter.save();
|
|
|
painter.setFont(labelFont);
|
|
|
painter.setPen(Qt::black);
|
|
|
QString yLabelText = m_yLabel;
|
|
|
if (!m_yUnit.isEmpty()) {
|
|
|
yLabelText += " (" + m_yUnit + ")";
|
|
|
}
|
|
|
|
|
|
painter.translate(15, height() / 2);
|
|
|
painter.rotate(-90);
|
|
|
QRect yLabelRect = painter.fontMetrics().boundingRect(yLabelText);
|
|
|
painter.drawText(-yLabelRect.width()/2, 0, yLabelText);
|
|
|
painter.restore();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void nmWxPressFlowChartWidget::drawSelectedFlowSegments(QPainter& painter)
|
|
|
{
|
|
|
const QVector<FlowSegmentData>& segments = m_perforationData.getFlowSegments();
|
|
|
|
|
|
// 1. 首先绘制选中流动段的半透明背景
|
|
|
for(int i = 0; i < segments.size(); ++i) {
|
|
|
const FlowSegmentData& segment = segments[i];
|
|
|
|
|
|
if(segment.isSelected) {
|
|
|
double startTime = segment.segmentStart.getValue().toDouble();
|
|
|
double endTime = segment.segmentEnd.getValue().toDouble();
|
|
|
|
|
|
// 如果是外部拖动状态,并且是被拖动的边界,使用拖动中的时间
|
|
|
if(m_externalDragActive && i == m_externalDragBoundaryIndex) {
|
|
|
startTime = m_externalDragBoundaryTime;
|
|
|
}
|
|
|
|
|
|
// 检查下一个段是否受到拖动影响
|
|
|
if(m_externalDragActive && i + 1 < segments.size() && (i + 1) == m_externalDragBoundaryIndex) {
|
|
|
endTime = m_externalDragBoundaryTime;
|
|
|
}
|
|
|
|
|
|
// 转换为屏幕坐标
|
|
|
int leftX = m_plotArea.left() + (startTime - m_minX) / (m_maxX - m_minX) * m_plotArea.width();
|
|
|
int rightX = m_plotArea.left() + (endTime - m_minX) / (m_maxX - m_minX) * m_plotArea.width();
|
|
|
|
|
|
// 绘制半透明矩形背景
|
|
|
QColor fillColor(255, 180, 180, 140);
|
|
|
painter.fillRect(QRect(leftX, m_plotArea.top(),
|
|
|
rightX - leftX, m_plotArea.height()), fillColor);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 2. 绘制所有流动段的边界线(蓝色)
|
|
|
painter.setPen(QPen(QColor(0, 0, 255), 2)); // 蓝色,2像素宽
|
|
|
|
|
|
for(int i = 0; i < segments.size(); ++i) {
|
|
|
const FlowSegmentData& segment = segments[i];
|
|
|
|
|
|
double startTime = segment.segmentStart.getValue().toDouble();
|
|
|
double endTime = segment.segmentEnd.getValue().toDouble();
|
|
|
|
|
|
// 如果是外部拖动状态,并且是被拖动的边界,使用拖动中的时间
|
|
|
if(m_externalDragActive && i == m_externalDragBoundaryIndex) {
|
|
|
startTime = m_externalDragBoundaryTime;
|
|
|
}
|
|
|
|
|
|
// 检查下一个段是否受到拖动影响
|
|
|
if(m_externalDragActive && i + 1 < segments.size() && (i + 1) == m_externalDragBoundaryIndex) {
|
|
|
endTime = m_externalDragBoundaryTime;
|
|
|
}
|
|
|
|
|
|
// 检查是否处于合并状态
|
|
|
// 转换为屏幕坐标
|
|
|
int leftX = m_plotArea.left() + (startTime - m_minX) / (m_maxX - m_minX) * m_plotArea.width();
|
|
|
// 绘制起点边界线
|
|
|
painter.drawLine(leftX, m_plotArea.top(), leftX, m_plotArea.bottom());
|
|
|
// 只有最后一个段画终点
|
|
|
if (i == segments.size() - 1) {
|
|
|
int rightX = m_plotArea.left() + (endTime - m_minX) / (m_maxX - m_minX) * m_plotArea.width();
|
|
|
painter.drawLine(rightX, m_plotArea.top(), rightX, m_plotArea.bottom());
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// 3. 如果正在进行外部拖动,绘制拖动中的边界线
|
|
|
if(m_externalDragActive && m_externalDragBoundaryIndex >= 0) {
|
|
|
int dragX = m_plotArea.left() + (m_externalDragBoundaryTime - m_minX) / (m_maxX - m_minX) * m_plotArea.width();
|
|
|
painter.drawLine(dragX, m_plotArea.top(), dragX, m_plotArea.bottom());
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void nmWxPressFlowChartWidget::calculateDataRange()
|
|
|
{
|
|
|
if (m_data.isEmpty()) {
|
|
|
m_minX = 0; m_maxX = 1;
|
|
|
m_minY = 0; m_maxY = 1;
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
m_minX = m_maxX = m_data[0].x();
|
|
|
m_minY = m_maxY = m_data[0].y();
|
|
|
|
|
|
foreach (const QPointF& point, m_data) {
|
|
|
m_minX = qMin(m_minX, point.x());
|
|
|
m_maxX = qMax(m_maxX, point.x());
|
|
|
m_minY = qMin(m_minY, point.y());
|
|
|
m_maxY = qMax(m_maxY, point.y());
|
|
|
}
|
|
|
|
|
|
// 添加一些边距
|
|
|
double xMargin = (m_maxX - m_minX) * 0.02;
|
|
|
double yMargin = (m_maxY - m_minY) * 0.1;
|
|
|
|
|
|
m_minX -= xMargin;
|
|
|
m_maxX += xMargin;
|
|
|
m_minY -= yMargin;
|
|
|
m_maxY += yMargin;
|
|
|
|
|
|
// 确保范围不为零
|
|
|
if (m_maxX - m_minX < 1e-6) {
|
|
|
m_minX -= 0.5;
|
|
|
m_maxX += 0.5;
|
|
|
}
|
|
|
if (m_maxY - m_minY < 1e-6) {
|
|
|
m_minY -= 0.5;
|
|
|
m_maxY += 0.5;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
QPointF nmWxPressFlowChartWidget::dataToScreen(const QPointF& dataPoint)
|
|
|
{
|
|
|
double x = m_plotArea.left() +
|
|
|
(dataPoint.x() - m_minX) / (m_maxX - m_minX) * m_plotArea.width();
|
|
|
double y = m_plotArea.bottom() -
|
|
|
(dataPoint.y() - m_minY) / (m_maxY - m_minY) * m_plotArea.height();
|
|
|
return QPointF(x, y);
|
|
|
}
|
|
|
|
|
|
QPointF nmWxPressFlowChartWidget::screenToData(const QPointF& screenPoint)
|
|
|
{
|
|
|
double x = m_minX + (screenPoint.x() - m_plotArea.left()) / m_plotArea.width() * (m_maxX - m_minX);
|
|
|
double y = m_minY + (m_plotArea.bottom() - screenPoint.y()) / m_plotArea.height() * (m_maxY - m_minY);
|
|
|
return QPointF(x, y);
|
|
|
}
|
|
|
|
|
|
void nmWxPressFlowChartWidget::mouseMoveEvent(QMouseEvent *event)
|
|
|
{
|
|
|
if(m_isDraggingBoundary && m_draggingBoundaryIndex >= 0) {
|
|
|
QPointF dataPoint = screenToData(event->pos());
|
|
|
double newTime = dataPoint.x();
|
|
|
|
|
|
// 限制拖动范围
|
|
|
const QVector<FlowSegmentData>& segments = m_perforationData.getFlowSegments();
|
|
|
double minTime = (m_draggingBoundaryIndex > 1) ?
|
|
|
segments[m_draggingBoundaryIndex - 1].segmentStart.getValue().toDouble() + 0.001 : m_minX;
|
|
|
double maxTime = (m_draggingBoundaryIndex < segments.size() - 1) ?
|
|
|
segments[m_draggingBoundaryIndex + 1].segmentStart.getValue().toDouble() - 0.001 : m_maxX;
|
|
|
|
|
|
newTime = qMax(minTime, qMin(maxTime, newTime));
|
|
|
|
|
|
// 保存原始时间,用于合并检查
|
|
|
double originalNewTime = newTime;
|
|
|
newTime = snapToNearestFlowStep(newTime);
|
|
|
|
|
|
if(qAbs(newTime - m_draggingBoundaryTime) > 1e-6) {
|
|
|
m_draggingBoundaryTime = newTime;
|
|
|
|
|
|
// 实时更新本地数据副本
|
|
|
updateLocalBoundaryPosition(m_draggingBoundaryIndex, newTime);
|
|
|
|
|
|
// 在snap模式下,检查是否应该立即合并
|
|
|
if(m_snapToRateChanges && shouldMergeSegments(originalNewTime, m_draggingBoundaryIndex)) {
|
|
|
// 立即触发合并
|
|
|
emit segmentsMerged(m_draggingBoundaryIndex);
|
|
|
m_isDraggingBoundary = false;
|
|
|
m_draggingBoundaryIndex = -1;
|
|
|
m_draggingBoundaryTime = 0.0;
|
|
|
setCursor(Qt::ArrowCursor);
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// 发送信号同步到另一个图表
|
|
|
emit boundaryMoved(m_draggingBoundaryIndex, newTime);
|
|
|
|
|
|
update();
|
|
|
}
|
|
|
} else if(m_plotArea.contains(event->pos())) {
|
|
|
int boundaryIndex = findBoundaryAtPosition(event->pos(), 8.0);
|
|
|
if(boundaryIndex >= 0 && isBoundaryDraggable(boundaryIndex)) {
|
|
|
setCursor(Qt::SizeHorCursor);
|
|
|
} else {
|
|
|
setCursor(Qt::ArrowCursor);
|
|
|
}
|
|
|
} else {
|
|
|
setCursor(Qt::ArrowCursor);
|
|
|
}
|
|
|
|
|
|
QWidget::mouseMoveEvent(event);
|
|
|
}
|
|
|
|
|
|
void nmWxPressFlowChartWidget::mouseReleaseEvent(QMouseEvent *event)
|
|
|
{
|
|
|
if(m_isDraggingBoundary && event->button() == Qt::LeftButton) {
|
|
|
m_isDraggingBoundary = false;
|
|
|
setCursor(Qt::ArrowCursor);
|
|
|
|
|
|
// 检查是否需要合并段(使用当前数据)
|
|
|
if(shouldMergeSegments(m_draggingBoundaryTime, m_draggingBoundaryIndex)) {
|
|
|
emit segmentsMerged(m_draggingBoundaryIndex);
|
|
|
} else {
|
|
|
// 拖动结束,发送最终更新信号
|
|
|
emit boundaryMovedFinal(m_draggingBoundaryIndex, m_draggingBoundaryTime);
|
|
|
}
|
|
|
|
|
|
m_draggingBoundaryIndex = -1;
|
|
|
m_draggingBoundaryTime = 0.0;
|
|
|
}
|
|
|
|
|
|
QWidget::mouseReleaseEvent(event);
|
|
|
}
|
|
|
|
|
|
void nmWxPressFlowChartWidget::updateLocalBoundaryPosition(int boundaryIndex, double newTime)
|
|
|
{
|
|
|
QVector<FlowSegmentData> segments = m_perforationData.getFlowSegments();
|
|
|
|
|
|
if(boundaryIndex <= 0 || boundaryIndex >= segments.size()) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// 更新当前段的起始时间
|
|
|
segments[boundaryIndex].segmentStart.setValue(QString::number(newTime, 'f', 5));
|
|
|
segments[boundaryIndex].startTime.setValue(QString::number(newTime, 'f', 5));
|
|
|
|
|
|
// 更新前一个段的结束时间
|
|
|
segments[boundaryIndex - 1].segmentEnd.setValue(QString::number(newTime, 'f', 5));
|
|
|
|
|
|
// 更新本地数据副本
|
|
|
m_perforationData.setFlowSegments(segments);
|
|
|
}
|
|
|
|
|
|
int nmWxPressFlowChartWidget::findBoundaryAtPosition(const QPoint& pos, double tolerance) const
|
|
|
{
|
|
|
if(!m_plotArea.contains(pos)) {
|
|
|
return -1;
|
|
|
}
|
|
|
|
|
|
const QVector<FlowSegmentData>& segments = m_perforationData.getFlowSegments();
|
|
|
|
|
|
// 检查每个流动段的边界线
|
|
|
for(int i = 0; i < segments.size(); ++i) {
|
|
|
const FlowSegmentData& segment = segments[i];
|
|
|
|
|
|
// 检查段的起始边界(第一个段的起始边界不可拖动)
|
|
|
if(i > 0) {
|
|
|
double startTime = segment.segmentStart.getValue().toDouble();
|
|
|
int boundaryX = m_plotArea.left() + (startTime - m_minX) / (m_maxX - m_minX) * m_plotArea.width();
|
|
|
|
|
|
if(qAbs(pos.x() - boundaryX) <= tolerance) {
|
|
|
return i; // 返回段索引,表示该段的左边界
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return -1;
|
|
|
}
|
|
|
|
|
|
// 检查分界线是否可拖动
|
|
|
bool nmWxPressFlowChartWidget::isBoundaryDraggable(int boundaryIndex) const
|
|
|
{
|
|
|
const QVector<FlowSegmentData>& segments = m_perforationData.getFlowSegments();
|
|
|
|
|
|
// 第一个段的起始边界和最后一个段的结束边界不可拖动
|
|
|
return boundaryIndex > 0 && boundaryIndex < segments.size();
|
|
|
}
|
|
|
|
|
|
// 将时间吸附到最近的流量阶梯边界
|
|
|
double nmWxPressFlowChartWidget::snapToNearestFlowStep(double time) const
|
|
|
{
|
|
|
if(!m_snapToRateChanges) {
|
|
|
return time; // 未勾选对齐,直接返回
|
|
|
}
|
|
|
|
|
|
return m_currentWell->findSmartFlowStepBoundary(time);
|
|
|
}
|
|
|
|
|
|
// 检查是否应该合并段
|
|
|
bool nmWxPressFlowChartWidget::shouldMergeSegments(double newBoundaryTime, int boundaryIndex) const
|
|
|
{
|
|
|
const QVector<FlowSegmentData>& segments = m_perforationData.getFlowSegments();
|
|
|
|
|
|
if(boundaryIndex <= 0 || boundaryIndex >= segments.size()) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
double mergeThreshold = 1.0; // 1小时的合并阈值
|
|
|
|
|
|
// 检查与左侧分界线的距离
|
|
|
if(boundaryIndex > 1) {
|
|
|
double leftBoundaryTime = segments[boundaryIndex - 1].segmentStart.getValue().toDouble();
|
|
|
double distance = qAbs(newBoundaryTime - leftBoundaryTime);
|
|
|
if(distance < mergeThreshold) {
|
|
|
return true;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 检查与右侧分界线的距离
|
|
|
if(boundaryIndex < segments.size() - 1) {
|
|
|
double rightBoundaryTime = segments[boundaryIndex + 1].segmentStart.getValue().toDouble();
|
|
|
double distance = qAbs(newBoundaryTime - rightBoundaryTime);
|
|
|
if(distance < mergeThreshold) {
|
|
|
return true;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
void nmWxPressFlowChartWidget::mousePressEvent(QMouseEvent *event)
|
|
|
{
|
|
|
if (event->button() == Qt::LeftButton && m_plotArea.contains(event->pos())) {
|
|
|
// 首先检查是否点击在分界线附近
|
|
|
int boundaryIndex = findBoundaryAtPosition(event->pos());
|
|
|
|
|
|
if(boundaryIndex >= 0 && isBoundaryDraggable(boundaryIndex)) {
|
|
|
// 开始拖动分界线
|
|
|
m_isDraggingBoundary = true;
|
|
|
m_draggingBoundaryIndex = boundaryIndex;
|
|
|
|
|
|
// 获取当前分界线时间
|
|
|
const QVector<FlowSegmentData>& segments = m_perforationData.getFlowSegments();
|
|
|
m_draggingBoundaryTime = segments[boundaryIndex].segmentStart.getValue().toDouble();
|
|
|
|
|
|
m_lastMousePos = event->pos();
|
|
|
setCursor(Qt::SizeHorCursor);
|
|
|
update();
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// 如果不是拖动分界线,执行原有逻辑
|
|
|
QPointF dataPoint = screenToData(event->pos());
|
|
|
double clickTime = dataPoint.x();
|
|
|
|
|
|
emit chartPointClicked(clickTime);
|
|
|
|
|
|
//int segmentIndex = nmDataAnalyzeManager::getCurrentInstance()->findFlowSegmentByTime(clickTime);
|
|
|
int segmentIndex = m_perforationData.findFlowSegmentByTime(clickTime);
|
|
|
if(segmentIndex >= 0) {
|
|
|
emit flowSegmentClicked(segmentIndex);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
QWidget::mousePressEvent(event);
|
|
|
}
|
|
|
|
|
|
void nmWxPressFlowChartWidget::wheelEvent(QWheelEvent *event)
|
|
|
{
|
|
|
// 实现缩放功能
|
|
|
Q_UNUSED(event);
|
|
|
}
|
|
|
|
|
|
void nmWxPressFlowChartWidget::setFlowSegmentData(const nmDataPerforation& perforationData)
|
|
|
{
|
|
|
m_perforationData = perforationData;
|
|
|
update();
|
|
|
}
|
|
|
|
|
|
void nmWxPressFlowChartWidget::setSnapToRateChanges(bool snap) {
|
|
|
m_snapToRateChanges = snap;
|
|
|
}
|
|
|
|
|
|
void nmWxPressFlowChartWidget::setExternalDragState(bool isDragging, int boundaryIndex, double boundaryTime)
|
|
|
{
|
|
|
m_externalDragActive = isDragging;
|
|
|
m_externalDragBoundaryIndex = boundaryIndex;
|
|
|
m_externalDragBoundaryTime = boundaryTime;
|
|
|
|
|
|
// 如果是外部拖动,也要同步更新本地数据副本
|
|
|
if(isDragging && boundaryIndex >= 0) {
|
|
|
updateLocalBoundaryPosition(boundaryIndex, boundaryTime);
|
|
|
}
|
|
|
|
|
|
// 立即重绘以显示外部拖动状态
|
|
|
update();
|
|
|
}
|
|
|
|
|
|
void nmWxPressFlowChartWidget::clearExternalDragState()
|
|
|
{
|
|
|
m_externalDragActive = false;
|
|
|
m_externalDragBoundaryIndex = -1;
|
|
|
m_externalDragBoundaryTime = 0.0;
|
|
|
update();
|
|
|
}
|