本文基于专注于D3的吕之华前辈分享的树状图画法来写纵向组织结构树图
第十五章中树状图的样式为:
我们可以进行拓展,写出公司项目更为常用的竖向组织结构树,效果图如下:
主要需要进行的工作为:
- 将横纵坐标对换,变成纵向树状图
- 将曲线更改为折线
- 将圆圈节点更改为矩形节点,显示的文字位置也需要进行相应切换
原树状图代码为:
<html>
<head>
<meta charset="utf-8">
<title>树状图</title>
<style>
.node circle {
fill: #fff;
stroke: steelblue;
stroke-width: 1.5px;
}
.node {
font: 12px sans-serif;
}
.link {
fill: none;
stroke: #ccc;
stroke-width: 1.5px;
}
</style>
</head>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
var width = 500,
height = 500;
var root = {
"name": "中国",
"children": [{
"name": "浙江",
"children": [{
"name": "杭州"
},
{
"name": "宁波"
},
{
"name": "温州"
},
{
"name": "绍兴"
}
]
},
{
"name": "广西",
"children": [{
"name": "桂林",
"children": [{
"name": "秀峰区"
},
{
"name": "叠彩区"
},
{
"name": "象山区"
},
{
"name": "七星区"
}
]
},
{
"name": "南宁"
},
{
"name": "柳州"
},
{
"name": "防城港"
}
]
},
{
"name": "黑龙江",
"children": [{
"name": "哈尔滨"
},
{
"name": "齐齐哈尔"
},
{
"name": "牡丹江"
},
{
"name": "大庆"
}
]
},
{
"name": "新疆",
"children": [{
"name": "乌鲁木齐"
},
{
"name": "克拉玛依"
},
{
"name": "吐鲁番"
},
{
"name": "哈密"
}
]
}
]
};
var tree = d3.layout.tree()
.size([width, height - 200])
.separation(function (a, b) {
return (a.parent == b.parent ? 1 : 2);
});
var diagonal = d3.svg.diagonal()
.projection(function (d) {
return [d.y, d.x];
});
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(40,0)");
var nodes = tree.nodes(root);
var links = tree.links(nodes);
console.log(nodes);
console.log(links);
var link = svg.selectAll(".link")
.data(links)
.enter()
.append("path")
.attr("class", "link")
.attr("d", diagonal);
var node = svg.selectAll(".node")
.data(nodes)
.enter()
.append("g")
.attr("class", "node")
.attr("transform", function (d) {
return "translate(" + d.y + "," + d.x + ")";
})
node.append("circle")
.attr("r", 4.5);
node.append("text")
.attr("dx", function (d) {
return d.children ? -8 : 8;
})
.attr("dy", 3)
.style("text-anchor", function (d) {
return d.children ? "end" : "start";
})
.text(function (d) {
return d.name;
});
</script>
</body>
</html>
- 横向树状图更改为纵向无非就是横纵坐标相关配置进行对调,修改如下:
var width = 1200 // edited
var diagonal = d3.svg.diagonal()
.projection(function (d) {
return [d.x, d.y]; // edited
});
var svg = d3.select("body").append("svg")
.attr("width", width + 80) // 画布扩大,防止边缘文字被遮挡
.attr("height", height)
.append("g")
.attr("transform", "translate(40,40)"); // 将图整体下移,以防止顶部节点被遮挡
var node = svg.selectAll(".node")
.data(nodes)
.enter()
.append("g")
.attr("class", "node")
.attr("transform", function (d) {
return "translate(" + d.x + "," + d.y + ")"; // edited
})
显示效果如下
- 将曲线更改为折线:
// 定义矩形的宽高,折线据此确定横纵坐标
var boxWidth = 65, boxHeight = 40;
/* var link = svg.selectAll(".link")
.data(links)
.enter()
.append("path")
.attr("class", "link")
.attr("d", diagonal);
*/
drawLine();
// 将曲线换为折线
function drawLine() {
var link = svg.selectAll("path.link")
// The function we are passing provides d3 with an id
// so that it can track when data is being added and removed.
// This is not necessary if the tree will only be drawn once
// as in the basic example.
.data(links);
// Add new links
link.enter().append("path")
.attr("class", "link");
// Remove any links we don't need anymore
// if part of the tree was collapsed
link.exit().remove();
// Update the links positions (old and new)
link.attr("d", elbow);
function elbow(d) {
let sourceX = d.source.x,
sourceY = d.source.y + boxHeight,
targetX = d.target.x,
targetY = d.target.y;
return "M" + sourceX + "," + sourceY +
"V" + ((targetY - sourceY) / 2 + sourceY) +
"H" + targetX +
"V" + targetY;
}
}
显示效果如图:
- 将圆圈节点更改为矩形节点:
// 圆形节点与对应文字
// node.append("circle")
// .attr("r", 4.5);
// node.append("text")
// .attr("dx", function (d) {
// return d.children ? -8 : 8;
// })
// .attr("dy", 3)
// .style("text-anchor", function (d) {
// return d.children ? "end" : "start";
// })
// .text(function (d) {
// return d.name;
// });
// 绘制矩形与文字
drawRect();
function drawRect() {
node.append("rect")
.attr('y', 0)
.attr('x', function (d) {
return d.depth !== 2 ? -(boxWidth / 2) : -(boxHeight / 2)
})
.attr('width', function (d) {
return d.depth !== 2 ? boxWidth : boxHeight;
})
.attr('height', function (d) {
return d.depth !== 2 ? boxHeight : boxWidth;
})
// 矩形背景色以及边框颜色宽度
.attr('fill', '#fff')
.attr('stroke', 'steelblue')
.attr('strokeWidth', '1px')
.on('click', function (evt) {
console.log(evt); // 显示所点击节点数据
});
// Draw the person's name and position it inside the box
node.append("text")
.attr('y', function (d) {
return d.depth !== 2 ? boxHeight / 2 + 5 : 0;
})
// .attr('rotate', function (d) { //显示竖直显示中文时rotate为0,英文-90
// return 0;
// })
.attr('style', function (d) {
return d.depth !== 2 ? '' : "writing-mode: tb;letter-spacing:0px";
})
.attr("text-anchor", function (d) {
return d.depth !== 2 ? 'middle' : "start";
})
.text(function (d) {
return d.name;
});
}
显示效果如图:
再增加放大放小的功能
// 用来拖拽图以及扩大缩放
var zoom = d3.behavior.zoom()
.scaleExtent([.1, 1])
.on('zoom', function () {
svg.attr("transform", "translate(" + d3.event.translate + ") scale(" + d3.event.scale + ")");
});
var svg = d3.select("body").append("svg")
.attr("width", width + 80) // 画布扩大,防止边缘文字被遮挡
.attr("height", height)
.append("g")
.call(zoom) // 相当于zoom(svg)
.attr("transform", "translate(40,40)"); // 将图整体下移,以防止顶部节点被遮挡
至此成功将横向树状图成功转换为纵向组织结构树图
完整代码如下:
<!Doctype html>
<head>
<meta charset="utf-8">
<title>树状图</title>
<style>
.node circle {
fill: #fff;
stroke: steelblue;
stroke-width: 1.5px;
}
.node {
font: 12px sans-serif;
}
.link {
fill: none;
stroke: #ccc;
stroke-width: 1.5px;
}
</style>
</head>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<!-- 纵向树状图 -->
<script>
var width = 1200,
height = 500,
boxWidth = 65,
boxHeight = 40;
var root = {
"name": "中国",
"children": [{
"name": "浙江",
"children": [{
"name": "杭州"
},
{
"name": "宁波"
},
{
"name": "温州"
},
{
"name": "绍兴"
}
]
},
{
"name": "广西",
"children": [{
"name": "桂林",
"children": [{
"name": "秀峰区"
},
{
"name": "叠彩区"
},
{
"name": "象山区"
},
{
"name": "七星区"
}
]
},
{
"name": "南宁"
},
{
"name": "柳州"
},
{
"name": "防城港"
}
]
},
{
"name": "黑龙江",
"children": [{
"name": "哈尔滨"
},
{
"name": "齐齐哈尔"
},
{
"name": "牡丹江"
},
{
"name": "大庆"
}
]
},
{
"name": "新疆",
"children": [{
"name": "乌鲁木齐"
},
{
"name": "克拉玛依"
},
{
"name": "吐鲁番"
},
{
"name": "哈密"
}
]
}
]
};
var tree = d3.layout.tree()
.size([width, height - 200])
.separation(function (a, b) {
return (a.parent == b.parent ? 1 : 2);
});
var diagonal = d3.svg.diagonal()
.projection(function (d) {
return [d.x, d.y]; // edited
});
// 用来拖拽图以及扩大缩放
var zoom = d3.behavior.zoom()
.scaleExtent([.1, 1])
.on('zoom', function () {
svg.attr("transform", "translate(" + d3.event.translate + ") scale(" + d3.event.scale + ")");
});
var svg = d3.select("body").append("svg")
.attr("width", width + 80) // 画布扩大,防止边缘文字被遮挡
.attr("height", height)
.append("g")
.call(zoom) // 相当于zoom(svg)
.attr("transform", "translate(40,40)"); // 将图整体下移,以防止顶部节点被遮挡
var nodes = tree.nodes(root);
var links = tree.links(nodes);
console.log(nodes);
console.log(links);
// var link = svg.selectAll(".link")
// .data(links)
// .enter()
// .append("path")
// .attr("class", "link")
// .attr("d", diagonal);
drawLine();
// 将曲线换为折线
function drawLine() {
var link = svg.selectAll("path.link")
// The function we are passing provides d3 with an id
// so that it can track when data is being added and removed.
// This is not necessary if the tree will only be drawn once
// as in the basic example.
.data(links);
// Add new links
link.enter().append("path")
.attr("class", "link");
// Remove any links we don't need anymore
// if part of the tree was collapsed
link.exit().remove();
// Update the links positions (old and new)
link.attr("d", elbow);
function elbow(d) {
let sourceX = d.source.x,
sourceY = d.source.y + boxHeight,
targetX = d.target.x,
targetY = d.target.y;
return "M" + sourceX + "," + sourceY +
"V" + ((targetY - sourceY) / 2 + sourceY) +
"H" + targetX +
"V" + targetY;
}
}
var node = svg.selectAll(".node")
.data(nodes)
.enter()
.append("g")
.attr("class", "node")
.attr("transform", function (d) {
return "translate(" + d.x + "," + d.y + ")"; // edited
})
// 圆形节点与对应文字
// node.append("circle")
// .attr("r", 4.5);
// node.append("text")
// .attr("dx", function (d) {
// return d.children ? -8 : 8;
// })
// .attr("dy", 3)
// .style("text-anchor", function (d) {
// return d.children ? "end" : "start";
// })
// .text(function (d) {
// return d.name;
// });
// 绘制矩形与文字
drawRect();
function drawRect() {
node.append("rect")
.attr('y', 0)
.attr('x', function (d) {
return d.depth !== 2 ? -(boxWidth / 2) : -(boxHeight / 2)
})
.attr('width', function (d) {
return d.depth !== 2 ? boxWidth : boxHeight;
})
.attr('height', function (d) {
return d.depth !== 2 ? boxHeight : boxWidth;
})
// 矩形背景色以及边框颜色宽度
.attr('fill', '#fff')
.attr('stroke', 'steelblue')
.attr('strokeWidth', '1px')
.on('click', function (evt) {
console.log(evt); // 显示所点击节点数据
});
// Draw the person's name and position it inside the box
node.append("text")
.attr('y', function (d) {
return d.depth !== 2 ? boxHeight / 2 + 5 : 0;
})
// .attr('rotate', function (d) { //显示竖直显示中文时rotate为0,英文-90
// return 0;
// })
.attr('style', function (d) {
return d.depth !== 2 ? '' : "writing-mode: tb;letter-spacing:0px";
})
.attr("text-anchor", function (d) {
return d.depth !== 2 ? 'middle' : "start";
})
.text(function (d) {
return d.name;
});
}
</script>
</body>
</html>
现在我们来进行代码优化,将代码封装到StructureGraph类中,相关节点与连线的渲染画法代码逻辑绑定到此类的原型上,最后通过StructureGraph的render方法渲染出来纵向组织结构树图,代码如下:
<html>
<head>
<meta charset="utf-8">
<title>树状图</title>
<style>
.node circle {
fill: #fff;
stroke: steelblue;
stroke-width: 1.5px;
}
.node {
font: 12px sans-serif;
}
.link {
fill: none;
stroke: #ccc;
stroke-width: 1.5px;
}
</style>
</head>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
var options = {
width: 1200,
height: 500,
boxWidth: 65,
boxHeight: 40
}
var city_tree = {
"name": "中国",
"children": [{
"name": "浙江",
"children": [{
"name": "杭州"
},
{
"name": "宁波"
},
{
"name": "温州"
},
{
"name": "绍兴"
}
]
},
{
"name": "广西",
"children": [{
"name": "桂林",
"children": [{
"name": "秀峰区"
},
{
"name": "叠彩区"
},
{
"name": "象山区"
},
{
"name": "七星区"
}
]
},
{
"name": "南宁"
},
{
"name": "柳州"
},
{
"name": "防城港"
}
]
},
{
"name": "黑龙江",
"children": [{
"name": "哈尔滨"
},
{
"name": "齐齐哈尔"
},
{
"name": "牡丹江"
},
{
"name": "大庆"
}
]
},
{
"name": "新疆",
"children": [{
"name": "乌鲁木齐"
},
{
"name": "克拉玛依"
},
{
"name": "吐鲁番"
},
{
"name": "哈密"
}
]
}
]
}
// 纵向树状图类
var StructureGraph = function () {
// 布局
this.tree = d3.layout.tree()
.size([options.width, options.height - 200])
.separation(function (a, b) {
return (a.parent == b.parent ? 1 : 2);
});
this.diagonal = d3.svg.diagonal()
.projection(function (d) {
return [d.x, d.y];
});
// 用来拖拽图以及扩大缩放
let zoom = d3.behavior.zoom()
.scaleExtent([.1, 1])
.on('zoom', () => {
this.svg.attr("transform", "translate(" + d3.event.translate + ") scale(" + d3.event.scale + ")");
});
this.svg = d3.select("body").append("svg")
.attr("width", options.width + 80)
.attr("height", options.height)
.call(zoom)
.append("g")
.attr("transform", "translate(40,40)");
}
// 开始渲染节点与连线
StructureGraph.prototype.render = function (source) {
this.renderLinks(source);
this.renderNodes(source);
return this;
}
// 渲染节点
StructureGraph.prototype.renderNodes = function (source) {
var _this = this;
this.node = this.svg.selectAll(".node")
.data(this.nodes)
.enter()
.append("g")
.attr("class", "node")
.attr("strokeWidth", 100)
.attr("transform", function (d) {
return "translate(" + d.x + "," + d.y + ")";
})
// 绘制矩形与文字
drawRect();
function drawRect() {
_this.node.append("rect")
.attr('y', 0)
.attr('x', function (d) {
return d.depth !== 2 ? -(options.boxWidth / 2) : -(options.boxHeight / 2)
})
.attr('width', function (d) {
return d.depth !== 2 ? options.boxWidth : options.boxHeight;
})
.attr('height', function (d) {
return d.depth !== 2 ? options.boxHeight : options.boxWidth;
})
// 矩形背景色以及边框颜色宽度
.attr('fill', '#fff')
.attr('stroke', 'steelblue')
.attr('strokeWidth', '1px')
.on('click', function (evt) {
console.log(evt); // 显示所点击节点数据
});
// Draw the person's name and position it inside the box
_this.node.append("text")
.attr('y', function (d) {
return d.depth !== 2 ? options.boxHeight / 2 + 5 : 0;
})
// .attr('rotate', function (d) { //显示竖直显示中文时rotate为0,英文-90
// return 0;
// })
.attr('style', function (d) {
return d.depth !== 2 ? '' : "writing-mode: tb;letter-spacing:0px";
})
.attr("text-anchor", function (d) {
return d.depth !== 2 ? 'middle' : "start";
})
.text(function (d) {
return d.name;
});
}
}
// 渲染连线
StructureGraph.prototype.renderLinks = function (source) {
var _this = this;
this.nodes = this.tree.nodes(source);
this.links = this.tree.links(this.nodes);
drawLine();
// 将曲线换为折线
function drawLine() {
var link = _this.svg.selectAll("path.link")
// The function we are passing provides d3 with an id
// so that it can track when data is being added and removed.
// This is not necessary if the tree will only be drawn once
// as in the basic example.
.data(_this.links);
// Add new links
link.enter().append("path")
.attr("class", "link");
// Remove any links we don't need anymore
// if part of the tree was collapsed
link.exit().remove();
// Update the links positions (old and new)
link.attr("d", elbow);
function elbow(d) {
let sourceX = d.source.x,
sourceY = d.source.y + options.boxHeight,
targetX = d.target.x,
targetY = d.target.y;
return "M" + sourceX + "," + sourceY +
"V" + ((targetY - sourceY) / 2 + sourceY) +
"H" + targetX +
"V" + targetY;
}
}
}
// 初始化实例。与jquery配合使用可以通过将下面两行代码放到$.fn.structure = (){}中,增加自定义插件
var ins = new StructureGraph();
ins.render(city_tree);
</script>
</body>
</html>
这样我们代码就显得有条理多了,维护性与可用性也大大提高。在线观看demo
总结
学习D3的时间才几天没研究透彻,有些更高级的功能比如点击节点的收缩以及动画效果还没研究,后期持续更新。由于入门不久,有需要纠正以及优化的地方欢迎评论提出~
参考文献
树图的展开和折叠: https://blog.csdn.net/qq_26562641/article/details/77480767
组织结构树:https://blog.csdn.net/u014324940/article/details/81206364
D3 入门教程:http://wiki.jikexueyuan.com/project/d3wiki/introduction.html
D3官方网站:http://d3js.org
网友评论