因为工作中要用到所以自己做了一个雷达图的实现。记录一下 , 使用方法请自行探索,图例的实现暂时没写, 但是留了图例获取对应数据的接口。
代码里留了一个小bug(也不能算是bug,就是在面对一些特殊数据的时候会有问题),交给各位去探索了。
效果
源码
//RadarPlot.h
#pragma once
#include <qwidget.h>
#include <qmap.h>
#include <qvector.h>
#include <qpair.h>
#include <qpainter.h>
#include <qrect.h>
#include <qdebug.h>
#include <qpolygon.h>
namespace mortal
{
struct RadarPlotData
{
QColor color_;
QVector<float> data_;
void SetColor(const QColor& color)
{
color_ = color;
}
QColor GetColor(void)
{
return color_;
}
void AddData(float data)
{
data_.push_back(data);
}
void ClearData(void) { data_.clear(); }
};
class RadarPlot :public QWidget
{
Q_OBJECT
private:
unsigned int number_data_item_;
unsigned int number_data_source_;
bool fill_the_color_;
float color_alpha_;
int inside_line_width_, exterior_line_width_ , dot_width_ ;
int item_name_size_ , item_name_height_;
QColor inside_line_color_, exterior_line_color_ , text_color_;
QMap<QString, RadarPlotData> data_;
QVector<QString> item_name_;
/*
* date : 2024-07-27
* name : mortal
* describe: 在paintEvent中调用,用来绘制一个数据曲线
* *painter 当前绘制所使用的painter 指针
data 对当前数据项的常引用,用来获取绘制数据。
* return : void
*/
void DrawADataLine(QPainter* painter, const RadarPlotData& data)
{
painter->save();
QColor temporary_color = data.color_;
QPen pen;
int radius = 400;
pen.setColor(temporary_color);
painter->setPen(pen);
QPolygon local_poly;
double angle = double(2 * 3.1415926) / number_data_item_;
painter->save();
QPen local_pen = painter->pen();
local_pen.setWidth(dot_width_);
painter->setPen(local_pen);
for (int i = 0; i < number_data_item_; i++)
{
local_poly << QPoint(data.data_[i] * radius * cos(angle * i), data.data_[i] * radius * sin(angle * i));
painter->drawPoint(QPoint(data.data_[i] * radius * cos(angle * i), data.data_[i] * radius * sin(angle * i)));
}
painter->restore();
if (fill_the_color_ == true)
{
QPainterPath linshi;
linshi.addPolygon(local_poly);
painter->fillPath(linshi, QBrush(QColor(temporary_color.red(), temporary_color.green(), temporary_color.blue(), color_alpha_ * 255)));
}
else
{
painter->drawPolygon(local_poly);
}
painter->restore();
}
/*
* date : 2024-07-27
* name : mortal
* describe: 得到数据项标注应该处于的位置
* data 为数据项线段末端点坐标
* *position 为标注数据项文本在该QRect中对齐方式,是一个指针返回值
* return : 返回一个QRect用来限制数据项名称的位置。
*/
QRect GetItemNameTextPosition(QPoint data ,int *position)
{
int local_rect_hight = item_name_height_;
QSize local_rect_size(100, local_rect_hight);
if (data.x() >= 0 && data.y() >= 0)
{
(*position) = 0;
return QRect(data, local_rect_size);
}
else if (data.x()< 0 && data.y() >=0)
{
(*position) = 1;
return QRect(QPoint(data.x()-100,data.y()), local_rect_size);
}
else if (data.x() < 0 && data.y() < 0)
{
(*position) = 2;
return QRect(QPoint(data.x() - 100, data.y() - local_rect_hight), local_rect_size);
}
else if (data.x() >= 0 && data.y() < 0)
{
(*position) = 3;
return QRect(QPoint(data.x(), data.y() - local_rect_hight), local_rect_size);
}
}
/*
* date : 2024-07-27
* name : mortal
* describe: 绘制单个数据项文本
* return : void
*/
void DrawADataItemName(QPainter *painter , QRect rect, int position, QString name)
{
painter->save();
QPen pen;
pen.setColor(text_color_);
pen.setWidth(item_name_size_);
painter->setPen(pen);
QFont font;
font.setPixelSize(item_name_size_);
painter->setFont(font);
if (position == 0)
{
painter->drawText(rect, Qt::AlignLeft | Qt::AlignTop, name);
}
else if (position == 1)
{
painter->drawText(rect, Qt::AlignRight | Qt::AlignTop, name);
}
else if (position == 2)
{
painter->drawText(rect, Qt::AlignRight | Qt::AlignBottom, name);
}
else if (position == 3)
{
painter->drawText(rect, Qt::AlignLeft | Qt::AlignBottom, name);
}
painter->restore();
return;
}
protected:
void paintEvent(QPaintEvent* event)override
{
QRect rect = this->rect();
double low_side = (rect.width() > rect.height() ? rect.height() : rect.width());
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
painter.translate(rect.width() / 2, rect.height() / 2);
painter.scale(low_side / 1000.0, low_side / 1000.0);
int radius = 400;
double angle = double(2 * 3.1415926) / number_data_item_;
QPen pen;
pen.setColor(inside_line_color_);
pen.setWidth(inside_line_width_);
painter.setPen(pen);
QPoint center(0, 0);
/*
* date : 2024-07-27
* name : mortal
* describe: 画内圈圆
*/
for (int i = 0; i < 10; i++)
{
painter.drawEllipse(center, 40 * i, 40 * i);
}
/*
* date : 2024-07-27
* name : mortal
* describe: 画标示数据项的线条
*/
for (int i = 0; i < number_data_item_; i++)
{
QPoint terminus(radius * (cos(angle * i)), radius * (sin(angle * i)));
painter.drawLine(center, terminus);
painter.save();
int local_int = 0;
QRect local_rect = GetItemNameTextPosition(terminus, &local_int);
this->DrawADataItemName(&painter, local_rect, local_int, item_name_[i]);
painter.restore();
MYOUT << terminus;
}
pen.setColor(exterior_line_color_);
pen.setWidth(exterior_line_width_);
painter.setPen(pen);
painter.drawEllipse(center, radius, radius);
/*
* date : 2024-07-27
* name : mortal
* describe: 画数据。
*/
for (auto i : data_)
{
this->DrawADataLine(&painter, i);
}
}
public:
/*
* date : 2024-07-27
* name : mortal
* describe: 默认构造函数用来设置初始值
* return :
*/
RadarPlot()
{
color_alpha_ = 1;
fill_the_color_ = false;
number_data_item_ = 0;
number_data_source_ = 0;
inside_line_width_ = 1;
inside_line_color_ = QColor(172, 172, 172);
exterior_line_width_ = 2;
exterior_line_color_ = QColor(0, 0, 0);
dot_width_ = 4;
item_name_size_ = 50;
item_name_height_ = 100;
text_color_ = QColor(0, 0, 0);
}
~RadarPlot() {};
/*
* date :2024-07-27
* name : mortal
* describe: 为雷达图设置数据项名称 , 会清除原有的数据项名称
*/
void SetItemName(const QVector<QString>& name)
{
int tick = 0;
item_name_.clear();
for (auto i : name)
{
item_name_.push_back(i);
tick++;
}
number_data_item_ = tick;
};
/*
* date :2024-07-27
* name : mortal
* describe: 设置每个曲线是否需要颜色填充
*/
void SetFillPlot(bool fill) { fill_the_color_ = fill; }
/*
* date :2024-07-27
* name : mortal
* describe: 设置雷达图曲线颜色的透明度[0,1],该属性只对开启fillplot模式起作用
*/
void SetColorAlpha(float alpha)
{
color_alpha_ = alpha;
}
/*
* date:2024-07-27
* name: mortal
* describe: 向雷达图中添加数据,
* line_name 为该曲线的名字
* data 为该曲线持有的数据,
* cover 为如果在雷达图中发现同名曲线是否覆盖原曲线
*/
void AddData(QString line_name, const RadarPlotData& data, bool cover = false)
{
if (data_.contains(line_name))
{
//是否有line_name 曲线
if (cover == false)
{
// 如果有这个数据曲线,且明确不对其进行覆盖则返回
return;
}
else
{
data_[line_name] = data;
return;
}
}
else
{
//没有line_name曲线 , 代表向雷达图中插入新曲线
data_.insert(line_name, data);
return;
}
}
/*
* date : 2024-07-27
* name : mortal
* describe: 从雷达图中取名为 line_name 的曲线数据。
* line_name 为待取数据名称
* can_get 为标志位,雷达图中是否有该曲线,默认值为空指针,代表不做测试。当其非空起作用。
* return : 如果找到数据返回该数据, 如果没有则返回一个初始构造的RadarPlotData
*/
RadarPlotData GetLineData(QString line_name , bool* can_get = nullptr)
{
if (data_.contains(line_name))
{
if (can_get != nullptr)
{
(*can_get) = true;
}
return data_[line_name];
}
else
{
if (can_get != nullptr)
{
(*can_get) = false;
}
return RadarPlotData();
}
}
/*
* date : 2024-07-27
* name : mortal
* describe: 判断雷达图中是否有 line_name 曲线
* line_name 为曲线名称
* return : 如有返回true 如果没有返回false
*/
bool HaveTheData(QString line_name)
{
if (data_.contains(line_name))
{
return true;
}
return false;
}
/*
* date : 2024-07-27
* name : mortal
* describe: 获取当前持有的数据曲线名称即对应颜色 ,该函数主要为了图例做准备。
* return : 返回一个vector<QPair<QString , QColor>>
*/
QVector <QPair<QString, QColor>> GetLineNameAndColor(void)
{
QVector<QPair<QString, QColor>> should_return;
QList<QString> name_list = data_.keys();
for (auto i : name_list)
{
QColor temporary = data_[i].GetColor();
should_return.push_back(QPair<QString, QColor>(i, temporary));
}
return should_return;
}
/*
* date : 2024-07-27
* name : mortal
* describe: 设置雷达图内部线条颜色,及其粗细
* return : void
*/
void SetInsideLine(int width = 1, QColor color = QColor(172,172,172))
{
inside_line_width_ = width;
inside_line_color_ = color;
}
/*
* date : 2024-07-27
* name : mortal
* describe: 设置雷达图外圈线条颜色,及其粗细
* return : void
*/
void SetExterior(int width = 2, QColor color = QColor(0, 0, 0))
{
exterior_line_width_ = width;
exterior_line_color_ = color;
}
/*
* date : 2024-07-27
* name : mortal
* describe: 设置数据点大小。
* return : void
*/
void SetDotWidth(int width = 4)
{
dot_width_ = width;
}
/*
* date : 2024-07-27
* name : mortal
* describe: 设值数据项字体大小 ,初始值为50
* size 需要小于等于 item_name_height
* return : 返回真时设置成功,假时失败
*/
bool SetTextSize(int size = 50)
{
if (size <= item_name_size_)
{
item_name_size_ = size;
return true;
}
return false;
}
/*
* date : 2024-07-27
* name : mortal
* describe: 设置数据项文本颜色 ,默认为黑色
* return : void
*/
void SetTextColor(QColor color = QColor(0, 0, 0))
{
text_color_ = color;
}
/*
* date : 2024-07-27
* name : mortal
* describe: 设值数据项文本高度 ,初始值为100
* height 需要大于等于 item_name_size
* return : 返回真时设置成功,假时失败
*/
bool SetItemNameHeight(int height = 100)
{
if (height >= item_name_size_)
{
item_name_height_ = height;
return true;
}
return false;
}
};
};