squarified算法

 

 

其他参考资料:

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算法

 

 

 

 

 

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值