QT实现雷达图

发布于:2025-07-21 ⋅ 阅读:(16) ⋅ 点赞:(0)

一、Qt中QPainter绘制雷达图

简单雷达图实例

#include <QApplication>
#include <QWidget>
#include <QPainter>
#include <QPen>
#include <QFont>
#include <QPointF>
#include <QPolygonF>
#include <cmath>
#include <qmath.h>
#include <vector>

class RadarWidgetEx : public QWidget {
public:
    RadarWidgetEx(QWidget *parent = nullptr) : QWidget(parent) {
        // 设置背景色
        setStyleSheet("background-color: #2c3e50;");
        resize(600, 600);
    }

    void setData(const std::vector<double>& values, const std::vector<QString>& labels) {
        dataValues = values;
        axisLabels = labels;
        update();  // 触发重绘
    }

protected:
    void paintEvent(QPaintEvent *event) override {
        Q_UNUSED(event);
        QPainter painter(this);
        painter.setRenderHint(QPainter::Antialiasing, true);

        // 中心点和半径
        QPointF center(width() / 2.0, height() / 2.0);
        double radius = std::min(width(), height()) * 0.4;

        // 绘制网格和轴线
        drawGrid(painter, center, radius);

        // 绘制数据多边形
        drawDataPolygon(painter, center, radius);

        // 绘制标签
        drawLabels(painter, center, radius);
    }

private:
    void drawGrid(QPainter& painter, const QPointF& center, double radius) {
        painter.setPen(QPen(QColor(100, 100, 100, 150), 2, Qt::DotLine));

        // 绘制同心圆网格
        for (int level = 1; level <= 10; ++level) {
            double r = radius * level / 10.0;
            painter.drawEllipse(center, r, r);
        }

        // 绘制轴线
        int axisCount = axisLabels.size();
        painter.setPen(QPen(Qt::white, 1));
        for (int i = 0; i < axisCount; ++i) {
            double angle = 2 * M_PI * i / axisCount - M_PI / 2;  // 从顶部开始
            QPointF endPoint(
                center.x() + radius * std::cos(angle),
                center.y() + radius * std::sin(angle)
            );
            painter.drawLine(center, endPoint);
        }
    }

    void drawDataPolygon(QPainter& painter, const QPointF& center, double radius) {
        if (dataValues.empty()) return;

        int axisCount = axisLabels.size();
        QPolygonF polygon;

        for (int i = 0; i < axisCount; ++i) {
            double value = dataValues[i];
            double angle = 2 * M_PI * i / axisCount - M_PI / 2;

            // 计算坐标点 (归一化到0~1范围)
            QPointF point(center.x() + radius * value * std::cos(angle), center.y() + radius * value * std::sin(angle));
            polygon << point;
        }

        // 闭合多边形
        polygon << polygon.first();

        // 设置绘制样式
        QColor fillColor(75, 181, 230, 150);
        QColor borderColor(30, 120, 180);

        painter.setPen(QPen(borderColor, 2));
        painter.setBrush(fillColor);
        painter.drawPolygon(polygon);

        // 绘制数据点
        painter.setBrush(Qt::white);
        for (const QPointF& point : polygon) {
            painter.drawEllipse(point, 5, 5);
        }
    }

    void drawLabels(QPainter& painter, const QPointF& center, double radius) {
        painter.setPen(Qt::white);
        QFont font = painter.font();
        font.setPointSize(10);
        painter.setFont(font);

        int axisCount = axisLabels.size();
        double labelRadius = radius * 1.1;  // 标签位置稍远于轴线

        for (int i = 0; i < axisCount; ++i) {
            double angle = 2 * M_PI * i / axisCount - M_PI / 2;
            QPointF labelPos(
                center.x() + labelRadius * std::cos(angle),
                center.y() + labelRadius * std::sin(angle)
            );

            // 文本居中处理
            QRectF textRect(labelPos.x() - 50, labelPos.y() - 15, 100, 30);
            painter.drawText(textRect, Qt::AlignCenter, axisLabels[i]);
        }
    }

private:
    std::vector<double> dataValues;
    std::vector<QString> axisLabels;
};

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);

    RadarWidgetEx radar;
    radar.setWindowTitle("Qt Radar Chart Example");

    // 设置示例数据
    std::vector<double> values = {0.8, 0.6, 0.9, 0.7, 0.5, 0.85};
    std::vector<QString> labels = {"speed", "power", "jixiao", "naili", "mingjie", "IQ"};

    radar.setData(values, labels);
    radar.show();

    return app.exec();
}

