vue.js中使用D3树状图异步按需加载数据绘制人物关系图,网上查了好多资料没找到合适的,就自己写个简单的,方便以后查看,附上效果图
重点:这个树状图不管是Vue的,还是HTML的,使用的D3.js 版本是3.5.17,如果使用别的版本,可能里面的语法不同,所以使用者请确认好下载的D3.js的版本
D3.js是一个基于 web 标准的 JavaScript 可视化库. D3 可以借助 SVG, Canvas 以及 HTML 将你的数据生动的展现出来. D3 结合了强大的可视化交互技术以及数据驱动 DOM 的技术结合起来, 让你可以借助于现代浏览器的强大功能自由的对数据进行可视化.
图形绘制,D3默认采用的是异步加载,但是,这里的异步加载,指的是一次性的将图形展示所需要的数据异步的方式加载到浏览器前端显示,最终以树状图展现给用户。 若一次性加载所有的数据,会比较影响用户体验,因为一次遍历数据库所有的跟踪记录,无论是递归先根遍历还是非递归方式循环查找,最终的体验都是不令人满意的。 我们便采取按需的异步加载数据方式,即,当用户点击节点时,才从后台取数据。由于D3的优秀数据管理架构,数据一旦加载了,后续便可以不用再从服务器后台取数据。
先使用HTML创建异步按需加载数据绘制的树状图
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.node {
cursor: pointer;
}
.node circle {
fill: #fff;
stroke: steelblue;
stroke-width: 1.5px;
}
.node text {
font: 10px sans-serif;
}
.link {
fill: none;
stroke: #ccc;
stroke-width: 1.5px;
}
.link2 {
fill: none;
stroke: #f00;
stroke-width: 1.5px;
}
</style>
<body>
<script src="lib/jquery.min.js" charset="utf-8"></script>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<script>
var root = {
"name": "flare",
"deal": "1",
"children": [{
"name": "AAA",
"deal": "2"
},{
"name": "BBB",
"deal": "3"
}]
};
var margin = {top: 20, right: 120, bottom: 20, left: 550},
width = 1024 - margin.right - margin.left,
height = 798 - margin.top - margin.bottom;
var i = 0,
duration = 750,
root;
var tree = d3.layout.tree().nodeSize([90, 60]);
var diagonal = d3.svg.diagonal()
.projection(function(d) { return [d.x, d.y]; });
var svg = d3.select("body").append("svg")
.attr("width", width + margin.right + margin.left)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
//Redraw for zoom
// function redraw() {
// // debugger
// // console.log("here", d3.event.translate, d3.event.scale);
// svg.attr("transform", "translate(" + d3.event.translate + ")" + " scale(" + d3.event.scale + ")");
// }
// console.log(d3.select("body"))
// var svg = d3.select("body").append("svg").attr("width", 1024).attr("height", 798)
// .call(zm = d3.behavior.zoom().scaleExtent([1,3]).on("zoom", redraw))
// .append("g")
// .attr("transform", "translate(" + 512 + "," + 50 + ")");
// console.log(svg)
//necessary so that zoom knows where to zoom and unzoom from
// zm.translate([512, 50]);
root.x0 = 0;
root.y0 = height / 2;
function collapse(d) {
// debugger
if (d.children) {
d._children = d.children;
d._children.forEach(collapse);
d.children = null;
}
}
console.log(root.children)
root.children.forEach(collapse);
update(root);
// d3.select(self.frameElement).style("height", "100px");
// console.log(d3.select(self.frameElement).style("height", "100px"))
function update(source) {
// debugger
// Compute the new tree layout.
var nodes = tree.nodes(root).reverse(),
links = tree.links(nodes);
// console.log(nodes)
// console.log(links)
// Normalize for fixed-depth.
nodes.forEach(function(d) { d.y = d.depth * 180; });
// Update the nodes…
// console.log(svg)
var node = svg.selectAll("g.node")
.data(nodes, function(d) { return d.id || (d.id = ++i); });
// console.log(node.transition())
// Enter any new nodes at the parent's previous position.
var nodeEnter = node.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) { return "translate(" + source.x0 + "," + source.y0 + ")"; })
.on("click", click);
nodeEnter.append("circle")
.attr("r", 1e-6)
.style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; });
nodeEnter.append("text")
.attr("cx", function(d) { return d.children || d._children ? -10 : 10; })
.attr("cy", ".35em")
.attr("text-anchor", function(d) { return d.children || d._children ? "end" : "start"; })
.text(function(d) { return d.name; })
.style("fill-opacity", 1e-6);
// Transition nodes to their new position.
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
// console.log(nodeUpdate)
nodeUpdate.select("circle")
.attr("r", 20)
.style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; });
nodeUpdate.select("text")
.style("fill-opacity", 1);
// Transition exiting nodes to the parent's new position.
var nodeExit = node.exit().transition()
.duration(duration)
.attr("transform", function(d) { return "translate(" + source.x + "," + source.y + ")"; })
.remove();
nodeExit.select("circle")
.attr("r", 1e-6);
nodeExit.select("text")
.style("fill-opacity", 1e-6);
// Update the links…
var link = svg.selectAll("path.link")
.data(links, function(d) { return d.target.id; });
// console.log(link)
// Enter any new links at the parent's previous position.
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});
});
/*
console.log(link);
link.enter().insert("path", "g")
.attr("class", function(d){
if(d.source.deal != null && d.source.deal != undefined){
if(d.target.deal != null && d.target.deal != undefined){
return "link2";
}
}
return "link";
})
.attr("d", function(d) {
var o = {x: source.x0, y: source.y0};
return diagonal({source: o, target: o});
});
*/
// Transition links to their new position.
link.transition()
.duration(duration)
.attr("d", diagonal);
// Transition exiting nodes to the parent's new position.
link.exit().transition()
.duration(duration)
.attr("d", function(d) {
var o = {x: source.x, y: source.y};
return diagonal({source: o, target: o});
})
.remove();
// Stash the old positions for transition.
nodes.forEach(function(d) {
d.x0 = d.x;
d.y0 = d.y;
});
}
function getNode(){
// #自定义的一个新的以同步方式从后台取数据的ajax函数
// debugger
var mynodes = [{
"name": "CCC",
"size": "4"
},{
"name": "DDD",
"size": "5"
},{
"name": "EEE",
"size": "6"
}];
// $.ajax({
// url : "./node",
// async : false, // 注意此处需要同步
// type : "POST",
// dataType : "json",
// success : function(data) {
// mynodes = data;
// console.log(mynodes);
// //nodes = JSON.parse(nodes);
// }
// });
return mynodes;
}
// Toggle children on click.
function click(d) {
// debugger
// console.log(d)
// #重点关注这个函数的不同之处。尤其是else部分
// debugger
if (d.children) {
d._children = d.children;
d.children = null;
} else if(d._children){
d.children = d._children;
d._children = null;
}else {
var mnodes = getNode();
// console.log(mnodes)
d.children = mnodes;
}
update(d);
}
</script>
这个HTML是参考:https://www.cnblogs.com/shihuc/p/6064448.html
Vue的是我自己改写的,里面使用了elementUI样式库,如果没有下载的话,可以把“<el-container>”这个标签删掉自己写,引入的D3的版本是3.5.17,如果是新版本,语法被简写了,可能不支持,所以下载D3.js版本的时候,要注意版本号。
<template>
<div class="register">
<el-container>
<div class="tree-svg" id="treeId"></div>
</el-container>
</div>
</template>
<script>
import d3 from 'd3'
var diagonal = d3.svg.diagonal()
.projection(function(d) { return [d.x, d.y]; });
export default {
name: 'register',
components: {
},
computed: {
},
data() {
return {
root: {
"name": "flare",
"size": "1",
"image": "http://www.ourd3js.com/demo/J-2.0/lingsha.png",
"children": [{
"name": "AAA",
"size": "11",
"image": "http://www.ourd3js.com/demo/J-2.0/tianhe.png"
},{
"name": "BBB",
"size": "12",
"image": "http://www.ourd3js.com/demo/J-2.0/mengli.png"
}]
},
tree: null,
zm: null,
height: 750,
count: 0,
duration: 800,
};
},
created () {
var that = this
var tree = d3.layout.tree().nodeSize([60, 60]);
that.tree = tree
},
mounted () {
var that = this;
var svg = d3.select("#treeId").append("svg").attr("width", 960).attr("height", 650)
.call(that.zm = d3.behavior.zoom().scaleExtent([1,3]).on("zoom", d=>{svg.attr("transform", "translate(" + d3.event.translate + ")" );}))
.append("g")
.attr("transform", "translate(" + 480 + "," + 50 + ")");
that.zm.translate([512, 50]);
that.svg = svg
that.root.x0 = 0;
that.root.y0 = that.height / 2;
function collapse(d) {
if (d.children) {
d._children = d.children;
d._children.forEach(collapse);
d.children = null;
}
}
// Initialize the display to show a few nodes.
that.root.children.forEach(collapse);
that.update(that.root);
},
methods: {
update (source) {
var that = this
// Compute the new tree layout.
var nodes = that.tree.nodes(that.root).reverse(),
links = that.tree.links(nodes);
// Normalize for fixed-depth.
nodes.forEach(function(d) { d.y = d.depth * 180; });
// Update the nodes…
var node = that.svg.selectAll("g.node")
.data(nodes, function(d) { return d.id || (d.id = ++that.count); });
// Enter any new nodes at the parent's previous position.
var nodeEnter = node.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) {
return "translate(" + source.x0 + "," + source.y0 + ")";
})
.on("click", d => that.click(d));
nodeEnter.append('image')
.attr('xlink:href', d => {
return d.image
})
.attr('x', d => {
return d.children || d._children ? -25 : -25
})
.attr('y', -50)
nodeEnter.append("text")
.attr("x", function(d) { return d.children || d._children ? -20 : -20; })
.attr("y", "15")
.attr("font-size", "18px")
.text(function(d) { return d.name; })
.style("fill-opacity", 1e-6);
// Transition nodes to their new position.
var nodeUpdate = node.transition()
.duration(that.duration)
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
nodeUpdate.selectAll('image')
.attr('width', 50)
.attr('height', 50)
nodeUpdate.select("text")
.style("fill-opacity", 1);
// Transition exiting nodes to the parent's new position.
var nodeExit = node.exit().transition()
.duration(that.duration)
.attr("transform", function(d) { return "translate(" + source.x + "," + source.y + ")"; })
.remove();
nodeExit.select('image')
.attr('width', 0)
.attr('height', 0)
nodeExit.select("text")
.style("fill-opacity", 1e-6);
// Update the links…
var link = that.svg.selectAll("path.link")
.data(links, function(d) {
return d.target.id;
});
// Enter any new links at the parent's previous position.
link.enter().insert("svg:path", "g")
.attr("class", "link")
.attr("fill", "none")
.attr("stroke", "#ccc")
.attr("stroke-width", "2")
.attr("d", function(d) {
var o = {x: source.x0, y: source.y0};
return diagonal({source: o, target: o});
});
// Transition links to their new position.
link.transition()
.duration(that.duration)
.attr("d", diagonal);
// Transition exiting nodes to the parent's new position.
link.exit().transition()
.duration(that.duration)
.attr("d", function(d) {
var o = {x: source.x, y: source.y};
return diagonal({source: o, target: o});
})
.remove();
// Stash the old positions for transition.
nodes.forEach(function(d) {
d.x0 = d.x;
d.y0 = d.y;
});
},
click(d) {
var that = this
// #重点关注这个函数的不同之处。尤其是else部分
if (d.children) {
d._children = d.children;
d.children = null;
} else if(d._children){
d.children = d._children;
d._children = null;
}else {
var mnodes = that.getNode(d.size);
d.children = mnodes;
}
that.update(d);
},
getNode (id) {
// #自定义的一个新的以同步方式从后台取数据的ajax函数
var mynodes = [];
if (id === '11') {
mynodes = [{
"name": "AAA01",
"image": "http://www.ourd3js.com/demo/J-2.0/ziying.png",
"size": "111"
},{
"name": "AAA02",
"image": "http://www.ourd3js.com/demo/J-2.0/tianqing.png",
"size": "112"
},{
"name": "AAA03",
"image": "http://www.ourd3js.com/demo/J-2.0/suyu.png",
"size": "113"
}];
} else if(id === '12') {
mynodes = [{
"name": "BBB01",
"image": "http://www.ourd3js.com/demo/J-2.0/xuanxiao.png",
"size": "121"
},{
"name": "BBB02",
"image": "http://www.ourd3js.com/demo/J-2.0/suyao.png",
"size": "122"
},{
"name": "BBB03",
"image": "http://www.ourd3js.com/demo/J-2.0/taiqing.png",
"size": "123"
}];
} else {
mynodes = [{
"name": "DDD",
"image": "http://www.ourd3js.com/demo/J-2.0/xizhong.png",
"size": "131"
},{
"name": "EEE",
"image": "http://www.ourd3js.com/demo/J-2.0/guixie.png",
"size": "132"
},{
"name": "FFF",
"image": "http://www.ourd3js.com/demo/J-2.0/chanyou.png",
"size": "133"
}];
}
return mynodes;
},
}
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.register {
padding-top: 50px;
width: 100%;
}
.tree-svg{
margin: 0 auto;
border: 1px solid #f00;
}
</style>