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>