js 樹(shù)形結(jié)構(gòu),以及數(shù)據(jù)層級(jí)匯總

關(guān)鍵點(diǎn):遞歸,樹(shù)形結(jié)構(gòu)層級(jí)構(gòu)造,樹(shù)形結(jié)構(gòu)層級(jí)匯總,樹(shù)形結(jié)構(gòu)去0值

樹(shù)形結(jié)構(gòu)構(gòu)造

??因?yàn)橐玫絜charts 的旭日?qǐng)D,需要的數(shù)據(jù)的樣子大致如下:

[
    {
        name:"z",
        value : 10,
        children:[
            {
                name:"zz",
                value :5,
                children:[
                    ...
                ]
            },{
                ...
            }
        ]
    },{
        ...
    }
]

??數(shù)據(jù)庫(kù)中存儲(chǔ)的信息是一個(gè)標(biāo)準(zhǔn)的樹(shù)結(jié)構(gòu),大致字段如下:
??ID,PID,NAME,VALUE

??解決的思路是用遞歸,具體代碼如下:

// 加載 樹(shù)狀數(shù)據(jù),進(jìn)行數(shù)據(jù)匯總
loadData: function (data, j, root) {
    // data 為后端取數(shù)的所有數(shù)據(jù),j 為根節(jié)點(diǎn), root 為每個(gè)節(jié)點(diǎn)的 id ,如果是最上級(jí)節(jié)點(diǎn), 則為null,data 為無(wú)序狀態(tài)
    var that = this;
    for (var i = 0; i < data.length; i++) {
        // 循環(huán)遍歷所有節(jié)點(diǎn),看父id 是否 等于 root ,如果是,說(shuō)明此節(jié)點(diǎn)為 j 的children,繼續(xù)
        if (data[i].pid == root) {
            var id = data[i].id;
            var pid = data[i].pid;
            var value = data[i].value || 0;
            var name = data[i].name || '';

            var temobj = {};          
            temobj.name = name;
            temobj.id = id;           
            temobj.pid = pid;
            temobj.value = value;            
            j.children = j.children || [];
            j.children.push(temobj);
        }
    }

    // 獲取第一層級(jí)所有節(jié)點(diǎn)以后,繼續(xù)循環(huán) 所有子節(jié)點(diǎn),進(jìn)行遞歸
    for (var i = 0; j.children && i < j.children.length; i++) {
        that.loadData(data, j.children[i], j.children[i].id);       
    }
},

exec:function(){
    var that = this;
    // 調(diào)用:
    var j = {};
    var root = "";
    // 服務(wù)端返回值
    var data = [];
    var res = that.loadData(data,j,root);
    return res.children;
}

進(jìn)階一:為每一層級(jí)加上level 標(biāo)識(shí)

??因?yàn)槟承┰?需要在匯總的樹(shù)上加上每一層級(jí)的level
??思路:樹(shù)形結(jié)構(gòu)本身就是分層的,遞歸的邏輯就是先遍歷所有祖先節(jié)點(diǎn),在遍歷子節(jié)點(diǎn)

  • 先遍歷第一層所有節(jié)點(diǎn)
  • 在遍歷第一層第一個(gè)的所有子節(jié)點(diǎn)
  • 在遍歷第一層第一個(gè)子節(jié)點(diǎn)的第一個(gè)子節(jié)點(diǎn)
  • 遍歷完成,遍歷第一層第二個(gè)子節(jié)點(diǎn)
示意圖

??所以可以考慮加一個(gè)level 字段,遞歸進(jìn)入到下一層的時(shí)候,level +1, 當(dāng)全部遞歸完成后返回的時(shí)候,level -1 具體代碼如下:

var testpage = function () {
    this.render();
}

