D3.js这个绘图工具,功能强大不必多说,完全一个Data Driven Document的绘图工具,用户可以按照自己的数据以及希望实现的图形,随心所欲的绘图。
图形绘制,D3默认采用的是异步加载,但是,这里的异步加载,指的是一次性的将图形展示所需要的数据异步的方式加载到浏览器前端显示。主要有如下这两种方式:
1 d3.csv(url[[, row], callback]) 2 3 Creates a request for the CSV file at the specified url with the default mime type text/csv. An optional row conversion function may be specified to map and filter row objects to a more-specific representation; see dsv.parse for details. 4 5 The row conversion function can be changed by calling request.row on the returned instance. For example, this: 6 7 d3.csv(url, row, callback); 8 Is equivalent to this: 9 10 d3.csv(url) 11 .row(row) 12 .get(callback);
1 d3.json(url[, callback]) 2 3 Creates a request for the JSON file at the specified url with the default mime type application/json. 4 5 This convenience constructor is approximately equivalent to: 6 7 d3.request(url) 8 .mimeType("application/json") 9 .response(function(xhr) { return JSON.parse(xhr.responseText); }) 10 .get(callback);
上述两种方式获取的数据,在很多时候,是比较难满足实际需求场景的。
比如,我们现在设计的一款微信公众号的应用中,捕获关注者转帖的轨迹,最终以树状图展现给用户。 若一次性加载所有的数据,会比较影响用户体验,因为一次遍历数据库所有的跟踪记录,无论是递归先根遍历还是非递归方式循环查找,最终的体验都是不令人满意的。 我们便采取按需的异步加载数据方式,即,当用户点击节点时,才从后台取数据。由于D3的优秀数据管理架构,数据一旦加载了,后续便可以不用再从服务器后台取数据。
其实,实现这种on demand方式的异步加载,其实也很简单。下面就基于官网的一个例子,做点修改,介绍如何实现。
官网原版的例子如下:
1 <!DOCTYPE html> 2 <meta charset="utf-8"> 3 <style> 4 5 .node { 6 cursor: pointer; 7 } 8 9 .node circle { 10 fill: #fff; 11 stroke: steelblue; 12 stroke-width: 1.5px; 13 } 14 15 .node text { 16 font: 10px sans-serif; 17 } 18 19 .link { 20 fill: none; 21 stroke: #ccc; 22 stroke-width: 1.5px; 23 } 24 25 </style> 26 <body> 27 <script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script> 28 <script> 29 var root = { 30 "name": "flare", 31 "deal": "2", 32 "children": [{ 33 "name": "analytics" , 34 "children": [{ 35 "name": "cluster", 36 "children": [{ 37 "name": "AgglomerativeCluster", 38 "size": 3938 39 }, { 40 "name": "CommunityStructure", 41 "size": 3812 42 }, { 43 "name": "HierarchicalCluster", 44 "size": 6714 45 }, { 46 "name": "MergeEdge", 47 "size": 743 48 }] 49 }] 50 }, { 51 "name": "ISchedulable", 52 "deal": "2", 53 "size": 1041 54 }, { 55 "name": "Parallel", 56 "size": 5176 57 }, { 58 "name": "Pause", 59 "size": 449 60 } 61 ] 62 }; 63 var margin = {top: 20, right: 120, bottom: 20, left: 120}, 64 width = 1024 - margin.right - margin.left, 65 height = 798 - margin.top - margin.bottom; 66 67 var i = 0, 68 duration = 750, 69 root; 70 71 var tree = d3.layout.tree().nodeSize([90, 60]); 72 73 var diagonal = d3.svg.diagonal() 74 .projection(function(d) { return [d.x, d.y]; }); 75 76 /* 77 var svg = d3.select("body").append("svg") 78 .attr("width", width + margin.right + margin.left) 79 .attr("height", height + margin.top + margin.bottom) 80 .append("g") 81 .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); 82 */ 83 84 //Redraw for zoom 85 function redraw() { 86 //console.log("here", d3.event.translate, d3.event.scale); 87 svg.attr("transform", "translate(" + d3.event.translate + ")" + " scale(" + d3.event.scale + ")"); 88 } 89 90 var svg = d3.select("body").append("svg").attr("width", 1024).attr("height", 798) 91 .call(zm = d3.behavior.zoom().scaleExtent([1,3]).on("zoom", redraw)).append("g") 92 .attr("transform", "translate(" + 512 + "," + 50 + ")"); 93 94 //necessary so that zoom knows where to zoom and unzoom from 95 zm.translate([512, 50]); 96 97 //d3.json("flare.json", function(error, flare) 98 // if (error) throw error; 99 { 100 root.x0 = 0; 101 root.y0 = height / 2; 102 103 function collapse(d) { 104 if (d.children) { 105 d._children = d.children; 106 d._children.forEach(collapse); 107 d.children = null; 108 } 109 } 110 111 root.children.forEach(collapse); 112 update(root); 113 } 114 115 d3.select(self.frameElement).style("height", "800px"); 116 117 function update(source) { 118 119 // Compute the new tree layout. 120 var nodes = tree.nodes(root).reverse(), 121 links = tree.links(nodes); 122 123 debugger; 124 // Normalize for fixed-depth. 125 nodes.forEach(function(d) { d.y = d.depth * 180; }); 126 127 // Update the nodes… 128 var node = svg.selectAll("g.node") 129 .data(nodes, function(d) { return d.id || (d.id = ++i); }); 130 131 // Enter any new nodes at the parent's previous position. 132 var nodeEnter = node.enter().append("g") 133 .attr("class", "node") 134 .attr("transform", function(d) { return "translate(" + source.x0 + "," + source.y0 + ")"; }) 135 .on("click", click); 136 137 nodeEnter.append("circle") 138 .attr("r", 1e-6) 139 .style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; }); 140 141 nodeEnter.append("text") 142 .attr("x", function(d) { return d.children || d._children ? -10 : 10; }) 143 .attr("dy", ".35em") 144 .attr("text-anchor", function(d) { return d.children || d._children ? "end" : "start"; }) 145 .text(function(d) { return d.name; }) 146 .style("fill-opacity", 1e-6); 147 148 // Transition nodes to their new position. 149 var nodeUpdate = node.transition() 150 .duration(duration) 151 .attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; }); 152 153 nodeUpdate.select("circle") 154 .attr("r", 20) 155 .style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; }); 156 157 nodeUpdate.select("text") 158 .style("fill-opacity", 1); 159 160 // Transition exiting nodes to the parent's new position. 161 var nodeExit = node.exit().transition() 162 .duration(duration) 163 .attr("transform", function(d) { return "translate(" + source.x + "," + source.y + ")"; }) 164 .remove(); 165 166 nodeExit.select("circle") 167 .attr("r", 1e-6); 168 169 nodeExit.select("text") 170 .style("fill-opacity", 1e-6); 171 172 // Update the links… 173 var link = svg.selectAll("path.link") 174 .data(links, function(d) { return d.target.id; }); 175 176 // Enter any new links at the parent's previous position. 177 link.enter().insert("path", "g") 178 .attr("class", "link") 179 .attr("d", function(d) { 180 var o = {x: source.x0, y: source.y0}; 181 return diagonal({source: o, target: o}); 182 }); 183 184 // Transition links to their new position. 185 link.transition() 186 .duration(duration) 187 .attr("d", diagonal); 188 189 // Transition exiting nodes to the parent's new position. 190 link.exit().transition() 191 .duration(duration) 192 .attr("d", function(d) { 193 var o = {x: source.x, y: source.y}; 194 return diagonal({source: o, target: o}); 195 }) 196 .remove(); 197 198 // Stash the old positions for transition. 199 nodes.forEach(function(d) { 200 d.x0 = d.x; 201 d.y0 = d.y; 202 }); 203 } 204 205 // Toggle children on click. 206 function click(d) { 207 if (d.children) { 208 d._children = d.children; 209 d.children = null; 210 } else { 211 d.children = d._children; 212 d._children = null; 213 } 214 update(d); 215 } 216 217 </script>
下面,再看看,如何实现on demand的异步加载:
1 <!DOCTYPE html> 2 <meta charset="utf-8"> 3 <style> 4 5 .node { 6 cursor: pointer; 7 } 8 9 .node circle { 10 fill: #fff; 11 stroke: steelblue; 12 stroke-width: 1.5px; 13 } 14 15 .node text { 16 font: 10px sans-serif; 17 } 18 19 .link { 20 fill: none; 21 stroke: #ccc; 22 stroke-width: 1.5px; 23 } 24 25 .link2 { 26 fill: none; 27 stroke: #f00; 28 stroke-width: 1.5px; 29 } 30 31 </style> 32 <body> 33 <script src="js/jquery-2.1.1.min.js" charset="utf-8"></script> 34 <script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script> 35 <script> 36 var root = { 37 "name": "flare", 38 "deal": "2", 39 "children": [{ 40 "name": "analytics" , 41 "children": [{ 42 "name": "cluster", 43 "children": [{ 44 "name": "AgglomerativeCluster", 45 "size": 3938 46 }, { 47 "name": "CommunityStructure", 48 "size": 3812 49 }, { 50 "name": "HierarchicalCluster", 51 "size": 6714 52 }, { 53 "name": "MergeEdge", 54 "size": 743 55 }] 56 }] 57 }, { 58 "name": "ISchedulable", 59 "deal": "2", 60 "size": 1041 61 }, { 62 "name": "Parallel", 63 "size": 5176 64 }, { 65 "name": "Pause", 66 "size": 449 67 } 68 ] 69 }; 70 var margin = {top: 20, right: 120, bottom: 20, left: 120}, 71 width = 1024 - margin.right - margin.left, 72 height = 798 - margin.top - margin.bottom; 73 74 var i = 0, 75 duration = 750, 76 root; 77 78 var tree = d3.layout.tree().nodeSize([90, 60]); 79 80 var diagonal = d3.svg.diagonal() 81 .projection(function(d) { return [d.x, d.y]; }); 82 83 /* 84 var svg = d3.select("body").append("svg") 85 .attr("width", width + margin.right + margin.left) 86 .attr("height", height + margin.top + margin.bottom) 87 .append("g") 88 .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); 89 */ 90 91 //Redraw for zoom 92 function redraw() { 93 //console.log("here", d3.event.translate, d3.event.scale); 94 svg.attr("transform", "translate(" + d3.event.translate + ")" + " scale(" + d3.event.scale + ")"); 95 } 96 97 var svg = d3.select("body").append("svg").attr("width", 1024).attr("height", 798) 98 .call(zm = d3.behavior.zoom().scaleExtent([1,3]).on("zoom", redraw)).append("g") 99 .attr("transform", "translate(" + 512 + "," + 50 + ")"); 100 101 //necessary so that zoom knows where to zoom and unzoom from 102 zm.translate([512, 50]); 103 104 //d3.json("flare.json", function(error, flare) 105 // if (error) throw error; 106 107 root.x0 = 0; 108 root.y0 = height / 2; 109 110 function collapse(d) { 111 if (d.children) { 112 d._children = d.children; 113 d._children.forEach(collapse); 114 d.children = null; 115 } 116 } 117 118 root.children.forEach(collapse); 119 update(root); 120 121 122 d3.select(self.frameElement).style("height", "800px"); 123 124 function update(source) { 125 126 // Compute the new tree layout. 127 var nodes = tree.nodes(root).reverse(), 128 links = tree.links(nodes); 129 130 // Normalize for fixed-depth. 131 nodes.forEach(function(d) { d.y = d.depth * 180; }); 132 133 // Update the nodes… 134 var node = svg.selectAll("g.node") 135 .data(nodes, function(d) { return d.id || (d.id = ++i); }); 136 137 // Enter any new nodes at the parent's previous position. 138 var nodeEnter = node.enter().append("g") 139 .attr("class", "node") 140 .attr("transform", function(d) { return "translate(" + source.x0 + "," + source.y0 + ")"; }) 141 .on("click", click); 142 143 nodeEnter.append("circle") 144 .attr("r", 1e-6) 145 .style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; }); 146 147 nodeEnter.append("text") 148 .attr("cx", function(d) { return d.children || d._children ? -10 : 10; }) 149 .attr("cy", ".35em") 150 .attr("text-anchor", function(d) { return d.children || d._children ? "end" : "start"; }) 151 .text(function(d) { return d.name; }) 152 .style("fill-opacity", 1e-6); 153 154 // Transition nodes to their new position. 155 var nodeUpdate = node.transition() 156 .duration(duration) 157 .attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; }); 158 159 nodeUpdate.select("circle") 160 .attr("r", 20) 161 .style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; }); 162 163 nodeUpdate.select("text") 164 .style("fill-opacity", 1); 165 166 // Transition exiting nodes to the parent's new position. 167 var nodeExit = node.exit().transition() 168 .duration(duration) 169 .attr("transform", function(d) { return "translate(" + source.x + "," + source.y + ")"; }) 170 .remove(); 171 172 nodeExit.select("circle") 173 .attr("r", 1e-6); 174 175 nodeExit.select("text") 176 .style("fill-opacity", 1e-6); 177 178 // Update the links… 179 var link = svg.selectAll("path.link") 180 .data(links, function(d) { return d.target.id; }); 181 182 // Enter any new links at the parent's previous position. 183 link.enter().insert("path", "g") 184 .attr("class", "link") 185 .attr("d", function(d) { 186 var o = {x: source.x0, y: source.y0}; 187 return diagonal({source: o, target: o}); 188 }); 189 /* 190 console.log(link); 191 192 link.enter().insert("path", "g") 193 .attr("class", function(d){ 194 if(d.source.deal != null && d.source.deal != undefined){ 195 if(d.target.deal != null && d.target.deal != undefined){ 196 return "link2"; 197 } 198 } 199 return "link"; 200 }) 201 .attr("d", function(d) { 202 var o = {x: source.x0, y: source.y0}; 203 return diagonal({source: o, target: o}); 204 }); 205 */ 206 // Transition links to their new position. 207 link.transition() 208 .duration(duration) 209 .attr("d", diagonal); 210 211 // Transition exiting nodes to the parent's new position. 212 link.exit().transition() 213 .duration(duration) 214 .attr("d", function(d) { 215 var o = {x: source.x, y: source.y}; 216 return diagonal({source: o, target: o}); 217 }) 218 .remove(); 219 220 // Stash the old positions for transition. 221 nodes.forEach(function(d) { 222 d.x0 = d.x; 223 d.y0 = d.y; 224 }); 225 } 226 227 function getNode(){ #自定义的一个新的以同步方式从后台取数据的ajax函数 228 var mynodes = null; 229 $.ajax({ 230 url : "./node", 231 async : false, // 注意此处需要同步 232 type : "POST", 233 dataType : "json", 234 success : function(data) { 235 mynodes = data; 236 console.log(mynodes); 237 //nodes = JSON.parse(nodes); 238 } 239 }); 240 return mynodes; 241 } 242 243 // Toggle children on click. 244 function click(d) { #重点关注这个函数的不同之处。尤其是else部分 245 if (d.children) { 246 d._children = d.children; 247 d.children = null; 248 } else if(d._children){ 249 d.children = d._children; 250 d._children = null; 251 }else { 252 var mnodes = getNode(); 253 d.children = mnodes.children; 254 } 255 update(d); 256 } 257 258 </script>
配合这个ajax的函数,java后台的代码,其实非常的简单,为了测试,构建D3树状图所需的数据结构。主要都是Array (含有孩子节点)
1 /** 2 * @author "shihuc" 3 * @date 2016年11月14日 4 */ 5 package com.tk.es.search.controller; 6 7 import java.util.ArrayList; 8 import java.util.HashMap; 9 10 import javax.servlet.http.HttpServletRequest; 11 12 import org.springframework.stereotype.Controller; 13 import org.springframework.web.bind.annotation.RequestMapping; 14 import org.springframework.web.bind.annotation.ResponseBody; 15 16 import com.google.gson.Gson; 17 18 /** 19 * @author chengsh05 20 * 21 */ 22 @Controller 23 public class D3Controller { 24 25 @RequestMapping(value = "/d3") 26 public String d3Page(HttpServletRequest req){ 27 return "d3demo"; 28 } 29 30 @RequestMapping(value = "/node") 31 @ResponseBody 32 public String asyncGet(HttpServletRequest req){ 33 HashMap<String, Object> data = new HashMap<String, Object>(); 34 ArrayList<Object>elem1 = new ArrayList<Object>(); 35 HashMap<String, String> elem1e = new HashMap<String, String>(); 36 elem1e.put("name", "one"); 37 elem1e.put("deal", "2"); 38 HashMap<String, String> elem2e = new HashMap<String, String>(); 39 elem2e.put("name", "two"); 40 HashMap<String, String> elem3e = new HashMap<String, String>(); 41 elem3e.put("name", "three"); 42 elem1.add(elem1e); 43 elem1.add(elem2e); 44 elem1.add(elem3e); 45 46 data.put("name", "Pause"); 47 data.put("children", elem1); 48 49 Gson gson = new Gson(); 50 return gson.toJson(data); 51 } 52 }
有一定的参考价值,需要的请转载,转载指明出处!