本文基于專注于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