qt 数据雷达图

        因为工作中要用到所以自己做了一个雷达图的实现。记录一下 , 使用方法请自行探索,图例的实现暂时没写, 但是留了图例获取对应数据的接口。

        代码里留了一个小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;
		}



	};

};

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值