由于随着需求的多样化,个性化,Qt自带的原生控件QChart已经不能够满足我们的追求个性与美完美融合的过分要求。所以在闲暇时间把玩C艹, 自定义绘制了饼状统计图。该自定义控件实现了颜色自定义设置,鼠标悬浮突出显示该部分在全部数据的占比。话不多说,直接上效果图:
bandicam
一、绘制背景(即每个扇形区域);
void CustomPieChart::drawValues(QPainter *painter)
{
auto iter = m_valueMap.begin();
QString key = "";
qreal value = 0.0;
qreal startAngle = 0.0;
qreal angleLength = 0.0;
for(;iter!=m_valueMap.end();iter++){
key = iter.key();
value = iter.value();
angleLength = (value / m_total) * 360 - BLANK_W;
gradientArc(painter, key, startAngle, angleLength, m_colorMap[key]);
m_anglePairMap.insert(key, QPair<qreal, qreal>(startAngle, angleLength));
startAngle += (angleLength + BLANK_W);
}
}
void CustomPieChart::gradientArc(QPainter *painter, QString key, qreal startAngle, qreal angleLength, QColor color)
{
qreal radius = m_valueStateMap.value(key)?m_radius+SUPERPLUS_H:m_radius;
painter->setBrush(color);
QRectF rectf(-radius, -radius, radius * 2, radius * 2);
QPainterPath path;
path.arcTo(rectf, startAngle, angleLength);
painter->drawPath(path);
}
绘制完成后的效果图如下所示:
二、绘制中间的圆形区域:
void CustomPieChart::drawMidRound(QPainter *painter)
{
if(m_total == 0)
return;
QLinearGradient linear(QPointF(0, 0), QPointF(150, 150));
linear.setColorAt(0, QColor("#c5c5c5"));
linear.setColorAt(1, QColor("#ffffff"));
painter->setBrush(linear);
painter->drawEllipse(QPointF(0, 0), m_midR, m_midR);
}
绘制完成后的效果图如下所示:
三、绘制中心圆形区域
void CustomPieChart::drawCenterRound(QPainter *painter)
{
if(m_total==0)
return;
painter->setBrush(Qt::white);
painter->drawEllipse(QPointF(0, 0), m_centerR, m_centerR);
}
绘制完成后的效果图如下所示:
四、绘制中心文字
void CustomPieChart::drawText(QPainter *painter)
{
if(m_total==0)
return;
QPen pen;
pen.setColor(QColor("#26303c"));
pen.setWidth(3);
painter->setPen(pen);
painter->setFont(QFont("Source Han Sans CN", m_fontSize));
int value = qCeil((float)m_valueMap.value(m_curKey) / (float)m_total * 100.0);
qreal w = QFontMetrics(painter->font()).width(QObject::tr("%1%").arg(value));
painter->drawText(-(w/2), 10, QObject::tr("%1%").arg(value));
painter->setFont(QFont("Source Han Sans CN", m_fontSize - 16));
w = QFontMetrics(painter->font()).width(m_curKey);
painter->drawText(-(w/2), 40, m_curKey);
}
绘制完成后的效果图如下所示:
到目前为止,整个饼状图已经绘制完成,现在需要实现的就是鼠标悬浮每个区域后突出显示该部分,并且中心文字为该部分的占比以及名称。这样就需要重新实现QWidget的鼠标移动事件,当我们鼠标在移动的时候,指向某一块后,将该快的半径大于其他区域的半径即就可以实现突出显示的效果,mouseMoveEvent,默认下只接收鼠标按下后的移动事件,所以必须设置:
setMouseTracking(true);
重点:如何找到鼠标当前所在的区域:以圆心为坐标原点,将鼠标所在的坐标为pos(x, y)转换为原点坐标;鼠标到圆心的距离 L等于:
qSqrt(qPow(pos.x(), 2) + qPow(pos.y(), 2));
如果当前L>midR && L< R, 那么知道当前鼠标在彩色的块区域部分,在绘制扇形区域时候会记录当前区域的其实角度和结束角度,那么计算pos和原点的夹角(不同象限不同计算);判断当前夹角在哪一块区域内,则突出显示该部分。
//起点到鼠标跨过的角度
float CustomPieChart::getPosInAngle(QPointF pos)
{
qreal a = qAbs(pos.y());
qreal c = qSqrt(qPow(pos.x(), 2) + qPow(pos.y(), 2));
qreal radians = qAsin((float)a/(float)c);
qreal rangle = 180.0 / M_PI * radians;
if(pos.x()>0 && pos.y()>0)
return rangle;
if(pos.x()<0 && pos.y()>0)
return 180.0 - rangle;
if(pos.x()<0 && pos.y()<0)
return 180.0 + rangle;
if(pos.x()>0 && pos.y()<0)
return 360.0 - rangle;
else
return 0.0;
}
主要代码头文件:
#ifndef CUSTOM_PIE_CHART_H
#define CUSTOM_PIE_CHART_H
#include <QWidget>
#include <QObject>
#include <QPainter>
#include <QPaintEvent>
#include <QMouseEvent>
#include <QEvent>
class CustomPieChart : public QWidget
{
QOBJECT_H
public:
explicit CustomPieChart(QWidget* parent = nullptr);
explicit CustomPieChart(const QSize& size, QWidget* parent = nullptr);
~CustomPieChart();
void setThisFixedSize(const QSize& size);
QSize thisSize()const;
void setData(const QMap<QString, int>& dataMap);
QMap<QString, int> data()const;
void setValueColor(const QMap<QString, QColor>& colorMap);
QMap<QString, QColor> valuesColor()const;
protected:
void paintEvent(QPaintEvent* e);
void leaveEvent(QEvent *event);
void mouseMoveEvent(QMouseEvent *event);
private:
void drawValues(QPainter* painter);
void drawMidRound(QPainter* painter);
void drawCenterRound(QPainter* painter);
void drawText(QPainter* painter);
void gradientArc(QPainter *painter, QString key, qreal startAngle, qreal angleLength, QColor color);
float getPosInAngle(QPointF pos);
QMap<QString, QColor> m_colorMap;
QMap<QString, int> m_valueMap;
qreal m_radius;
qreal m_total = 0;
qreal m_midR;
qreal m_centerR;
QMap<QString, QPair<qreal, qreal>> m_anglePairMap;
QMap<QString, bool> m_valueStateMap;
QRect m_midRect, m_centerRect;
qreal m_fontSize;
QString m_curKey = QString();
};
#endif // CUSTOM_PIE_CHART_H
源文件:
#include "custom_pie_chart.h"
#include <QDebug>
#include <QFontMetrics>
#include <qmath.h>
#define SUPERPLUS_H 10 //悬浮状态超出高度
#define BLANK_W 1 //各个value 间间隔
#define DEFAULT_FONT_SIZE 40 //默认字体大小
CustomPieChart::CustomPieChart(QWidget* parent)
:QWidget(parent)
{
setMouseTracking(true);
m_radius = width()/2 - SUPERPLUS_H; //大圆的半径
m_midR = m_radius*2/3; //中间圆的半径
m_centerR = m_radius/2; //中心圆的半径
m_fontSize = DEFAULT_FONT_SIZE; //字体默认大小
}
CustomPieChart::CustomPieChart(const QSize &size, QWidget *parent)
:QWidget(parent)
{
setMouseTracking(true);
setFixedSize(size);
m_radius = width()/2 - SUPERPLUS_H;
m_midR = m_radius*2/3;
m_centerR = m_radius/2;
m_fontSize = DEFAULT_FONT_SIZE;
}
CustomPieChart::~CustomPieChart(){}
void CustomPieChart::setThisFixedSize(const QSize &size)
{
setFixedSize(size);
m_radius = width()/2 - 20;
m_midR = m_radius*2/3;
m_centerR = m_radius/2;
update();
}
QSize CustomPieChart::thisSize()const
{
return size();
}
void CustomPieChart::setData(const QMap<QString, int>& dataMap)
{
m_valueStateMap.clear();
m_colorMap.clear();
m_anglePairMap.clear();
m_valueMap = dataMap;
auto iter = m_valueMap.begin();
for(int index = 1;iter!=m_valueMap.end();iter++){
m_total += iter.value();
if(index % 2 !=0)
m_colorMap.insert(iter.key(), QColor("#ee5b5b"));
else
m_colorMap.insert(iter.key(), QColor("#30c1d8"));
if(index == 1)
m_curKey = iter.key();
bool state = (index == 1)?true:false;
m_valueStateMap.insert(iter.key(), state);
index++;
}
if(m_colorMap.size()>0 && m_valueMap.size()>0)
update();
}
QMap<QString, int> CustomPieChart::data()const
{
return m_valueMap;
}
void CustomPieChart::setValueColor(const QMap<QString, QColor> &colorMap)
{
m_colorMap = colorMap;
if(m_colorMap.size()>0 && m_valueMap.size()>0)
update();
}
QMap<QString, QColor> CustomPieChart::valuesColor()const
{
return m_colorMap;
}
void CustomPieChart::paintEvent(QPaintEvent *)
{
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing, true);
painter.translate(width() >> 1, height() >> 1);
painter.setPen(Qt::NoPen);
drawValues(&painter); //绘制每一个扇形值区域
drawMidRound(&painter); //绘制中部圆
drawCenterRound(&painter); //绘制中心圆
drawText(&painter); //绘制占比
}
void CustomPieChart::drawValues(QPainter *painter)
{
auto iter = m_valueMap.begin();
QString key = "";
qreal value = 0.0;
qreal startAngle = 0.0;
qreal angleLength = 0.0;
for(;iter!=m_valueMap.end();iter++){
key = iter.key();
value = iter.value();
angleLength = (value / m_total) * 360 - BLANK_W;
gradientArc(painter, key, startAngle, angleLength, m_colorMap[key]);
m_anglePairMap.insert(key, QPair<qreal, qreal>(startAngle, angleLength));
startAngle += (angleLength + BLANK_W);
}
}
void CustomPieChart::drawMidRound(QPainter *painter)
{
if(m_total == 0)
return;
QLinearGradient linear(QPointF(0, 0), QPointF(150, 150));
linear.setColorAt(0, QColor("#c5c5c5"));
linear.setColorAt(1, QColor("#ffffff"));
painter->setBrush(linear);
painter->drawEllipse(QPointF(0, 0), m_midR, m_midR);
}
void CustomPieChart::drawCenterRound(QPainter *painter)
{
if(m_total==0)
return;
painter->setBrush(Qt::white);
painter->drawEllipse(QPointF(0, 0), m_centerR, m_centerR);
}
void CustomPieChart::drawText(QPainter *painter)
{
if(m_total==0)
return;
QPen pen;
pen.setColor(QColor("#26303c"));
pen.setWidth(3);
painter->setPen(pen);
painter->setFont(QFont("Source Han Sans CN", m_fontSize));
int value = qCeil((float)m_valueMap.value(m_curKey) / (float)m_total * 100.0);
qreal w = QFontMetrics(painter->font()).width(QObject::tr("%1%").arg(value));
painter->drawText(-(w/2), 10, QObject::tr("%1%").arg(value));
painter->setFont(QFont("Source Han Sans CN", m_fontSize - 16));
w = QFontMetrics(painter->font()).width(m_curKey);
painter->drawText(-(w/2), 40, m_curKey);
}
void CustomPieChart::gradientArc(QPainter *painter, QString key, qreal startAngle, qreal angleLength, QColor color)
{
qreal radius = m_valueStateMap.value(key)?m_radius+SUPERPLUS_H:m_radius;
painter->setBrush(color);
QRectF rectf(-radius, -radius, radius * 2, radius * 2);
QPainterPath path;
path.arcTo(rectf, startAngle, angleLength);
painter->drawPath(path);
}
void CustomPieChart::leaveEvent(QEvent *event)
{
return QWidget::leaveEvent(event);
}
void CustomPieChart::mouseMoveEvent(QMouseEvent *event)
{
QPointF pos = event->pos();
pos = QPointF(pos.x() - m_radius, m_radius - pos.y());
qreal l = qSqrt(qPow(pos.x(), 2)+qPow(pos.y(), 2));
//内圆
if(l < m_radius/2){
m_centerR = m_radius/2 + SUPERPLUS_H;
m_fontSize = DEFAULT_FONT_SIZE + 2;
update();
return QWidget::mouseMoveEvent(event);
}
//外圈圆
if(l > m_radius*2/3 && l<m_radius){
m_centerR = m_radius/2;
m_fontSize = DEFAULT_FONT_SIZE;
qreal rangle = getPosInAngle(pos);
auto iter = m_anglePairMap.begin();
for(;iter!=m_anglePairMap.end();iter++){
QPair<qreal, qreal> value = iter.value();
if(rangle>=value.first && rangle<=(value.first + value.second)){
m_valueStateMap[iter.key()] = true;
m_curKey = iter.key();
}
else
m_valueStateMap[iter.key()] = false;
}
update();
return QWidget::mouseMoveEvent(event);
}
return QWidget::mouseMoveEvent(event);
}
float CustomPieChart::getPosInAngle(QPointF pos)
{
qreal a = qAbs(pos.y());
qreal c = qSqrt(qPow(pos.x(), 2) + qPow(pos.y(), 2));
qreal radians = qAsin((float)a/(float)c);
qreal rangle = 180.0 / M_PI * radians;
if(pos.x()>0 && pos.y()>0)
return rangle;
if(pos.x()<0 && pos.y()>0)
return 180.0 - rangle;
if(pos.x()<0 && pos.y()<0)
return 180.0 + rangle;
if(pos.x()>0 && pos.y()<0)
return 360.0 - rangle;
else
return 0.0;
}
测试代码:
ui->setupUi(this);
setMouseTracking(true);
m_pieChartView = new CustomPieChart(QSize(400, 400), this);
QGridLayout* glayout = new QGridLayout(this);
glayout->addWidget(m_pieChartView);
QMap<QString, int> dataMap;
dataMap.insert("a", 24);
dataMap.insert("b", 40);
dataMap.insert("c", 70);
dataMap.insert("d", 30);
dataMap.insert("e", 50);
dataMap.insert("f", 90);
dataMap.insert("g", 35);
dataMap.insert("h", 35);
QMap<QString, QColor> colorMap;
colorMap.insert("a", QColor("#4EEE94"));
colorMap.insert("b", QColor("#6495ED"));
colorMap.insert("c", QColor("#8470FF"));
colorMap.insert("d", QColor("#FFD700"));
colorMap.insert("e", QColor("#EE6363"));
colorMap.insert("f", QColor("#FF1493"));
colorMap.insert("g", QColor("#EE2C2C"));
colorMap.insert("h", QColor("#A020F0"));
m_pieChartView->setData(dataMap);
m_pieChartView->setValueColor(colorMap);