結構圖前端實現


公司項目有個功能要實現樹形結構的展示,類似於組織架構的那種。

想了下幾種方案,

  1.    表格內實現
  2.    div內通過絕對定位實現
  3.    canva實現

想了一下,感覺第一種簡單一些, 用表格的話可能不需要計算繁瑣的定位,top,left什么的,不過用表格的壞處就是可擴展性差了些

粗略一想,大概設計方案如下:

  •   后台組織好json格式數據返回前端,屬性結構肯定有id和pid字段
  •   結構圖前端js+css展示,內容用帶邊框的方塊展示,關系用線條。

想玩就像立馬碼字實現,代碼還沒敲問題來了,樹形結構后面的分叉是動態的,該怎么實現呢?

於是我在紙上嘗試畫出圖形,從頂層開始畫,第一層一個節點 T,第二層兩個節點T1,T2。上下距離岔開點,我一想如果T1后面有3個節點,T2后面又有3個節點。在不知道第三次的情況下我之前T1,T2上下就留了一點位置,然后畫第三層就會得到示例的圖形: 

如果上圖不算丑,那遇到第三層4個,第二個節點也有,則位置會被占用,那必然會出現更加不好看的效果:

第一張圖如果我們事先知道是 1 2 6的結構,那我們肯定能畫的好看一些,如下

這時候我意識到我這邊除了之前考慮的那兩點外,我可能還需要計算方塊位置的算法,基於我們用table來實現位置的分布,方塊位置需要如下展示:

 

那么如何得到圖中的這個位置結構呢,首先知道這么畫我們是知道后面節點的位置的,所以我決定從后往前推,T4,T5,T6是同一個父節點下的葉子節點,那T3的位置是與T5平齊的,於是得到下圖:

如此可以得到T3位置,然后根據T2,T3得到T1的位置

當然這種子節點都在最后一層是理想結果,顯示並不是所有情況都是上面那樣,比如:

當然這只是簡單的情況,還有各種更多層更加復雜的結構。

圖中做了些改進,不同層之前間隔兩列,一個用來父節點延申,一列用來分叉到子節點。另外便於畫圖,初步設計是兩個子節點中間間隔一行,這樣父節點位置剛好在一個表格的td中間。

現在來說核心的算法,不是太復雜,就是首先算好葉子節點的位置,比如圖中的T3,T4,T5,T6,T8,T9。這些都可以通過遍歷json數據來得到,通過遞歸,我們能得到前面所說幾個節點的行和列的位置。

然后計算得到T2,T7位置,最后得出T1的位置。

得到方塊內容的位置,我們就需要連線,連線的大體思路就是,td里放DIV,通過設置div的邊框顏色和div的位置來繪制,小箭頭么可以設置border得到三角形。

當然要實現並不是這么容易,需要各種折騰寫樣式,最后我是通過div然后寫個before的偽類來實現線條,after偽類來實現箭頭。

另外一般頁面大小也就那么點大,為了減少點空間我去除了兩個葉子節點中間的行,不過這也給父節點的定位造成小小的麻煩,比如T6,T7 這兩個位置是在td中,而父節點則需要向上偏半個方塊的高度。當然方塊的高度是比td矮一些的,不然T6,T7就會碰一塊了。

最后效果如下:

