Squarified Treemaps 論文演算法復現

Zih_An發表於2020-11-28

Squarified Treemaps

復現了論文Squarified Treemaps的演算法,將層次結構視覺化為寬高比適宜的矩形劃分圖
GitHub完整程式碼

實驗環境

  • windows10
  • visual studio 2017
  • opengl: GLFW
  • c++

視覺化效果

使用論文中的測試資料

測試資料同論文Squarified Treemaps中樣例(按100的比例擴大),結果標註面積如圖所示(由於本次實現座標從左上角開始計算,因此行4 3和行2 2 1位置不同,但沒有任何影響)

  • 資料:vector<double> areas = { 60000,60000,40000,30000,20000,20000,10000 };
  • 空間尺寸:WIDTH = 600, HEIGHT = 400
Squarified Treemaps 論文演算法復現

實現效果

Squarified Treemaps 論文演算法復現

論文原圖

交換測試資料順序進行測試

為驗證矩形面積順序對視覺化效果的影響,將上述測試資料順序進行調整並檢視效果

  • 資料:{ 4,6,6,3,2,2,1 };
    Squarified Treemaps 論文演算法復現

  • 資料:{ 4,6,2,3,2,6,1 };
    Squarified Treemaps 論文演算法復現

視覺化思路

影像的繪製

在演算法layoutrow()時存下當前行矩形的資訊,包括4個座標點,在GLFW的主迴圈中使用display展示所有的矩形

演算法涉及的函式原型及資料結構

函式原型

  • void squarify(vector<double> children, vector<double> row, double w);
    • children 為還需要安置的矩形面積
    • row為當前行已安置的矩形面積
    • w為當前正在安置位置的大矩形的寬(短邊長)
  • double worst(vector<double> R, double w);
    • 返回值:當前矩形的最差長寬比(與1之差絕對值的最大值)
    • R為矩形面積列表
    • w為當前正在安置位置的大矩形的寬(短邊長)
  • void layoutrow(vector<double> R, double w);
    • R為矩形面積列表
    • w為當前正在安置位置的大矩形的寬(短邊長)
  • double width(vector<double> R, int w);
    • 返回值:當前空餘面積的短邊,使用全域性變數RwidthRheight儲存空餘矩形資訊,返回二者之間的最小值
    • R為矩形面積列表
    • w為當前正在安置位置的大矩形的寬(短邊長)

資料結構

  • 矩形:存放4個頂點座標
struct Rectangle {
    double x1, y1, x2, y2, x3, y3, x4, y4;
};

技術實現

squarify

void squarify(vector<double> children, vector<double> row, double w) {
    if (w <= 0) return;
    if (children.empty()) {
        if(!row.empty()) layoutrow(row, w);  // output current row
        return;
    }
    // 
    double c = children[0];
    vector<double> newrow = row;  // newrow
    newrow.push_back(c);

    if (worst(row, w) >= worst(newrow, w)) {  // can be placed in this row
        //cout << " add: " << c << endl;
        vector<double> tmp(children.begin() + 1, children.end());
        squarify(tmp, newrow, w);
    }
    else {  // placed in a empty new row
        layoutrow(row, w);  // output current row
        squarify(children, {}, width(row, w));
    }
}

worst

double worst(vector<double> R, double w) {
    if (R.empty()) return INF;
    double rmx = 0, rmn = INF, s = 0;
    for (auto r : R) {
        s += r;
        if (r > rmx) rmx = r;
        if (r < rmn) rmn = r;
    }
    double pw = pow(w, 2), sw = pow(s, 2);
    double res = max(pw*rmx / sw, sw / (pw*rmn));
    return max(pw*rmx / sw, sw / (pw*rmn));
}

layoutrow

void layoutrow(vector<double> R, double w) {
    double lx = WIDTH - Rwidth + (_WIDTH-WIDTH)/2., 
        ly = HEIGHT - Rheight + (_HEIGHT - HEIGHT) / 2.;  // left-top
    
    int direction;  // 0: horizontal;  1: vertical

    // refresh Rwidth, Rheight
    double sum = 0;
    for (auto r : R)
        sum += r;
    double ext = sum / w;
    if (abs(w - Rwidth) <= 1e-6) {
        Rheight -= ext;
        direction = 0;
    }
    else {
        Rwidth -= ext;
        direction = 1;
    }

    // store
    for (auto r : R) {
        if (direction == 0) {
            double hh = ext, ww = r / ext;
            rects.emplace_back(
                transx(lx), transy(ly),
                transx(lx+ww), transy(ly),
                transx(lx+ww), transy(ly+hh),
                transx(lx), transy(ly+hh)
            );
            // refresh
            lx += ww;
        }
        else {
            double ww = ext, hh = r / ext;
            rects.emplace_back(
                transx(lx), transy(ly),
                transx(lx + ww), transy(ly),
                transx(lx + ww), transy(ly + hh),
                transx(lx), transy(ly + hh)
            );
            // refresh
            ly += hh;
        }
    }
    
}

width

double worst(vector<double> R, double w) {
    if (R.empty()) return INF;
    double rmx = 0, rmn = INF, s = 0;
    for (auto r : R) {
        s += r;
        if (r > rmx) rmx = r;
        if (r < rmn) rmn = r;
    }
    double pw = pow(w, 2), sw = pow(s, 2);
    double res = max(pw*rmx / sw, sw / (pw*rmn));
    return max(pw*rmx / sw, sw / (pw*rmn));
}

display

void display(Color c1 = RED, Color c2 = YELLOW) {
    for (Rectangle rect : rects) {
        // fill in color
        glLineWidth(3);
        glBegin(GL_POLYGON);
        glColor3f(c2.r, c2.g, c2.b);
        glVertex2f(rect.x1, rect.y1);
        glVertex2f(rect.x2, rect.y2);
        glVertex2f(rect.x3, rect.y3);
        glVertex2f(rect.x4, rect.y4);
        glEnd();

        // draw the border
        glBegin(GL_LINE_LOOP);
        glColor3f(c1.r, c1.g, c1.b);
        glVertex2f(rect.x1, rect.y1);
        glVertex2f(rect.x2, rect.y2);
        glVertex2f(rect.x3, rect.y3);
        glVertex2f(rect.x4, rect.y4);
        glEnd();
    }
}

總結與思考

通過本次實驗對論文Squarified Treemaps的演算法復現,我更深刻的明白了寬高比(aspect ratio)≈1在視覺化中有更好的效果,體會到了矩形面積順序對於treemap展示的影響,同時,也對該演算法展示層級結構的不明顯理解更加深刻。

相關文章