testpage.prototype = {

    consts: {
        level: 0,
    },

    // 加載 樹(shù)狀數(shù)據(jù),進(jìn)行數(shù)據(jù)匯總
    loadData: function (data, j, root) {
        // data 為后端取數(shù)的所有數(shù)據(jù),j 為根節(jié)點(diǎn), root 為每個(gè)節(jié)點(diǎn)的 id ,如果是最上級(jí)節(jié)點(diǎn), 則為null,data 為無(wú)序狀態(tài)
        var that = this;
        for (var i = 0; i < data.length; i++) {
            // 循環(huán)遍歷所有節(jié)點(diǎn),看父id 是否 等于 root ,如果是,說(shuō)明此節(jié)點(diǎn)為 j 的children,繼續(xù)
            if (data[i].pid == root) {
                var id = data[i].id;
                var pid = data[i].pid;
                var value = data[i].value || 0;
                var name = data[i].name || '';

                var temobj = {};
                temobj.name = name;
                temobj.id = id;
                temobj.level = that.consts.level;
                temobj.pid = pid;
                temobj.value = value;
                j.children = j.children || [];
                j.children.push(temobj);
            }
        }

        // 獲取第一層級(jí)所有節(jié)點(diǎn)以后,繼續(xù)循環(huán) 所有子節(jié)點(diǎn),進(jìn)行遞歸
        for (var i = 0; j.children && i < j.children.length; i++) {
            that.consts.level ++;
            that.loadData(data, j.children[i], j.children[i].id);
            that.consts.level --;
        }
    },

    render:function(){
        var that = this;
        // 調(diào)用:
        var j = {};
        var root = "";
        // 服務(wù)端返回值
        var data = [];
        var res = that.loadData(data,j,root);
        return res.children;
    }
}

return new testpage();

進(jìn)階二:循環(huán)匯總子節(jié)點(diǎn)的數(shù)據(jù)到父節(jié)點(diǎn)上

??數(shù)據(jù)庫(kù)中的數(shù)據(jù)所有數(shù)據(jù)都在子節(jié)點(diǎn)上,父節(jié)點(diǎn)的值都是0,因需要前端過(guò)濾,所以數(shù)據(jù)的匯總只能在前端進(jìn)行.
??思路:上一個(gè)方法已經(jīng)在每個(gè)節(jié)點(diǎn)中加入了level 信息,那自然可以想到,用同樣的方式,把每個(gè)子集的所有數(shù)據(jù),匯總到上一級(jí)所有數(shù)據(jù).
??之前計(jì)算level 的時(shí)候,可以發(fā)現(xiàn),遞歸的邏輯圖如下:


示意圖

那么,可以利用level 字段 以及 棧,進(jìn)行數(shù)據(jù)的匯總,代碼如下

var testpage = function () {
    this.render();
}

testpage.prototype = {

    consts: {
        level: 0,
        sum:0,
        sumstack : []
    },

    // 加載 樹(shù)狀數(shù)據(jù),進(jìn)行數(shù)據(jù)匯總
    loadData: function (data, j, root) {
        // data 為后端取數(shù)的所有數(shù)據(jù),j 為根節(jié)點(diǎn), root 為每個(gè)節(jié)點(diǎn)的 id ,如果是最上級(jí)節(jié)點(diǎn), 則為null,data 為無(wú)序狀態(tài)
        var that = this;
        for (var i = 0; i < data.length; i++) {
            // 循環(huán)遍歷所有節(jié)點(diǎn),看父id 是否 等于 root ,如果是,說(shuō)明此節(jié)點(diǎn)為 j 的children,繼續(xù)
            if (data[i].pid == root) {
                var id = data[i].id;
                var pid = data[i].pid;
                var value = data[i].value || 0;
                var name = data[i].name || '';

                var temobj = {};
                temobj.name = name;
                temobj.id = id;
                temobj.level = that.consts.level;
                temobj.pid = pid;
                temobj.value = value;
                // 這里匯總數(shù)據(jù)
                that.consts.sum += value;
                j.children = j.children || [];
                j.children.push(temobj);
            }
        }

        // 獲取第一層級(jí)所有節(jié)點(diǎn)以后,繼續(xù)循環(huán) 所有子節(jié)點(diǎn),進(jìn)行遞歸
        for (var i = 0; j.children && i < j.children.length; i++) {
            that.consts.level ++;
            // 將結(jié)果壓入棧中, 這么做的原因是,比如一個(gè)父節(jié)點(diǎn)有多個(gè)子節(jié)點(diǎn),那么,每次遍歷的時(shí)候,要先把之前的結(jié)果存起來(lái)
            that.consts.sumstack.push(that.consts.sum);
            that.consts.sum = 0;
            that.loadData(data, j.children[i], j.children[i].id);
            // 這一步的目的是,如果最底層節(jié)點(diǎn)上面有多層父節(jié)點(diǎn),那么,要把直接匯總的和 加到祖父節(jié)點(diǎn)上去,否則祖父節(jié)點(diǎn)就是 0 了.
            // 簡(jiǎn)單來(lái)說(shuō)就是 sumstack[1] += sumstack[2];  1,2, 表示level ,這里面是做了校驗(yàn),防止報(bào)錯(cuò)
            that.consts.sumstack[(that.consts.sumstack.length - 2 >= 0) && (that.consts.sumstack.length - 2) || 0] += that.consts.sumstack[(that.consts.sumstack.length - 1 >= 0) && (that.consts.sumstack.length - 1) || 0];
            // 出棧,如示意圖中的level3 返回到level2 的過(guò)程.
            j.value = (j.value || 0) + that.consts.sumstack.pop();
            that.consts.level --;
        }
    },

    render:function(){
        var that = this;
        // 調(diào)用:
        var j = {};
        var root = "";
        // 服務(wù)端返回值
        var data = [];
        var res = that.loadData(data,j,root);
        return res.children;
    }
}

