vue.js中使用D3树状图异步按需加载数据绘制人物关系图,网上查了好多资料没找到合适的,就本身写个简单的,方便之后查看,附上效果图html
重点:这个树状图无论是Vue的,仍是HTML的,使用的D3.js 版本是3.5.17,若是使用别的版本,可能里面的语法不一样,因此使用者请确认好下载的D3.js的版本前端
D3.js是一个基于 web 标准的 JavaScript 可视化库. D3 能够借助 SVG, Canvas 以及 HTML 将你的数据生动的展示出来. D3 结合了强大的可视化交互技术以及数据驱动 DOM 的技术结合起来, 让你能够借助于现代浏览器的强大功能自由的对数据进行可视化.vue
图形绘制,D3默认采用的是异步加载,可是,这里的异步加载,指的是一次性的将图形展现所须要的数据异步的方式加载到浏览器前端显示,最终以树状图展示给用户。 若一次性加载全部的数据,会比较影响用户体验,由于一次遍历数据库全部的跟踪记录,不管是递归先根遍历仍是非递归方式循环查找,最终的体验都是不使人满意的。 咱们便采起按需的异步加载数据方式,即,当用户点击节点时,才从后台取数据。因为D3的优秀数据管理架构,数据一旦加载了,后续即可以不用再从服务器后台取数据。node
先使用HTML建立异步按需加载数据绘制的树状图jquery
<!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.htmlweb
Vue的是我本身改写的,里面使用了elementUI样式库,若是没有下载的话,能够把“<el-container>”这个标签删掉本身写,引入的D3的版本是3.5.17,若是是新版本,语法被简写了,可能不支持,因此下载D3.js版本的时候,要注意版本号。ajax
<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>