运行结果: 

代码说明:

  1. RadarWidget 类

    • 继承自 QWidget,负责绘制雷达图

    • setData() 方法用于设置数据和标签

    • 重写 paintEvent() 实现绘图逻辑

  2. 主要绘制部分

    • drawGrid():绘制同心圆网格和轴线

    • drawDataPolygon():绘制数据多边形和顶点

    • drawLabels():在轴线末端绘制标签

  3. 坐标计算

    • 使用极坐标转换:x = center.x + radius * cos(angle)

    • 角度从顶部开始(-π/2),按等分角度分布

    • 数据值归一化到 0~1 范围

  4. 视觉样式

    • 半透明填充色

    • 白色数据点

    • 虚线网格

    • 深色背景增强对比度

多组数据雷达图实例

多组数据
修改数据结构以支持多组数据,使用不同颜色绘制多个多边形。

#include <QApplication>
#include <QWidget>
#include <QPainter>
#include <QPen>
#include <QFont>
#include <QPointF>
#include <QPolygonF>
#include <QColor>
#include <cmath>
#include <qmath.h>
#include <vector>
#include <map>

class RadarWidget : public QWidget {
public:
    RadarWidget(QWidget *parent = nullptr) : QWidget(parent) {
        setStyleSheet("background-color: #2c3e50;");
        resize(600, 600);
    }

    // 添加一组数据
    void addData(const QString& name, const std::vector<double>& values, const QColor& color) {
        dataSets[name] = {values, color};
        update();
    }

    void setAxisLabels(const std::vector<QString>& labels) {
        axisLabels = labels;
        update();
    }

protected:
    void paintEvent(QPaintEvent *event) override {
        Q_UNUSED(event);
        QPainter painter(this);
        painter.setRenderHint(QPainter::Antialiasing, true);

        QPointF center(width() / 2.0, height() / 2.0);
        double radius = std::min(width(), height()) * 0.4;

        drawGrid(painter, center, radius);
        drawDataPolygons(painter, center, radius);
        drawLabels(painter, center, radius);
    }

private:
    struct DataSet {
        std::vector<double> values;
        QColor color;
    };

    std::map<QString, DataSet> dataSets;
    std::vector<QString> axisLabels;

    void drawGrid(QPainter& painter, const QPointF& center, double radius) {
        painter.setPen(QPen(QColor(100, 100, 100, 150), 1, Qt::DotLine));

        // 绘制5层同心圆网格
        for (int level = 1; level <= 5; ++level) {
            double r = radius * level / 5.0;
            painter.drawEllipse(center, r, r);
        }

        // 绘制轴线
        int axisCount = axisLabels.size();
        painter.setPen(QPen(Qt::white, 1));
        for (int i = 0; i < axisCount; ++i) {
            double angle = 2 * M_PI * i / axisCount - M_PI / 2;
            QPointF endPoint(
                center.x() + radius * std::cos(angle),
                center.y() + radius * std::sin(angle)
            );
            painter.drawLine(center, endPoint);
        }
    }

    void drawDataPolygons(QPainter& painter, const QPointF& center, double radius) {
        if (dataSets.empty()) return;

        int axisCount = axisLabels.size();

        // 为每组数据绘制多边形
        for (const auto& pair : dataSets) {
            const QString& name = pair.first;     // key
            const DataSet& dataset = pair.second; // value
            // 使用 name 和 dataset
            QPolygonF polygon;
            for (int i = 0; i < axisCount; ++i) {
                double value = dataset.values[i];
                double angle = 2 * M_PI * i / axisCount - M_PI / 2;

                QPointF point(
                    center.x() + radius * value * std::cos(angle),
                    center.y() + radius * value * std::sin(angle));
                polygon << point;
            }

            polygon << polygon.first();  // 闭合多边形

            // 设置颜色,填充半透明,边框不透明
            QColor fillColor = dataset.color;
            fillColor.setAlpha(100);
            QColor borderColor = dataset.color;
            borderColor.setAlpha(220);

            painter.setPen(QPen(borderColor, 2));
            painter.setBrush(fillColor);
            painter.drawPolygon(polygon);

            // 绘制数据点
            painter.setBrush(borderColor);
            for (const QPointF& point : polygon) {
                painter.drawEllipse(point, 4, 4);
            }
        }
    }