return new testpage();

番外一:根據(jù) 基礎(chǔ)數(shù)據(jù)和 數(shù)據(jù)表構(gòu)造樹(shù)

??思路:基礎(chǔ)數(shù)據(jù)表 表結(jié)構(gòu): ID,PID,LEAF,NAME, 數(shù)據(jù)表表結(jié)構(gòu): NAME, 基礎(chǔ)數(shù)據(jù)表ID,... 其中,數(shù)據(jù)表存儲(chǔ)的是 基礎(chǔ)數(shù)據(jù)表中的葉子節(jié)點(diǎn), 所以前端構(gòu)造樹(shù)的時(shí)候,要根據(jù)基礎(chǔ)表 本身的樹(shù)結(jié)構(gòu),和數(shù)據(jù)表,把數(shù)據(jù)表的子節(jié)點(diǎn)數(shù)據(jù)放到 基礎(chǔ)表的相應(yīng)位置:
??做法也比較簡(jiǎn)單 ヽ(ー_ー)ノ,直接看代碼就好:


// 基礎(chǔ)表樹(shù)信息,這個(gè)是原始的結(jié)果,還沒(méi)有構(gòu)造成樹(shù)
var tree = [];
var treeinde = {};
// 記錄原始樹(shù)的id 的出現(xiàn)位置
for (var i = 0; i < tree.length; i++) {
    treeinde[tree[i].id] = i;
}

// rr 為數(shù)據(jù)表數(shù)據(jù)
// 樹(shù)的id 大概是這樣 K01, K0101 K010101 所以可以根據(jù)這個(gè)獲取層級(jí)
for (var i = 0; i < rr.length; i++) {  
    var keys = Object.keys(rr[i]);
    var values = Object.values(rr[i]);
    // 遍歷每個(gè)樹(shù)形,賦值到樹(shù)結(jié)構(gòu)上去
    for (var j = 0; j < keys.length; j++) {
        tree[treeinde[rr[i].id]][keys[j]] = values[j];
        tree[treeinde[rr[i].id]]["ifzero"] = 1;
    }
}

進(jìn)階三:去除所有的零節(jié)點(diǎn)

