简介
显而易见,树状布局就是我们通常所理解的数据结构中的树,由一个根节点向外展开,与力导向图的数据结构模型其实是一致的,只是展示形式略有差别,用树状结构展示看起来更加简洁清晰一点。
数据
var dataset = {
name: "中国",
children: [
{
name: "浙江",
children: [
{ name: "杭州", value: 100 },
{ name: "宁波", value: 100 },
{ name: "温州", value: 100 },
{ name: "绍兴", value: 100 }
]
},
{
name: "广西",
children: [
{
name: "桂林",
children: [
{ name: "秀峰区", value: 100 },
{ name: "叠彩区", value: 100 },
{ name: "象山区", value: 100 },
{ name: "七星区", value: 100 }
]
},
{ name: "南宁", value: 100 },
{ name: "柳州", value: 100 },
{ name: "防城港", value: 100 }
]
},
{
name: "黑龙江",
children: [
{ name: "哈尔滨", value: 100 },
{ name: "齐齐哈尔", value: 100 },
{ name: "牡丹江", value: 100 },
{ name: "大庆", value: 100 }
]
},
{
name: "新疆",
children:
[
{ name: "乌鲁木齐" },
{ name: "克拉玛依" },
{ name: "吐鲁番" },
{ name: "哈密" }
]
}
]
};
主程序
var width = 1000;
var height = 700;
var dome = document.getElementById('svgAll');
var svg = d3.create("svg").attr("width", width).attr("height", height);
var g = svg.append("g").attr("transform", "translate(10,10)");
//指定缩放范围,缩放功能
const zoom = d3
.zoom()
.scaleExtent([0.2, 4])
.on("zoom", (e) => {
g.attr("transform", `translate(${e.transform.x},${e.transform.y}) scale(${e.transform.k})`);
});
//动画持续时间
var initialTransform = d3.zoomIdentity.translate(20, 20).scale(2);//初始缩放平移
g.transition()
.duration(1000)
.call(zoom.transform, d3.zoomIdentity);
svg.call(zoom);
// 层级布局
var hierarchyData = d3.hierarchy(dataset)
.sum(function (d) {
return d.value;
});
// 树状图
var tree = d3.tree()
.size([1000, 500])
.separation(function (a, b) {
return (a.parent == b.parent ? 1 : 2) / a.depth;
})
function drawData(g,hiData) {
// 初始化绘制树基本数据
var treeData = tree(hiData);
// 边和节点
var nodes = treeData.descendants();
var links = treeData.links();
// 绘制边
var Bézier_curve_generator = d3.linkHorizontal()
.x(function (d) { return d.y; })
.y(function (d) { return d.x; });
var drawLink = ({ source, target }) => {// 直角连接线
const halfDistance = (target.y - source.y) / 2;
const halfY = source.y + halfDistance;
return `M${source.x},${source.y} L${source.x},${halfY} ${target.x},${halfY} ${target.x},${target.y}`;
}
g.append("g")
.selectAll("path")
.data(links)
.enter()
.append("path")
.attr("d", function (d) {
var start = { x: d.source.x, y: d.source.y };
var end = { x: d.target.x, y: d.target.y };
return drawLink({ source: start, target: end });
})
.attr("fill", "none")
.attr("stroke", "yellow")
.attr("stroke-width", 1);
// 文字分组
var gs = g.append("g")
.selectAll("g")
.data(nodes)
.enter()
.append("g")
.attr("transform", function (d) {
var cx = d.x;
var cy = d.y;
return "translate(" + cx + "," + cy + ")";
});
gs.append("circle")
.attr("r", 6)
.attr("fill", "white")
.attr("stroke", "blue")
.attr("stroke-width", 1)
.on("click", function (e,d) {
console.log(d);
});
gs.append("text")
.attr("x", function (d) {
return 8;
})
.attr("y", -5)
.attr("dy", 10)
.text(function (d) {
return d.data.name;
})
}
drawData(g,hierarchyData);
dome.appendChild(svg.node());
数据动态更新
树状布局数据更新,需要重新计算布局,重新绘制。
// 修改点击事件
...
.on("click", function (e,d) {
console.log(d);
updateData(d);
})
更新方法
function updateData(d) {
var addData = [
{ name: "测试1", value: 100 },
{ name: "测试2", value: 100 },
{ name: "测试3", value: 100 }
]
var appendData = {
children: addData
}
//重新设置depth
var setDepth = function (node, num, appendLeaf) {
node.depth = num;
if (node.children && node.children.length) {
node.children.forEach(function (item) {
setDepth(item, num + 1, appendLeaf);
});
} else {
appendLeaf.push(node);
}
};
if (d.data.name === '柳州') {
//设置数据
var appendLeaf = []; //增加的叶子节点
if (appendData.children.length) {
d.children = [];
appendData.children.forEach(function (item, index) {
var newNode = d3.hierarchy(item);
newNode.parent = d;
setDepth(newNode, d.depth + 1, appendLeaf);
d.children.push(newNode);
});
}
d.data.children = addData;
// 清空旧节点
g.selectAll('g').remove();
// 重绘
drawData(g,hierarchyData);
}
}
tree update 优化
// 更新线条处修改
var mLinks = g.select('.link-class');
if(mLinks.empty()){
mLinks = g.append("g").attr('class','link-class');
}
function linkD(d){
var start = {
x: d.source.x,
y: d.source.y
};
var end = {
x: d.target.x,
y: d.target.y
};
return drawLink({
source: start,
target: end
});
}
mLinks
.selectAll("path")
.data(links)
.attr("d", linkD)
.enter()
.append("path")
.attr("d", linkD)
.attr("fill", "none")
.attr("stroke", "yellow")
.attr("stroke-width", 1);
// 更新文本处修改
var mNodes = g.select('.node-class');
if(mNodes.empty()){
mNodes = g.append("g").attr('class','node-class');
}
function nodeTransform(d){
var cx = d.x;
var cy = d.y;
return "translate(" + cx + "," + cy + ")";
}
var gs = mNodes
.selectAll("g")
.data(nodes)
.attr("transform", nodeTransform)
.enter()
.append("g")
.attr("transform", nodeTransform);
// 更函数 不在需要清空根部g组了
// g.selectAll('g').remove();
//局部更新节点坐标,只会更新d节点一下的坐标
// var mdata = tree(d);
// console.log(mdata)
tree数据层级分组
- tree - 将指定的层次数据布局为整齐的树布局.
- tree.size - 指定整个布局的宽和高.
- tree.nodeSize - 给全部结点指定一个固定的大小(会导致tree.size失效).
- tree.separation - 设置或获取相隔结点之间的间隔计算函数.
- d3.group - 对数据集进行了分组
const data = [
{"root":"project","project_nr":"project 1","department":"1","devision":"A"},
{"root":"project","project_nr":"project 1","department":"1","devision":"B"},
{"root":"project","project_nr":"project 1","department":"2","devision":"A"},
{"root":"project","project_nr":"project 2","department":"3","devision":"A"}
]
var groupedData = d3.group(data,
d => d.root,
d => d.project_nr,
d => d.department,
d => d.devision);
var root = d3.hierarchy(groupedData);