    void drawLabels(QPainter& painter, const QPointF& center, double radius) {
        painter.setPen(Qt::white);
        QFont font = painter.font();
        font.setPointSize(10);
        painter.setFont(font);

        int axisCount = axisLabels.size();
        double labelRadius = radius * 1.1;

        for (int i = 0; i < axisCount; ++i) {
            double angle = 2 * M_PI * i / axisCount - M_PI / 2;
            QPointF labelPos(
                center.x() + labelRadius * std::cos(angle),
                center.y() + labelRadius * std::sin(angle));

            QRectF textRect(labelPos.x() - 50, labelPos.y() - 15, 100, 30);
            painter.drawText(textRect, Qt::AlignCenter, axisLabels[i]);
        }

        // 绘制图例
        if (!dataSets.empty()) {
            int legendX = 20;
            int legendY = 20;
            int legendItemHeight = 20;

            painter.setPen(Qt::white);
            font.setPointSize(9);
            painter.setFont(font);

            for (const auto& pair : dataSets) {
                const QString& name = pair.first;     // key
                const DataSet& dataset = pair.second; // value
                painter.setBrush(dataset.color);
                painter.drawRect(legendX, legendY, 15, 15);
                painter.drawText(legendX + 20, legendY + 12, name);
                legendY += legendItemHeight;
            }
        }
    }
};

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);

    RadarWidget radar;
    radar.setWindowTitle("Qt Radar Chart with Multiple Datasets");

    // 设置坐标轴标签
    std::vector<QString> labels = {"速度", "力量", "技巧", "耐力", "敏捷", "智力"};
    radar.setAxisLabels(labels);

    // 添加三组数据
    radar.addData("玩家1", {0.8, 0.6, 0.9, 0.7, 0.5, 0.85}, QColor(75, 181, 230));
    radar.addData("玩家2", {0.5, 0.8, 0.7, 0.6, 0.9, 0.6}, QColor(255, 107, 107));
    radar.addData("玩家3", {0.7, 0.7, 0.8, 0.5, 0.7, 0.9}, QColor(120, 224, 143));

    radar.show();

    return app.exec();
}

运行效果:

 

二、OpenGL和Qt绘制雷达图

创建步骤

创建一个 RadarWidget,继承自 QOpenGLWidget,并使用 OpenGL 3.3+ 核心模式进行渲染。

确保你的项目已链接 Qt OpenGL 模块,并在 .pro 文件中添加:

qmake

QT += opengl widgets

完整代码 

radarwidget.h

// radarwidget.h
#ifndef RADARWIDGET_H
#define RADARWIDGET_H

#include <QOpenGLWidget>
#include <QOpenGLFunctions_3_3_Core>
#include <QOpenGLShaderProgram>
#include <QOpenGLBuffer>
#include <QOpenGLVertexArrayObject>
#include <QVector>
#include <QVector3D>

class RadarWidget : public QOpenGLWidget, protected QOpenGLFunctions_3_3_Core
{
    Q_OBJECT

public:
    explicit RadarWidget(QWidget *parent = nullptr);
    ~RadarWidget();

    void setData(const QVector<QVector<float>> &data);
    void setLabels(const QStringList &labels);

protected:
    void initializeGL() override;
    void resizeGL(int w, int h) override;
    void paintGL() override;

    void mousePressEvent(QMouseEvent *event) override;
    void mouseMoveEvent(QMouseEvent *event) override;
    void wheelEvent(QWheelEvent *event) override;

private:
    void drawRadarGrid();
    void drawDataAreas();
    void drawLabels();

    QOpenGLShaderProgram *program;
    QOpenGLVertexArrayObject vao;
    QOpenGLBuffer vbo;

    QVector<QVector<float>> radarData;
    QStringList radarLabels;

    QVector3D backgroundColor;
    QVector3D gridColor;
    QVector<QVector3D> dataColors;

    int dimensions;
    float scale;
    QPoint lastPos;
    float rotationX, rotationY;
};

#endif // RADARWIDGET_H

radarwidget.cpp

