其他参考资料:
https://www.docin.com/p-1509919023.html
https://blog.csdn.net/HANZY72/article/details/110253400
手绘草图,发觉之前网上很多的图都会误导大家去理解
算法前处理
1 首先对输入数据进行排序
2 对数据数据总和和窗口面积进行等比换手。
算法基本思路:
原始数据:{6,6,4,3,2,2,1}
step1: 首先确认最短边,将第一个元素6放置在最短边处,已知最短边为 4 (矩形高度)
当前长宽比计算:6/4(面积/边长=另一边)和4两个数比值的最大值
8/3 = max(1.5/4,4/1.5);
step2: 取出第二个元素5,让其和上一个区域并列存放。(每次都先尝试并列存放)
已知固定边为4(矩形高),目前两个元素总和是12,那么12/4是另一边长3,所以可以
求出长宽比 3/2 = max(2/3,3/2).当前长宽比小于上一个长宽比(8/3),说明当前放置方
式是合适的。
step3: 继续放置4,如图第三步上方图像。通过总和(4+6+6)计算出矩形的宽比
新假如矩形的长 16/4 = 4 新加入矩形的高 4/4=1;长宽比:4/1 = max(1/4,4/1);
比较发现当前长宽比4/1大于之前的3/2,说明当前加入的矩形没有改善布局样式。
所以尝试在新的一行中加入,如图第step3下方图像。
简单说明: 始终靠这个最短边放置,总是尝试和前一个矩形进行并列放置,当发现加入的长宽比大于之前的,则保存好之前加入的矩形,
不再变动。然后在空白区域开始新的一行加入,重复之前动作,直到所有元素都加入到了矩形内。
代码展示
#ifndef SQUARIFY_H
#define SQUARIFY_H
/********************************************************************
created: 2021.1.21
author: 程敏
purpose: Squarified算法(正方化算法)实现
参考资料:
https://www.docin.com/p-1509919023.html
https://blog.csdn.net/HANZY72/article/details/110253400
https://note.youdao.com/ynoteshare/index.html?id=1daab9f1d47022635eeb2abe17beefeb&type=note
算法思路:
简单说下就是始终靠这个最短边放置,总是尝试和前一个矩形进行并列放置,当发现新加入的长宽比大于之前的,则保存好之前加入的矩形,不再变动。然后在空白区域开始新的一行加入,重复之前动作,直到所有元素
都加入到了矩形内。
*********************************************************************/
#include <QVector>
#include <QRectF>
#include <QVariant>
//输入数据
struct SquarifyInputItem
{
double value; // 处理的数据
QVariant userData; // 可选项,用于输出时标记,方便用户进行数据对比
};
//输出数据
struct SquarifyOutputItem
{
QVariant userData; //输入时的数据
QRectF rc; //数据布局的窗口区域
};
class Squarify
{
public:
Squarify();
/**
* @brief 正方化布局算法
* @param[in] rc 矩形区域
* @param[in] values 输入数据 外部需要确保数据有序
* @param[in] bClacRate 是否进行等比转换。默认是进行的。如果外部有条件可以把每个点算好。
* 集合中每个数据的等比算法 :(矩形面积 / 数据总和) * 单个数据值
* @retval 返回对应区域集合
*/
QVector<SquarifyOutputItem> squarifyMath(QRectF rc,const QVector<SquarifyInputItem>& values,bool bClacRate = true);
private:
//递归算每个点
void squarify(const QVector<SquarifyInputItem>& inputs,const QVector<SquarifyInputItem>& currValues);
//布局rectangle,经过这里布局后的区域,不会在改变
void layoutRects(const QVector<SquarifyInputItem>& preValues);
//计算输入的点位中最大的长宽比
double calcWHrate(const QVector<SquarifyInputItem>& currValues);
private:
//外部输入区域
QRectF m_rc;
//输入的数据
QVector<SquarifyInputItem> m_values;
//当前布局后,还剩余的区域 逐步缩小
QRectF m_emptyRc;
//输出计算好的数据
QVector<SquarifyOutputItem> m_rects;
};
#endif // SQUARIFY_H
#include "squarify.h"
Squarify::Squarify()
{
}
QVector<SquarifyOutputItem> Squarify::squarifyMath(QRectF rc,const QVector<SquarifyInputItem>& values,bool bClacRate)
{
//数据和区域面积等比换算 数据可能不是和面积是等比例的
m_values = values;
if(bClacRate)
{
double v;
//计算总的数
for(int i=0;i < m_values.size();i++)
{
v += m_values[i].value;
}
//比率 面积除以总数
v = rc.width()*rc.height()/v;
for(int i=0;i < m_values.size();i++)
{
//每个点计算放大或缩小
m_values[i].value *= v;
}
}
m_emptyRc = rc;
m_rc = rc;
squarify(m_values,QVector<SquarifyInputItem>());
return m_rects;
}
void Squarify::squarify(const QVector<SquarifyInputItem> &inputs, const QVector<SquarifyInputItem> &currValues)
{
if(m_emptyRc.isEmpty())
return;
if(inputs.isEmpty())
{
//输入数据为空,并放置好数据退出递归循环
if(!currValues.isEmpty())
{
layoutRects(currValues);
}
return;
}
//取出最顶部的点,和之前数组的值组成新的数组。用心数组原因是如果放置不正确可以回溯
SquarifyInputItem c = inputs[0];
QVector<SquarifyInputItem> newrow = currValues; // newrow
newrow.push_back(c);
if(calcWHrate(newrow) < calcWHrate(currValues))
{
//当前放置长宽比弱小于之前的,表示可以在沿用之前方式继续放入
QVector<SquarifyInputItem> temp = inputs.mid(1); //剔除inputs中第一个点
squarify(temp,newrow); //新的数组继续递归
}else
{
//当前放入的矩形没能改善布局,被丢弃,将上一次正确布局的数据布局到数组中
layoutRects(currValues);
//重新开始一行布局 当前数组放入空,那么calcWHrate则会返回最大值
squarify(inputs,QVector<SquarifyInputItem>());
}
}
void Squarify::layoutRects(const QVector<SquarifyInputItem> &preValues)
{
double fixedW = m_emptyRc.width();
bool bhorizon = false; //剩余区域方向 表示剩余矩形是水平样式还是垂直样式
if(m_emptyRc.width() > m_emptyRc.height())
{
bhorizon = true; //当前剩余区域是水平放置
fixedW = m_emptyRc.height();
}
double allAcreage = 0;
for(auto r:preValues) //当前布局区域总的面积
{
allAcreage += r.value;
}
double oneSide = allAcreage/fixedW; //总面积除以固定等于另一边
QRectF rc = m_emptyRc;
QRectF emptyRc;
double tempV = DBL_MAX,tempV2 = DBL_MAX;
SquarifyOutputItem item;
for(int i= preValues.size()-1;i >= 0;i--)
{
if(bhorizon)
{
//剩余区域水平
if(tempV2 == DBL_MAX)
{
tempV2 = m_emptyRc.bottom();
emptyRc = m_emptyRc;
emptyRc.setLeft(m_emptyRc.left() + oneSide);
}
tempV = preValues[i].value/oneSide;
rc.setLeft(m_emptyRc.left());
rc.setTop(tempV2-tempV);
rc.setWidth(oneSide);
rc.setHeight(tempV);
item.rc = rc;
item.userData = preValues[i].userData;
m_rects.push_back(item);
tempV2 -= tempV;
}else
{
if(tempV2 == DBL_MAX)
{
emptyRc = m_emptyRc;
emptyRc.setBottom(m_emptyRc.bottom()-oneSide);
tempV2 = m_emptyRc.right();
}
tempV = preValues[i].value/oneSide;
rc.setLeft(tempV2-tempV);
rc.setTop(m_emptyRc.bottom()-oneSide);
rc.setWidth(tempV);
rc.setHeight(oneSide);
item.rc = rc;
item.userData = preValues[i].userData;
m_rects.push_back(item);
tempV2 -= tempV;
}
}
m_emptyRc = emptyRc;
}
double Squarify::calcWHrate(const QVector<SquarifyInputItem> &currValues)
{
if(currValues.isEmpty())
return DBL_MAX;
double fixedW = m_emptyRc.width();
if(m_emptyRc.width() > m_emptyRc.height())
{
fixedW = m_emptyRc.height();
}
double allAcreage = 0;
for(auto r:currValues) //总面积
{
allAcreage += r.value;
}
double retV = 0;
double side = 0;
for(auto r:currValues)
{
side = allAcreage/fixedW; //总面积除一边得到另外一边
//长宽比求最大值 当前面积除以边长(一边)比上另外一边 r.value/side/side ==》r.value/side:side
retV = std::max(retV,std::max(r.value/side/side,side/(r.value/side)));
}
return retV;
}
//绘制函数里面测试
void SquarifiedWnd::paintEvent(QPaintEvent *event)
{
QWidget::paintEvent(event);
QPainter dc(this);
//算法3///
// 测试数据 确保是有序的
SquarifyInputItem item;
QVector<SquarifyInputItem> dTemp;
item.value = 6;
dTemp.push_back(item);
item.value = 6;
dTemp.push_back(item);
item.value = 4;
dTemp.push_back(item);
item.value = 3;
dTemp.push_back(item);
item.value = 2;
dTemp.push_back(item);
item.value = 1;
dTemp.push_back(item);
// treemap保存每个矩形左上角和右下角的位置
QRectF rc = this->rect();
rc.adjust(20,20,-20,-20);
QVector<SquarifyOutputItem> treemap = Squarify().squarifyMath(rc,dTemp);
for(int i = 0; i<treemap.size(); ++i)
{
// 计算矩形的长、宽、面积
dc.drawRect(treemap[i].rc);
}
}
以上是算法,以下是是最终UI实现的效果:
QQ:710534964 如有问题可以联系,说明是Squarified算法