#pragma once
#include <QtWidgets>
class ContourPlotWidget : public QWidget {
public:
explicit ContourPlotWidget(QWidget* parent = nullptr) :
QWidget(parent),
m_zValues(nullptr),
m_nx(0),
m_ny(0),
m_nContours(10),
m_minZ(0.0),
m_maxZ(1.0),
m_gridPen(QPen(Qt::gray, 1)),
m_contourPens()
{
for (int i = 0; i < 10; ++i) {
m_contourPens << QPen(QColor::fromHsvF(i / 10.0, 1.0, 1.0), 2);
}
}
~ContourPlotWidget() override {
if (m_zValues) {
delete[] m_zValues;
}
}
void setZValues(int nx, int ny, const double* zValues) {
if (m_zValues) {
delete[] m_zValues;
}
m_zValues = new double[nx * ny];
memcpy(m_zValues, zValues, nx * ny * sizeof(double));
m_nx = nx;
m_ny = ny;
update();
}
void setNumContours(int nContours) {
m_nContours = nContours;
update();
}
void setRange(double minZ, double maxZ) {
m_minZ = minZ;
m_maxZ = maxZ;
update();
}
void setGridPen(const QPen& pen) {
m_gridPen = pen;
update();
}
protected:
void paintEvent(QPaintEvent* event) override {
Q_UNUSED(event);
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing, true);
// 计算坐标系的边界和范围
QRect clientRect = rect().adjusted(5, 5, -5, -5);
double xmin = 0.0;
double xmax = 1.0;
double ymin = 0.0;
double ymax = 1.0;
if (m_zValues) {
xmin = 0.0;
xmax = m_nx - 1;
ymin = 0.0;
ymax = m_ny - 1;
}
double xrange = xmax - xmin;
double yrange = ymax - ymin;
// 绘制网格线
painter.setPen(m_gridPen);
for (int i = 0; i < m_nx; ++i) {
double x = mapX(i, xmin, xmax, clientRect.left(), clientRect.right());
painter.drawLine(QPointF(x, clientRect.top()), QPointF(x, clientRect.bottom()));
}
for (int j = 0; j < m_ny; ++j) {
double y = mapY(j, ymin, ymax, clientRect.top(), clientRect.bottom());
painter.drawLine(QPointF(clientRect.left(), y), QPointF(clientRect.right(), y));
}
// 绘制等高线
if (m_zValues) {
QVector<QVector<QPointF>> contours = calculateContours();
for (int i = 0; i < contours.size(); ++i) {
painter.setPen(m_contourPens.at(i % m_contourPens.size()));
for (int j = 0; j < contours.at(i).size() - 1; ++j) {
double x1 = mapX(contours.at(i).at(j).x(), xmin, xmax, clientRect.left(), clientRect.right());
double y1 = mapY(contours.at(i).at(j).y(), ymin, ymax, clientRect.top(), clientRect.bottom());
double x2 = mapX(contours.at(i).at(j + 1).x(), xmin, xmax, clientRect.left(), clientRect.right());
double y2 = mapY(contours.at(i).at(j + 1).y(), ymin, ymax, clientRect.top(), clientRect.bottom());
painter.drawLine(QPointF(x1, y1), QPointF(x2, y2));
}
}
}
}
private:
QVector<QVector<QPointF>> calculateContours() const {
QVector<double> levels(m_nContours);
double step = (m_maxZ - m_minZ) / (m_nContours - 1);
for (int i = 0; i < m_nContours; ++i) {
levels[i] = m_minZ + i * step;
}
QVector<QVector<QPointF>> contours(m_nContours);
for (int levelIndex = 0; levelIndex < m_nContours; ++levelIndex) {
QVector<QPointF>& contour = contours[levelIndex];
for (int i = 0; i < m_nx - 1; ++i) {
for (int j = 0; j < m_ny - 1; ++j) {
// 计算四个顶点的值和中心点的值
double v[5] = {
m_zValues[i + j * m_nx],
m_zValues[(i + 1) + j * m_nx],
m_zValues[i + (j + 1) * m_nx],
m_zValues[(i + 1) + (j + 1) * m_nx],
(m_zValues[i + j * m_nx] + m_zValues[(i + 1) + j * m_nx] +
m_zValues[i + (j + 1) * m_nx] + m_zValues[(i + 1) + (j + 1) * m_nx]) / 4.0
};
// 判断是否跨越等高线
int mask = ((v[0] < levels[levelIndex]) << 0) |
((v[1] < levels[levelIndex]) << 1) |
((v[2] < levels[levelIndex]) << 2) |
((v[3] < levels[levelIndex]) << 3);
double x[4], y[4];
x[0] = i;
y[0] = j;
x[1] = i + 1;
y[1] = j;
x[2] = i;
y[2] = j + 1;
x[3] = i + 1;
y[3] = j + 1;
switch (mask) {
case 1: // 0001 -> 1000
interpolate(levels[levelIndex], v[0], v[1], x[0], y[0], x[1], y[1], contour);
break;
case 2: // 0010 -> 0100
interpolate(levels[levelIndex], v[1], v[3], x[1], y[1], x[3], y[3], contour);
break;
case 3: // 0011 -> 1100
interpolate(levels[levelIndex], v[0], v[3], x[0], y[0], x[3], y[3], contour);
break;
case 4: // 0100 -> 0010
interpolate(levels[levelIndex], v[2], v[3], x[2], y[2], x[3], y[3], contour);
break;
case 5: // 0101 -> 1010
interpolate(levels[levelIndex], v[0], v[2], x[0], y[0], x[2], y[2], contour);
interpolate(levels[levelIndex], v[1], v[3], x[1], y[1], x[3], y[3], contour);
break;
case 6: // 0110 -> 0101
interpolate(levels[levelIndex], v[1], v[2], x[1], y[1], x[2], y[2], contour);
interpolate(levels[levelIndex], v[0], v[3], x[0], y[0], x[3, y[3], contour);
break;
case 7: // 0111 -> 1000
interpolate(levels[levelIndex], v[0], v[1], x[0], y[0], x[1], y[1], contour);
break;
case 8: // 1000 -> 0001
interpolate(levels[levelIndex], v[0], v[1], x[0], y[0], x[1], y[1], contour);
break;
case 9: // 1001 -> 0010
interpolate(levels[levelIndex], v[1], v[3], x[1], y[1], x[3], y[3], contour);
interpolate(levels[levelIndex], v[0], v[2], x[0], y[0], x[2], y[2], contour);
break;
case 10: // 1010 -> 0101
interpolate(levels[levelIndex], v[0], v[2], x[0], y[0], x[2], y[2], contour);
interpolate(levels[levelIndex], v[1], v[3], x[1], y[1], x[3], y[3], contour);
break;
case 11: // 1011 -> 1100
interpolate(levels[levelIndex], v[0], v[3], x[0], y[0], x[3], y[3], contour);
break;
case 12: // 1100 -> 0011
interpolate(levels[levelIndex], v[0], v[3], x[0], y[0], x[3], y[3], contour);
break;
case 13: // 1101 -> 0100
interpolate(levels[levelIndex], v[2], v[3], x[2], y[2], x[3], y[3], contour);
interpolate(levels[levelIndex], v[0], v[1], x[0], y[0], x[1], y[1], contour);
break;
case 14: // 1110 -> 1001
interpolate(levels[levelIndex], v[1], v[2], x[1], y[1], x[2], y[2], contour);
interpolate(levels[levelIndex], v[0], v[2], x[0], y[0], x[2], y[2], contour);
break;
}
}
}
}
return contours;
}
void interpolate(double level, double z0, double z1, double x0, double y0, double x1, double y1, QVector<QPointF>& contour) const {
if (qFuzzyIsNull(z0 - z1)) {
return;
}
double ratio = (level - z0) / (z1 - z0);
double x = x0 + ratio * (x1 - x0);
double y = y0 + ratio * (y1 - y0);
contour.append(QPointF(x, y));
}
double mapX(double x, double xmin, double xmax, double left, double right) const {
return left + (x - xmin) / (xmax - xmin) * (right - left);
}
double mapY(double y, double ymin, double ymax, double top, double bottom) const {
return bottom - (y - ymin) / (ymax - ymin) * (bottom - top);
}
double* m_zValues;
int m_nx;
int m_ny;
int m_nContours;
double m_minZ;
double m_maxZ;
QPen m_gridPen;
QVector<QPen> m_contourPens;
};