作者:red_小火柿子 | 来源:互联网 | 2023-06-26 22:37
0.前言热力图是一种重要的数据可视化方式,可以直观地展现空间数据的疏密程度或频率高低。本文将使用QPainter+QImage来实现热力图绘制,效果参照百度ECharts图表库:《
0.前言
热力图是一种重要的数据可视化方式,可以直观地展现空间数据的疏密程度或频率高低。本文将使用QPainter+QImage来实现热力图绘制,效果参照百度ECharts图表库:
《从前慢》 ——–木心
记得早先少年时
大家诚诚恳恳
说一句 是一句
清早上火车站
长街黑暗无行人
卖豆浆的小店冒着热气
从前的日色变得慢
车,马,邮件都慢
一生只够爱一个人
从前的锁也好看
钥匙精美有样子
你锁了 人家就懂了
目录
1.实现思路
2.实现细节
3.代码链接
4.参考
1.实现思路
主要步骤如下:
a.每个单独的点为径向渐变色的圆,且透明度从中心到边缘逐渐降低
QRadialGradient gradient(posX,posY,radius); //径向渐变
gradient.setColorAt(0,QColor(0,0,0,alpha)); //点的透明度
gradient.setColorAt(1,QColor(0,0,0,0));
painter.setBrush(gradient);
painter.drawEllipse(QPointF(posX,posY),radius,radius);
b.多个点组合时,透明度是互相叠加的,所以我们先用透明度绘制一个Image(透明度就表示热度)
//透明度用Format_Alpha8就行了
_dataImg=QImage(ImgWidth,ImgHeight,QImage::Format_Alpha8);
_dataImg.fill(Qt::transparent);
//接下来追加绘制圆点
c.如果一个点出现多次,那么就需要有权重的概念,可以根据重复最多的点来作为计算的标准,也可以根据重复次数分段
//权重统计表(把权重单独拿出来,就可以不用遍历数据点来计算了)
//加()初始化为0
int *_countTable=new int[ImgWidth*ImgHeight]();
//以最大次数来计算该点的权重
const uchar alpha=uchar(_countTable[posX+posY*ImgWidth]/(double)_maxCount*255);
d.透明度的图绘制完成后就可以填充颜色,颜色根据透明度从计算好的颜色表中查表获得
void MainWindow::drawHeatImg()
{
//把alpha值转为颜色值
for(int row=0;row<_dataImg.height();row++)
{
//dataimg QImage::Format_Alpha8,一个点1个字节
const uchar *line_data=_dataImg.scanLine(row);
//heatimg QImage::Format_ARGB32,一个点4个字节
QRgb *line_heat=reinterpret_cast(_heatImg.scanLine(row));
for(int col=0;col<_dataImg.width();col++)
{
//根据alpha透明度从颜色表取颜色
line_heat[col]=_colorList[line_data[col]];
}
}
}
2.实现细节
a.颜色表的实现
颜色表我使用的线性渐变会知道Image来取颜色的方式,因为单纯 QLinearGradient 好像没有取某个点颜色的接口;还有就是计算颜色透明度的时候可能要考虑热力图整体的透明度。
颜色表的元素类型为 QRgb ,是 uint32 的 typedef ,表示 #AARRGGBB。
//根据线性渐变色条得到颜色表
QLinearGradient linear=QLinearGradient(QPoint(0,0),QPoint(255,0));
linear.setColorAt(0, Qt::blue);
linear.setColorAt(0.4, Qt::blue);
linear.setColorAt(0.5, Qt::cyan);
linear.setColorAt(0.6, Qt::green);
linear.setColorAt(0.8, Qt::yellow);
linear.setColorAt(0.95, Qt::red); //255对应透明度最大值,即中心点
//把渐变色绘制到Img方便取颜色
QImage img(256,1,QImage::Format_ARGB32);
QPainter painter(&img);
painter.fillRect(img.rect(),linear);
//HeatAlpha为热力图整体透明度
quint32 alpha=0;
for(quint32 i=0;i<256;i++){
//根据热力图透明度来计算颜色表的透明度
alpha=HeatAlpha/255.0*i;
//typedef unsigned int QRgb: format #AARRGGBB
//颜色+透明度
_colorList[i]=img.pixel(i,0)&0x00FFFFFF|(alpha<<24);
}
b.点的追加
如果点不是重合的,那么直接在透明度图中追加绘制就行,如果点重合了,那么出于权重对透明度的影响,需要重新绘制透明度图。
//为什么不直接if(pos_count>_maxCount)才重新绘制?
//而是有两个点叠加if(pos_count>1)就重新绘制?
//因为单纯的叠加目前还没有带入权重来计算,如果最大权重更大,那么这个叠加的点颜色就走样了
if(pos_count>1){
if(pos_count>_maxCount)
_maxCount=pos_count;
drawDataImg();
}else{
drawDataPoint(pt);
}
c.透明度图转热力图
因为我们是先绘制的透明度图来表示热度,再通过透明度查表来填充的颜色,所以我们需要两个Image。
//data用alpha叠加
_dataImg=QImage(ImgWidth,ImgHeight,QImage::Format_Alpha8);
_dataImg.fill(Qt::transparent);
//热力图通过alpha值查表
_heatImg=QImage(ImgWidth,ImgHeight,QImage::Format_ARGB32);
_heatImg.fill(Qt::transparent);
绘制热力图的时候,我们使用 QImage 的 scanLine() 函数,以及颜色查表来提高效率。
//把alpha值转为颜色值
for(int row=0;row<_dataImg.height();row++)
{
//dataimg QImage::Format_Alpha8,一个点1个字节
const uchar *line_data=_dataImg.scanLine(row);
//heatimg QImage::Format_ARGB32,一个点4个字节
QRgb *line_heat=reinterpret_cast(_heatImg.scanLine(row));
for(int col=0;col<_dataImg.width();col++)
{
//根据alpha透明度从颜色表取颜色
line_heat[col]=_colorList[line_data[col]];
}
}
3.代码链接
代码git链接:
https://github.com/gongjianbo/MyTestCode/tree/master/Qt/MyHeatMap
实现效果:
4.参考
JS版:https://blog.csdn.net/HiddlestonCloud/article/details/83743449
Qt版:https://blog.csdn.net/pbe_sedm/article/details/8982357