js代碼如下:

  1 $.fn.arch = function (rid, list) {
  2     var $tb = $(this);
  3     var maxRow = 0; //讀取葉子節點位置開始行
  4     var maxLevel = 0; //最大層級  
  5     readTree(list);
  6     initTable();
  7     drawTree();
  8     drawPath();
  9 
 10     function setLeaf(list) {
 11         for (var i = 0; i < list.length; i++) {
 12             var isleaf = 1;
 13             for (var j = 0; j < list.length; j++) {
 14                 if (list[i].id == list[j].pid) {
 15                     isleaf = 0;
 16                     break;
 17                 }
 18             }
 19             list[i].isleaf = isleaf;
 20         }
 21     }
 22 
 23     function readLeaf(list, pid, level) {
 24         maxLevel = Math.max(level, maxLevel);
 25         for (var i = 0; i < list.length; i++) {
 26             if (list[i].pid == pid) {
 27                 list[i].level = level;
 28                 if (!list[i].isleaf) {
 29                     readLeaf(list, list[i].id, level + 1);
 30                 } else {
 31                     list[i].r = maxRow;
 32                     list[i].offset = 0;
 33                     maxRow++;
 34                 }
 35             }
 36         }
 37     }
 38 
 39     function calcPosition() {
 40         var tmpList = [];
 41         for (var i = maxLevel; i > 0; i--) {
 42             for (var j = 0; j < list.length; j++) {
 43                 if (list[j].level == i && tmpList.indexOf(list[j].pid) < 0) {
 44                     var total = 0;
 45                     var count = 0;
 46                     var pindex = -1;
 47                     var isOffset = 0;
 48                     for (var k = 0; k < list.length; k++) {
 49                         if (list[k].pid == list[j].pid) {
 50                             isOffset = list[k].offset;
 51                             total += list[k].r;
 52                             count++;
 53                         } else if (list[k].id == list[j].pid) {
 54                             pindex = k;
 55                         }
 56                     }
 57                     var last = total % count;
 58 
 59                     if (pindex >= 0) {
 60                         list[pindex].r = count == 0 ? 0 : ((total - last) / count + ((last > 0) ? 1 : 0));
 61                         list[pindex].offset = last > 0 ? 1 : 0;
 62                         if (count == 1) {
 63                             list[pindex].offset = isOffset;
 64                         } else {
 65                             list[pindex].offset = last > 0 ? 1 : 0;
 66                         }
 67                     }
 68                     tmpList.push(list[j].pid);
 69                 }
 70             }
 71         }
 72     }
 73 
 74     function readTree(list) {
 75         setLeaf(list);
 76         readLeaf(list, rid, 1);
 77         calcPosition();
 78     }
 79 
 80     function drawTree() {
 81         for (var i = 0; i < list.length; i++) {
 82             var item = list[i];
 83             $tb.find("tr:eq(" + (item.r) + ")")
 84                 .find("td:eq(" + (item.level - 1) * 3 + ")>div")
 85                 .addClass("item-box" + (item.offset == 1 ? " offset" : ""))
 86                 .attr("data-pid", item.pid)
 87                 .attr("data-id", item.id)
 88                 .attr("data-offset", item.offset)
 89                 .append("<div class='item'>" + item.name + "</div>");
 90         }
 91     }
 92 
 93 
 94     function drawPath() {
 95         for (var i = 0; i < maxLevel; i++) {
 96             var targetColumn = (i - 1) * 3;
 97             var pNodes = $tb.find("tr").find("td:eq(" + targetColumn + ")>div[data-id]");
 98             for (var j = 0; j < pNodes.length; j++) {
 99                 var $pNode = $(pNodes[j]);
100                 var $pTd = $pNode.closest("td");
101                 var isPOffset = $pNode.is("[data-offset='1']");
102 
103                 var subNodes = $tb.find("tr").find("[data-pid='" + $pNode.attr("data-id") + "']");
104                 var length = subNodes.length;
105                 if (length == 0) {
106                     continue;
107                 }
108 
109                 var ptopCls = isPOffset ? "line-top" : "line-center";
110                 $pTd.next().children("div").addClass(ptopCls);
111 
112                 if (length == 1) {
113                     $(subNodes[0]).closest("td").prev().children("div").addClass(ptopCls + " arrow");
114                 } else if (length == 2) {
115                     var startIndex = $(subNodes[0]).closest("tr").index();
116                     var endIndex = $(subNodes[1]).closest("tr").index();
117 
118                     var firstOffset = $(subNodes[0]).is("[data-offset='1']");
119                     var lastOffset = $(subNodes[1]).is("[data-offset='1']");
120 
121                     if (endIndex - startIndex == 1) {
122                         if (firstOffset == lastOffset) {
123                             $(subNodes[0]).closest("td").prev().children("div").addClass("corner-top-bottom " + (firstOffset ? "" : "top-35"));
124                         } else if (firstOffset) {
125                             $(subNodes[0]).closest("td").prev().children("div").addClass("corner-top arrow");
126                             $(subNodes[1]).closest("td").prev().children("div").addClass("corner-bottom arrow top-35");
127                         }
128                     } else {
129                         $(subNodes[0]).closest("td").prev().children("div").addClass("corner-top arrow " + (firstOffset ? "" : "top-35"));
130                         !lastOffset && $(subNodes[1]).closest("td").prev().children("div").addClass("corner-bottom arrow top-35");
131 
132                         for (var m = startIndex + 1; m < endIndex; m++) {
133                             if (m == endIndex - 1 && lastOffset) {
134                                 $tb.find("tr:eq(" + m + ")").find("td:eq(" + (targetColumn + 2) + ")").children("div").addClass("corner-bottom arrow");
135                             } else {
136                                 $tb.find("tr:eq(" + m + ")").find("td:eq(" + (targetColumn + 2) + ")>div").addClass("line-left");
137                                 var currNodes = $tb.find("tr:eq(" + m + ")").find("td:eq(" + (targetColumn + 3) + ")").children(".item-box");
138                                 if (currNodes.length > 0) {
139                                     var $currNode = $(currNodes[0]);
140                                     var currOffset = $currNode.is("[data-offset='1']");
141                                     var cls = currOffset ? "line-top arrow" : "line-center arrow";
142                                     $tb.find("tr:eq(" + m + ")").find("td:eq(" + (targetColumn + 2) + ")").children("div").addClass(cls);
143                                 }
144                             }
145                         }
146                     }
147                 } else {
148                     var $firstNode = $(subNodes[0]);
149                     var $lastNode = $(subNodes[length - 1]);
150 
151                     var firstOffset = $firstNode.is("[data-offset='1']");
152                     $firstNode.closest("td").prev().children("div").addClass("corner-top arrow " + (firstOffset ? "" : "top-35"));
153 
154                     var lastOffset = $lastNode.is("[data-offset='1']");
155                     !lastOffset && $(subNodes[length - 1]).closest("td").prev().children("div").addClass("corner-bottom arrow top-35");
156 
157                     var startIndex = $firstNode.closest("tr").index();
158                     var endIndex = $lastNode.closest("tr").index();
159                     for (var m = startIndex + 1; m < endIndex; m++) {
160                         if (m == endIndex - 1 && lastOffset) {
161                             $tb.find("tr:eq(" + m + ")").find("td:eq(" + (targetColumn + 2) + ")").children("div").addClass("corner-bottom arrow");
162                         } else {
163                             $tb.find("tr:eq(" + m + ")").find("td:eq(" + (targetColumn + 2) + ")>div").addClass("line-left");
164                             var currNodes = $tb.find("tr:eq(" + m + ")").find("td:eq(" + (targetColumn + 3) + ")").children(".item-box");
165                             if (currNodes.length > 0) {
166                                 var $currNode = $(currNodes[0]);
167                                 var currOffset = $currNode.is("[data-offset='1']");
168                                 var cls = currOffset ? "line-top arrow" : "line-center arrow";
169                                 $tb.find("tr:eq(" + m + ")").find("td:eq(" + (targetColumn + 2) + ")").children("div").addClass(cls);
170                             }
171                         }
172                     }
173                 }
174             }
175         }
176     }
177 
178     function initTable() {
179         var rowCount = maxRow;
180         var columnCount = maxLevel * 3 - 2;
181         createTable(rowCount, columnCount);
182     }
183 
184     function createTable(r, c) {
185         for (var i = 0; i < r; i++) {
186             var $tr = $("<tr class='item-row'></tr>");
187             for (var j = 0; j < c; j++) {
188                 var last = j % 3;
189                 var cls = "";
190                 if (last == 0) {
191                     cls = "td-item";
192                 } else if (last == 1) {
193                     cls = "td-line";
194                 } else if (last == 2) {
195                     cls = "td-arrow";
196                 }
197                 $tr.append("<td class='" + cls + "'><div></div></td>");
198                 //$tr.append("<td " + (isitem ? "data-item='1'" : "width='50px'") + "><div class='" + (isitem ? "" : "line-item") + "'><div>" + (isitem ? "</div></div>" : "") + "</td>");
199             }
200             $tb.append($tr);
201         }
202     }
203 
204 }
View Code

 

