關(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é)束 破碗, 求贊 ︿( ̄︶ ̄)︿