// radarwidget.cpp
#include "radarwidget.h"
#include <QMouseEvent>
#include <QWheelEvent>
#include <QDebug>
#include <cmath>
#include <qmath.h>

RadarWidget::RadarWidget(QWidget *parent)
    : QOpenGLWidget(parent),
    dimensions(0),
    scale(1.0f),
    rotationX(0),
    rotationY(0)
{
    QSurfaceFormat format;
    format.setSamples(4);
    format.setVersion(3, 3);
    format.setProfile(QSurfaceFormat::CoreProfile);
    setFormat(format);

    backgroundColor = QVector3D(0.1f, 0.1f, 0.2f);
    gridColor = QVector3D(0.5f, 0.5f, 0.7f);

    // 预定义一些数据颜色
    dataColors << QVector3D(1.0f, 0.0f, 0.0f)  // 红
               << QVector3D(0.0f, 1.0f, 0.0f)  // 绿
               << QVector3D(0.0f, 0.0f, 1.0f)  // 蓝
               << QVector3D(1.0f, 1.0f, 0.0f)  // 黄
               << QVector3D(1.0f, 0.0f, 1.0f); // 紫
}

RadarWidget::~RadarWidget()
{
    makeCurrent();
    vbo.destroy();
    delete program;
    doneCurrent();
}

void RadarWidget::setData(const QVector<QVector<float>> &data)
{
    radarData = data;
    if (!radarData.isEmpty()) {
        dimensions = radarData.first().size();
    }
    update();
}

void RadarWidget::setLabels(const QStringList &labels)
{
    radarLabels = labels;
    update();
}

void RadarWidget::initializeGL()
{
    initializeOpenGLFunctions();
    glClearColor(backgroundColor.x(), backgroundColor.y(), backgroundColor.z(), 1.0f);

    // 初始化着色器程序
    program = new QOpenGLShaderProgram(this);
    program->addShaderFromSourceCode(QOpenGLShader::Vertex,
                                     R"(
#version 330 core

layout(location = 0) in vec3 aPos;
uniform mat4 mvp_matrix;

void main()
{
    gl_Position = mvp_matrix * vec4(aPos, 1.0);
}
)");
    program->addShaderFromSourceCode(QOpenGLShader::Fragment,
                                     R"(
#version 330 core

uniform vec3 color;
out vec4 fragColor;

void main()
{
    fragColor = vec4(color, 1.0);
}
)");
    if (!program->link()) {
        qDebug() << "Shader link error:" << program->log();
    }
    program->bind();

    vao.create();
    // 初始化VBO
    vbo.create();
    vbo.bind();
    vbo.setUsagePattern(QOpenGLBuffer::DynamicDraw);

    // 设置顶点属性指针
    program->enableAttributeArray(0);
    program->setAttributeBuffer(0, GL_FLOAT, 0, 3, 3 * sizeof(float));

    vbo.release();
    vao.release();
    program->release();

    // 启用深度测试
    glEnable(GL_DEPTH_TEST);
}

void RadarWidget::resizeGL(int w, int h)
{
    glViewport(0, 0, w, h);
}

void RadarWidget::paintGL()
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    program->bind();

    // 设置模型视图投影矩阵
    QMatrix4x4 projection;
    projection.perspective(45.0f, width() / float(height()), 0.1f, 100.0f);

    QMatrix4x4 view;
    view.translate(0.0f, 0.0f, -3.0f);
    view.rotate(rotationX, 1.0f, 0.0f, 0.0f);
    view.rotate(rotationY, 0.0f, 1.0f, 0.0f);

    QMatrix4x4 model;
    model.scale(scale);

    program->setUniformValue("mvp_matrix", projection * view * model);

    // 绘制雷达网格
    drawRadarGrid();

    // 绘制数据区域
    if (!radarData.isEmpty()) {
        drawDataAreas();
    }

    program->release();
}

