
一、功能:
1.增加节点
2.删除节点
3.上下移动节点
4.保存
5.画布可放大缩小
6.单击节点选中,双击节点折叠
二、知识点:
1.D3.js(官网:D3.js - Data-Driven Documents)(我是小白)
echarts和D3的作用相似,那他们有什么不同之处呢?我觉得他们之间最大的不同之处就在于echarts它是使用canvas来绘制图形的,而D3是通过Svg来绘制图形的。这两者的不同之处在于,svg可以操作dom支持事件处理器,想要实现某个操作,直接调用相关的方法实现效果就行,他那个里面存在链式语法,这个和 jQuery 的链式调用差不多,简单易读。canvas不支持事件处理器所以只能展示数据,而不能修改。
D3使用svg绘制图形,echarts使用canvas绘制图形
D3兼容IE9及以上主流浏览器,echarts兼容IE6及以上主流浏览器
D3使用灵活,学习成本大,echarts封装好的,使用简单,不够灵活
2.广度遍历的方式
英文缩写为BFS即Breadth FirstSearch。其过程检验来说是对每一层节点依次访问,访问完一层进 入下一层。对于上面的例子来说,广度优先遍历的 结果是:1,2,3,4,5,6..(假设每层节点从上向下访问)。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>D3 tree</title>
</head>
<style>
.node circle {
width: 39px;
height: 39px;
fill: #92BFFF;
stroke: #92BFFF;
stroke-width: 1.5px;
}
.circleStyle{
width: 40px;
height: 40px;
background-color: #92BFFF;
}
.node text {
font-size: 16px;
}
.link {
fill: none;
stroke: #3b8cff;
stroke-width: 1.5px;
}
.tree{
width: 1500px;
height: 800px;
margin: 0 auto;
background-color: #eeeaea;
}
.tree svg{
width: 100%;
height: 100%;
}
.node img{
width: 20px;
height: 20px;
display: block;
}
.button{
margin-bottom: 20px;
width: 500px;
display: flex;
justify-content: space-between;
}
.button div{
padding: 5px 20px;
height: 35px;
line-height: 35px;
cursor: pointer;
color: #fff;
background-color: #3b8cff;
border-radius: 5px;
font-size: 16px;
display: inline;
text-align: center;
}
</style>
<body>
<div class="button">
<div class="delete">删除节点</div>
<div class="add">增加节点</div>
<div class="move_up">上移</div>
<div class="move_down">下移</div>
<div class="preservation_btn">保存</div>
</div>
<div class="tree" id="tree"></div>
</body>
<script src="../js/jquery-3.1.1.js"></script>
<script src="https://d3js.org/d3.v3.min.js"></script>
<script>
var data = [
{
"name": "二级开关1",
'delayed':'2', //延时
"children": [
{
"name": "分支1",
'delayed':'0',
"children":[
{"name": "开关1",'delayed':'0'},
{"name": "开关2",'delayed':'0'},
{"name": "开关3",'delayed':'0'},
]
}
]
},
{
"name": "二级开关2",
'delayed':'2',
"children":[
{"name": "分支2",
'delayed':'0',},
{"name": "分支3",
'delayed':'0',},
]
},
{
"name": "二级开关3",
'delayed':'3', //延时
"children":[
{"name": "分支2",'delayed':'9',},
{"name": "分支3",'delayed':'8',},
]
},
];
var treeData = [
{
"name":"开始",
"id":"-1",
"children":data
}
];
var margin = [20, 120, 20, 120],
width = document.getElementById("tree").offsetWidth,
height = document.getElementById("tree").offsetHeight;
var i = 0,
duration = 750,
root,
lastNum = 0;
var tree = d3.layout.tree() //创建d3树布局,并定义画布大小
.size([height, width])
var diagonal = d3.svg.diagonal()
.projection(function(d) { return [d.y, d.x]; });
var zoom = d3.behavior.zoom().scaleExtent([0.1, 100]).on("zoom", zoomed);//添加放大缩小事件
var svg = d3.select("body").select("#tree").append("svg")
.call(zoom).on("dblclick.zoom", null)//给svg绑定zoom事件
.append("g")
.attr("transform", "translate(" + margin[3] + "," + margin[0] + ")")
.call(zoom).on("dblclick.zoom", null)//给g绑定zoom事件
.append("g");
function zoomed() { //根据操作更改svg的大小位置
svg.attr("transform",
"translate(" + zoom.translate() + ")" +
"scale(" + zoom.scale() + ")"
);
}
var arr = [];
var domInfo;
root = treeData[0];
root.x0 = height / 2;
root.y0 = 0;
update(root); //根据数据,创建节点以及连线
// 传入一个数组
// 按照特定方式格式化
function sortArr(arr, str) {
var _arr = [],
_t = [],
_tmp; // 临时的变量
// 按照特定的参数将数组排序将具有相同值得排在一起
arr = arr.sort(function(a, b) {
var s = a[str],
t = b[str];
return s - t;
});
if (arr.length ){
_tmp = arr[0][str];
}
// 将相同类别的对象添加到统一个数组
for (var i in arr) {
if ( arr[i][str] === _tmp ){
_t.push( arr[i] );
} else {
_tmp = arr[i][str];
_arr.push( _t );
_t = [arr[i]];
}
}
// 将最后的内容推出新数组
_arr.push( _t );
return _arr;
}
function update(source) {
// 计算新树的布局
var nodes = tree.nodes(root);
var links = tree.links(nodes);
//树的广度方式遍历渲染执行顺序序号
sortArr(nodes,'depth')
var num = 0;
var prevDelayed = 0;
var prevDepth = 1;
nodes.forEach(function(d) {
// 树的深度这里树d.y。
d.y = d.depth * 180;
//数字排序顺序:自增1(1,2,3,4...)
d.order = num++;
//数字排序顺序:1.同层级排序 2.同层级延时为0时序号一样 3.不同层级递增1 (领导暂时不用了)
// if(d.id !== '-1'){
// console.log(Number(d.delayed));
// if(prevDepth === d.depth){
// if(Number(d.delayed) == 0){
// d.order = num;
// if(prevDelayed !== Number(d.delayed) ){
// d.order = num = num+1;
// }
// }else{
// if(prevDelayed === Number(d.delayed) && Number(d.delayed) === 0){
// d.order = num;
// }else{
// d.order = num = num+1;
// }
// }
// }else{
// d.order = num = num+1;
// }
// }else{
// d.order = 0;
// d.delayed = 0;
// d.depth = 0;
// }
// prevDelayed = Number(d.delayed)
// prevDepth = Number(d.depth)
});
//数据连接,根据id绑定数据,最初新点开的节点都没有id,为没有id的节点添加上ID
var node = svg.selectAll("g.node")
.data(nodes, function(d) { return d.id || (d.id = ++i); });
//点击时增加新的子节点
var nodeEnter = node.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) { return "translate(" + source.y0 + "," + source.x0 + ")"; })
.on("dblclick",click)
.on("click", function(d){
domInfo = d;
var color;
$(".text").each(function(i,item){
d3.select(item).style('fill', '#666');
d3.select(item).attr("text-decoration","none");
d3.select(item).attr("font-weight","nromal");
});
$(".rectIcon").each(function(i,item){
if(item.parentNode.id == "-1"){
color = "#42FF42"
}else{
color = "#92BFFF"
}
d3.select(item).style('fill', color);
});
var sel1 = d3.select(this.childNodes[2]);
var selIcon = d3.select(this.childNodes[0]);
sel1.style('fill', '#3b8cff');
sel1.attr("text-decoration","underline");
sel1.attr("font-weight","bold");
selIcon.style('fill', '#3B8CFF');
})
.style("cursor",'pointer')
.attr("id",function(d){
return d.id
});
nodeEnter.append("rect")
.attr("class","rectIcon")
.attr("r", 1e-6)
.attr("x",-13)
.attr("y",-13)
.attr("width",26)
.attr("height",26)
.attr("rx",50)
.style("fill", function(d){
if(d.id == '-1'){
return "#42FF42"
}else{
return "#92BFFF"
}
});
//这个是添加数字的哦。
nodeEnter.append("text")
.style("fill",'#fff')
.attr("font-size",14)
.attr('class','number')
.attr("y", 6)
.attr("text-anchor", 'middle')
.text(function(d,i) {
if(d.id == '-1'){
return 0;
}else{
return d.order;
}
});
//执行时根据状态显示图标
// nodeEnter.append("svg:image")
// .attr("xlink:href",function(d){
// // console.log(d);
// var icon = 'https://static.dwtyun.com/img/portal/pc/login/login_dwtyun_logo.png';
// if(d.depth == 3){
// icon = 'https://img-blog.csdnimg.cn/20201014180756754.png?x-oss-process=image/resize,m_fixed,h_64,w_64';
// }else if(d.depth == 2){
// icon = 'https://avatars.observableusercontent.com/avatar/c29cf4d85eece3bf402bbaffe1e260c6e5881a490c99c6a1e52a8a2f3f6fb10e?s=128';
// }
// return icon;
// })
// .attr("width",26)
// .attr("height",26)
// .attr("x",-10)
// .attr("y",-10);
var text = nodeEnter.append("text")
.attr("x", function(d) { return d.children || d._children ? -15 : 15; })
.attr('class','text')
.attr("dy", ".35em")
.style("fill",'#666')
.attr("font-size",16)
.attr("text-anchor", function(d) { return d.children || d._children ? "end" : "start"; })
.text(function(d) { return d.name; })
.style("fill-opacity", 1e-6);
//执行的时候才显示
text.selectAll("tspan")
.data(['设备连接超时'])
.enter()
.append("tspan")
.attr('class','tspan')
.attr("x", function(d) { return d.children || d._children ? -15 : 15; })
.attr("text-anchor", function(d) { return d.children || d._children ? "end" : "start"; })
.attr("dy","1.5em")
.style("fill",'#999')
.attr("font-size",10)
.text(function(d){
if(d.id == '-1'){
return '';
}else{
return '未执行';
}
});
//原有节点更新到新位置
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; });
nodeUpdate.select("text")
.text(function(d,i) {
if(d.id == '-1'){
return 0;
}else{
return d.order;
}
});
nodeUpdate.select(".text")
.attr("x", function(d) { return d.children || d._children ? -15 : 15; })
.attr("text-anchor", function(d) { return d.children || d._children ? "end" : "start"; })
.style("fill-opacity", 1)
nodeUpdate.select("tspan")
.attr("x", function(d) { return d.children || d._children ? -15 : 15; })
.attr("text-anchor", function(d) { return d.children || d._children ? "end" : "start"; })
.style("fill-opacity", 1)
//折叠节点的子节点收缩回来
var nodeExit = node.exit().transition()
.duration(duration)
.attr("transform", function(d) { return "translate(" + source.y + "," + source.x + ")"; })
.remove();
nodeExit.select(".text")
.style("fill-opacity", 1e-6);
//数据连接,根据目标节点的id绑定数据
var link = svg.selectAll("path.link")
.data(links, function(d) { return d.target.id; });
//增加新连接
link.enter().insert("path", "g")
.attr("class", "link")
.attr("d", function(d) {
var o = {x: source.x0, y: source.y0};
return diagonal({source: o, target: o});
});
//原有连接更新位置
link.transition()
.duration(duration)
.attr("d", diagonal);
//折叠的链接,收缩到源节点处
link.exit().transition()
.duration(duration)
.attr("d", function(d) {
var o = {x: source.x, y: source.y};
return diagonal({source: o, target: o});
})
.remove();
// 把旧位置存下来,用以过渡
nodes.forEach(function(d) {
d.x0 = d.x;
d.y0 = d.y;
});
}
//切换折叠与否
function click(d) {
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
update(d); //重新渲染
}
//删除节点
$(".delete").click(function(){
console.log(domInfo);
removeNode(domInfo)
});
function removeNode(d){
var target = d;
var children = [];
if(target.parent){
target.parent.children.forEach(function(child){
if (child.id != target.id){
children.push(child);
}
});
target.parent.children = children;
}
update(d)
}
$(".add").click(function(){
var d = domInfo;
var target = d;
var children = [];
var addTheseJSON = {
"name": "新增未命名",
'delayed':'0', //延时
}
if(target.children){
children = target.children;
}
if(target._children){
children = target._children;
}
if(target.children == undefined && target._children == null){
children = target.children = [];
}
children.push(addTheseJSON);
update(d);
});
$(".move_up").click(function(){
var d = domInfo;
var target = d;
var index;
if(target.parent){
var parentChild = target.parent.children;
parentChild.forEach(function(child,i){
if (child.id === target.id){
index = i;
}
});
indexUp(parentChild,index,parentChild.length);
}
update(d)
});
$(".move_down").click(function(){
var d = domInfo;
var target = d;
var index;
if(target.parent){
var parentChild = target.parent.children;
parentChild.forEach(function(child,i){
if (child.id === target.id){
index = i;
}
});
indexDown(parentChild,index,parentChild.length);
}
update(d)
});
/**
* 数组元素交换位置
* @param {array} arr 数组
* @param {number} index1 添加项目的位置
* @param {number} index2 删除项目的位置
* index1和index2分别是两个数组的索引值,即是两个要交换元素位置的索引值,如1,5就是数组中下标为1和5的两个元素交换位置
*/
function swapArray(arr, index1, index2) {
arr[index1] = arr.splice(index2, 1, arr[index1])[0];
return arr;
}
//上移 将当前数组index索引与前面一个元素互换位置,向数组前面移动一位
function indexUp(arr,index,length){
if(index!= 0){
swapArray(arr, index, index-1);
}else{
alert('已经处于置顶,无法上移');
}
}
//下移 将当前数组index索引与后面一个元素互换位置,向数组后面移动一位
function indexDown(arr,index,length){
if(index+1 != length){
swapArray(arr, index, index+1);
}else{
alert('已经处于置底,无法下移');
}
}
//保存
$(".preservation_btn").click(function(){
console.log(recursion(root));;
});
function recursion(data) {
delete data.x
delete data.y
delete data.x0
delete data.y0
delete data.depth
delete data.id
delete data.parent
if (data.children) {
data.children.forEach(item => {
recursion(item)
});
}
return data
}
</script>
</html>