公司项目有个功能要实现树形结构的展示,类似于组织架构的那种。
想了下几种方案,
想了一下,感觉第一种简单一些, 用表格的话可能不需要计算繁琐的定位,top,left什么的,不过用表格的坏处就是可扩展性差了些
粗略一想,大概设计方案如下:
想玩就像立马码字实现,代码还没敲问题来了,树形结构后面的分叉是动态的,该怎么实现呢?
于是我在纸上尝试画出图形,从顶层开始画,第一层一个节点 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 }
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 }
js主要用到jquery库,css js 都写的比较毛糙,基础没打好呀,欢迎大神指正~
PS:写个博客好费时间,真是佩服那些大神写一系列的文章,给你们点赞,写了两小时我快吐了~
原文:https://www.cnblogs.com/xiaopcheche/p/12452536.html