void RadarWidget::drawRadarGrid()
{
    if (dimensions < 3) return;

    QVector<float> vertices;
    const int circles = 5; // 同心圆数量
    const float maxRadius = 1.0f;

    // 绘制同心圆
    for (int i = 1; i <= circles; ++i) {
        float radius = maxRadius * i / circles;
        for (int j = 0; j < dimensions; ++j) {
            float angle1 = 2 * M_PI * j / dimensions;
            float angle2 = 2 * M_PI * (j + 1) / dimensions;

            // 每个圆由线段组成
            vertices << radius * sin(angle1) << radius * cos(angle1) << 0.0f;
            vertices << radius * sin(angle2) << radius * cos(angle2) << 0.0f;
        }
    }

    // 绘制从中心到边缘的线
    for (int i = 0; i < dimensions; ++i) {
        float angle = 2 * M_PI * i / dimensions;
        vertices << 0.0f << 0.0f << 0.0f;
        vertices << maxRadius * sin(angle) << maxRadius * cos(angle) << 0.0f;
    }

    vao.bind();
    vbo.bind();
    vbo.allocate(vertices.constData(), vertices.size() * sizeof(float));
    program->enableAttributeArray(0);
    program->setAttributeBuffer(0, GL_FLOAT, 0, 3, 3 * sizeof(float));
    program->setUniformValue("color", gridColor);
    glDrawArrays(GL_LINES, 0, vertices.size() / 3);

    vbo.release();
    vao.release();
}

void RadarWidget::drawDataAreas()
{
    if (dimensions < 3) return;

    QVector<float> vertices;
    const float maxRadius = 1.0f;

    // 为每个数据集绘制多边形
    for (int dataset = 0; dataset < radarData.size(); ++dataset) {
        if (dataset >= dataColors.size()) break;

        vertices.clear();
        const QVector<float> &data = radarData[dataset];

        // 添加中心点
        vertices << 0.0f << 0.0f << 0.0f;

        // 添加数据点
        for (int i = 0; i < dimensions; ++i) {
            float angle = 2 * M_PI * i / dimensions;
            float radius = maxRadius * data[i];
            vertices << radius * sin(angle) << radius * cos(angle) << 0.0f;
        }

        // 闭合多边形(回到第一个数据点)
        float angle = 2 * M_PI * 0 / dimensions;
        float radius = maxRadius * data[0];
        vertices << radius * sin(angle) << radius * cos(angle) << 0.0f;

        vao.bind();
        vbo.bind();
        vbo.allocate(vertices.constData(), vertices.size() * sizeof(float));
        program->enableAttributeArray(0);
        program->setAttributeBuffer(0, GL_FLOAT, 0, 3, 3 * sizeof(float));
        // 设置颜色并绘制
        program->setUniformValue("color", dataColors[dataset]);
        glDrawArrays(GL_TRIANGLE_FAN, 0, vertices.size() / 3);

        vbo.release();
        vao.release();
    }
}

void RadarWidget::mousePressEvent(QMouseEvent *event)
{
    lastPos = event->pos();
}

void RadarWidget::mouseMoveEvent(QMouseEvent *event)
{
    int dx = event->x() - lastPos.x();
    int dy = event->y() - lastPos.y();

    if (event->buttons() & Qt::LeftButton) {
        rotationX = qBound(-90.0f, rotationX + dy * 0.5f, 90.0f);
        rotationY += dx * 0.5f;
        update();
    }

    lastPos = event->pos();
}

void RadarWidget::wheelEvent(QWheelEvent *event)
{
    float delta = event->angleDelta().y() / 120.0f;
    scale *= (1.0f + delta * 0.1f);
    scale = qBound(0.5f, scale, 2.0f);
    update();
}

mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include "radarwidget.h"

QT_BEGIN_NAMESPACE
namespace Ui {
class MainWindow;
}
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private:
    Ui::MainWindow *ui;
    RadarWidget *radarWidget;
};
#endif // MAINWINDOW_H

mainwindow.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    radarWidget = new RadarWidget(this);
    setCentralWidget(radarWidget);

    // 设置示例数据
    QVector<QVector<float>> data;
    data << QVector<float>{0.8f, 0.6f, 0.9f, 0.7f, 0.5f};
    data << QVector<float>{0.5f, 0.8f, 0.6f, 0.4f, 0.9f};

    radarWidget->setData(data);
    radarWidget->setLabels(QStringList() << "Speed" << "Power" << "Range" << "Durability" << "Precision");

    resize(800, 600);
}

MainWindow::~MainWindow()
{
    delete ui;
}

main.cpp

#include "mainwindow.h"

#include <QApplication>
#include <QWidget>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();
    return a.exec();
}

运行结果:


网站公告

今日签到

点亮在社区的每一天
去签到