??第二步中匯總的數(shù)據(jù)有個(gè)缺點(diǎn),因?yàn)榭赡苡行┳庸?jié)點(diǎn) 是0(具體的原因是,數(shù)據(jù)庫(kù)只有存最葉子節(jié)點(diǎn)的數(shù)據(jù),前端需要根據(jù) 原始樹(shù)結(jié)構(gòu),以及數(shù)據(jù)表,來(lái)構(gòu)造樹(shù),所以會(huì)有0 節(jié)點(diǎn), 見(jiàn)番外),導(dǎo)致.
??思路:過(guò)濾葉子節(jié)點(diǎn)是不是0 很簡(jiǎn)單,直接判斷 isleaf(數(shù)據(jù)庫(kù)樹(shù)形結(jié)構(gòu)表存儲(chǔ)) = 1 && value = 0 continue; 但是對(duì)于根節(jié)點(diǎn)就不是很好判斷了,最簡(jiǎn)單的方式是重新遍歷一遍樹(shù),去除掉0 ,但是這樣做感覺(jué)不太好┗( ▔, ▔ )┛,于是考慮,通過(guò)兩步,過(guò)濾掉為0 的數(shù)據(jù): 第一步,過(guò)濾掉葉子節(jié)點(diǎn)為0 的,第二步,過(guò)濾掉沒(méi)有 葉子節(jié)點(diǎn)的父節(jié)點(diǎn) , 至于 某個(gè)節(jié)點(diǎn)有葉子節(jié)點(diǎn)但是葉子節(jié)點(diǎn)都為0 的情況, 不能過(guò)濾,因?yàn)槭怯袛?shù)據(jù)的

  • 第一步: 直接在循環(huán)構(gòu)造樹(shù)的時(shí)候判斷,然后過(guò)濾掉即可:
loadData: function (data, j, root) {
    var that = this;
    for (var i = 0; i < data.length; i++) {
        if (data[i].pid == root) {
            // 葉子節(jié)點(diǎn),并且是0值的時(shí)候,返回
            if (!(data[i].value) && data[i].leaf) {
                continue;
            }
...
  • 第二步:先在初始化樹(shù)的時(shí)候加上所有的子節(jié)點(diǎn)的父節(jié)點(diǎn),然后 在構(gòu)造樹(shù)的時(shí)候,判斷 當(dāng)前數(shù)據(jù)的id 是否 在父節(jié)點(diǎn)的集合中
    具體如下:
// rr 為數(shù)據(jù)表數(shù)據(jù)
// 樹(shù)的id 大概是這樣 K01, K0101 K010101 所以可以根據(jù)這個(gè)獲取層級(jí)
for (var i = 0; i < rr.length; i++) {
    // 將所有節(jié)點(diǎn)的父節(jié)點(diǎn)加到一個(gè)集合
    // 樹(shù)的id 大概是這樣 K01, K0101 K010101 所以可以根據(jù)這個(gè)獲取層級(jí)
    var levelrr = Math.floor(rr[i].id.length / 2);
    // 比如當(dāng)前節(jié)點(diǎn)id 是 K01010101,這里吧 K01 ,K0101,K010101 加入到集合
    for (var j = 1; j <= levelrr; j++) {
        that.consts.pids[j] = that.consts.pids[j] || {};
        that.consts.pids[j][rr[i].id.substring(0, j * 2 + 1)] = 1;
    }

    //原有的
    var keys = Object.keys(rr[i]);
    var values = Object.values(rr[i]);
    for (var j = 0; j < keys.length; j++) {
        tree[treeinde[rr[i].id]][keys[j]] = values[j];
        tree[treeinde[rr[i].id]]["ifzero"] = 1;
    }
}

-----------------------------------------

loadData: function (data, j, root) {
    var that = this;
    for (var i = 0; i < data.length; i++) {
        if (data[i].pid == root) {
            // 葉子節(jié)點(diǎn),并且是0值的時(shí)候,返回
            if (!(data[i].value) && data[i].leaf) {
                continue;
            }

            var breakpoint = 1;

            // 非葉子節(jié)點(diǎn),但是 查詢回來(lái)的數(shù)據(jù)沒(méi)有父節(jié)點(diǎn)是本身的
            for (var k in that.consts.pids[that.consts.level + 1]) {
                if (data[i].leaf || data[i].id.indexOf(k) != -1) {
                    breakpoint = 0;
                    break;
                }
            }

            if (breakpoint) {
                continue;
            }
.............

到此大致結(jié)束 破碗, 求贊 ︿( ̄︶ ̄)︿

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容