一、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();
}
运行结果:
代码说明:
RadarWidget 类:
继承自 QWidget,负责绘制雷达图
setData()
方法用于设置数据和标签重写
paintEvent()
实现绘图逻辑
主要绘制部分:
drawGrid()
:绘制同心圆网格和轴线drawDataPolygon()
:绘制数据多边形和顶点drawLabels()
:在轴线末端绘制标签
坐标计算:
使用极坐标转换:
x = center.x + radius * cos(angle)
角度从顶部开始(-π/2),按等分角度分布
数据值归一化到 0~1 范围
视觉样式:
半透明填充色
白色数据点
虚线网格
深色背景增强对比度
多组数据雷达图实例
多组数据
修改数据结构以支持多组数据,使用不同颜色绘制多个多边形。
#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();
}
运行结果: