描述
有时候我们根据需求需要canvas或svg画一些效果图,本案例是画企业树形图,希望各位大大支持与意见。不用废话先上图。
效果图
github地址
https://github.com/callBackFnYDG/EnterpriseStructureTree
案例实现原理
首先我们定义出,节点宽度,节点高度,节点间距离,父子节点间距离
var nodeWidth = 100; // 节点宽度
var nodeHeight = 50; // 节点高度
var heightSpace = 200; // 父子节点间距离
var nodeSpace = 20; // 节点间距离
var treeSpace = nodeWidth + nodeSpace; // 节点总宽度
以上我们可以得到树最底层没有子节点的宽高节点间距,但是我们往上走的话父节点如果还用这个宽度计算节点位置 那么有可能会出现某些子节点叠加,解决方案,我们用回调函数从最底层计算宽度直至最顶层。
// 计算宽度
var traverse = function(node) {
var nodeWidth = 0
var childNodes = node.children ? node.children : [];
var leftWidth = 0
var rightWidth = 0
for (var i = 0; i < childNodes.length; i++) {
var data = traverse(childNodes[i]);
if (!data.nodeWidth) {
data.nodeWidth = treeSpace
}
if (!data.leftWidth) {
data.leftWidth = treeSpace / 2
}
if (!data.rightWidth) {
data.rightWidth = treeSpace / 2
}
if (i % 2 == 1) {
leftWidth += data.nodeWidth
} else {
rightWidth += data.nodeWidth
}
nodeWidth += data.nodeWidth
}
node.nodeWidth = nodeWidth
node.leftWidth = leftWidth
node.rightWidth = rightWidth
return node
};
以上出现 leftWidth 和 rightWidth 因为我计算节点位置的时候是根据上一个节点位置计算的,第一个节点位置是根据当前兄弟节点是否是双数,是->第一二节点特殊(与父节点x轴相差 leftWidth,或者 rightWidth),否-> 第一个节点特殊(与父节点x轴一致)。之后的节点根据前一个节点计算位置 公式大概为
// 左侧节点=>计算方向为水平方向 x
Xn = X(n-2) - ( X(n-2).leftWidth + Xn.rightWidth)
// 右侧节点=>计算方向为水平方向 x
Xn = X(n-2) + ( X(n-2).rightWidth + Xn.leftWidth)
// 计算位置
var position = function(node) {
var pos = node.position ? node.position : [0, 0];
var childNodes = node.children ? node.children : [];
var bol = childNodes.length % 2 == 1
var min = 0
var max = 0
for (var i = 0; i < childNodes.length; i++) {
var vector = i % 2 == 1 ? -1 : 1;
var y = pos[1] + heightSpace
// 单数情况下 第一项特殊
if (bol && i == 0) {
childNodes[i].position = [pos[0], y]
}
// 双数情况下 前两项特殊
else if (!bol && (i == 0 || i == 1)) {
var LEFT_OR_RIGHT_WIDTH = i == 1 ? childNodes[i].rightWidth : childNodes[i].leftWidth
childNodes[i].position = [pos[0] + vector * LEFT_OR_RIGHT_WIDTH , y]
}
// 其它情况下都一样
else {
var index = i < 2 ? 0 : i - 2;
var n = childNodes[index]
var w = i % 2 == 1 ? (n.leftWidth + childNodes[i].rightWidth) : (n.rightWidth + childNodes[i].leftWidth);
var x = n.position[0] + w * vector;
childNodes[i].position = [x, y]
}
// 计算中间那条线的距离
if (i == 0) {
max = childNodes[i].position[0]
min = childNodes[i].position[0]
}
if (childNodes[i].position[0] > max) max = childNodes[i].position[0]
if (childNodes[i].position[0] < min) min = childNodes[i].position[0]
// 父节点位置
childNodes[i].parentPosition = pos
// 父节点
childNodes[i].parentNode = node
var data = position(childNodes[i]);
}
node.lineWidth = [min, max]
node.position = pos
nodes.push(node)
return node
}
有了每个节点位置节点所占用宽度之后剩下的就只有绘图了,可以根据这个计算得到的画布上的节点画自己想要的线和节点的样式。
特别说明
1,如果需要祖先元素节点计算方式和子元素节点计算方式一致
2,计算时先计算的节点宽度,left宽度,right宽度后计算节点二维坐标位置,因此用到两次循环,各位大大有没有想法可以用一个循环解决
3,本案例一开始就没有考虑用d3,hcharts,echarts。