css如下

  1 .item-box {
  2     position: absolute;
  3     top: 3px;
  4     height: 100%;
  5 }
  6 
  7     .item-box.offset {
  8         top: -35px;
  9     }
 10 
 11     .item-box .item {
 12         background-color: #c1dcfc;
 13         border: 2px solid #4499D6;
 14         border-radius: 10px;
 15         width: auto;
 16         height: 100%;
 17     }
 18 
 19     .item-box .item {
 20         height: 70px;
 21     }
 22 
 23 .line-item {
 24 }
 25 
 26 .line-left {
 27     border-left: 1px solid #4499D6;
 28 }
 29 
 30 .line-center:before {
 31     content: ' ';
 32     display: inline-block;
 33     width: 100%;
 34     border-bottom: 1px solid #4499D6;
 35     position: absolute;
 36     top: 50%;
 37 }
 38 
 39 .line-center.arrow:after {
 40     top: 36px;
 41 }
 42 
 43 .line-top:before {
 44     content: ' ';
 45     display: inline-block;
 46     width: 100%;
 47     border-bottom: 1px solid #4499D6;
 48     position: absolute;
 49 }
 50 
 51 .line-top.arrow:after {
 52     top: -5px;
 53 }
 54 
 55 .corner-top:before {
 56     content: ' ';
 57     width: 100%;
 58     height: 100%;
 59     border-top: 1px solid #4499D6;
 60     border-left: 1px solid #4499D6;
 61     border-top-left-radius: 10px;
 62     display: inline-block;
 63     position: absolute;
 64 }
 65 
 66 .corner-top.top-35:before {
 67     top: 40px;
 68 }
 69 
 70 .corner-top.arrow:after {
 71     top: -5px;
 72 }
 73 
 74 .corner-top.arrow.top-35:after {
 75     top: 35px;
 76 }
 77 
 78 
 79 
 80 .corner-bottom:before {
 81     content: ' ';
 82     width: 100%;
 83     height: 100%;
 84     border-bottom: 1px solid #4499D6;
 85     border-left: 1px solid #4499D6;
 86     border-bottom-left-radius: 10px;
 87     display: inline-block;
 88     position: absolute;
 89 }
 90 
 91 .corner-bottom.top-35:before {
 92     top: -40px;
 93 }
 94 
 95 .corner-bottom.arrow:after {
 96     bottom: -5px;
 97 }
 98 
 99 .corner-bottom.arrow.top-35:after {
100     bottom: 35px;
101 }
102 
103 .corner-top-bottom {
104     border-top: 1px solid #4499D6;
105     border-left: 1px solid #4499D6;
106     border-bottom: 1px solid #4499D6;
107     border-top-left-radius: 10px;
108     border-bottom-left-radius: 10px;
109     position: relative;
110 }
111 
112     .corner-top-bottom.top-35 {
113         top: 40px;
114     }
115 
116     .corner-top-bottom::before {
117         content: " ";
118         width: 0px;
119         height: 0px;
120         border-left: 10px;
121         border-right: 0px;
122         border-top: 5px;
123         border-bottom: 5px;
124         border-style: solid;
125         border-color: transparent transparent transparent #4499D6;
126         position: absolute;
127         right: -1px;
128         bottom: -5px;
129     }
130 
131     .arrow::after, .corner-top-bottom::after {
132         content: " ";
133         width: 0px;
134         height: 0px;
135         border-left: 10px;
136         border-right: 0px;
137         border-top: 5px;
138         border-bottom: 5px;
139         border-style: solid;
140         border-color: transparent transparent transparent #4499D6;
141         position: absolute;
142         right: -1px;
143     }
144 
145     .corner-top-bottom::after {
146         top: -5px;
147     }
148 
149 
150 .archtable {
151     border-spacing: 0px;
152 }
153 
154 
155     .archtable td {
156         position: relative;
157     }
158 
159         .archtable td > div { 
160             width:100%;
161             height: 100%;
162         }
163 
164     .archtable tr.item-row td {
165         height: 80px;
166     }
167 
168     .archtable tr:first-child,.archtable tr:last-child {
169         height: 40px;
170     }
171 
172     .archtable td.td-item {
173         min-width: 140px;
174     }
175 
176     .archtable td.td-line {
177         min-width: 30px;
178     }
179 
180     .archtable td.td-arrow {
181         min-width: 50px;
182     }
View Code

js主要用到jquery庫,css js 都寫的比較毛糙,基礎沒打好呀,歡迎大神指正~

PS:寫個博客好費時間,真是佩服那些大神寫一系列的文章,給你們點贊,寫了兩小時我快吐了~

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM