SHIHUC

好记性不如烂笔头,还可以分享给别人看看! 专注基础算法,互联网架构,人工智能领域的技术实现和应用。
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

D3树状图异步按需加载数据

Posted on 2016-11-15 09:31  shihuc  阅读(7574)  评论(0编辑  收藏  举报

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 }

 

有一定的参考价值,需要的请转载,转载指明出处!