D3樹狀圖異步按需加載數據


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 }

 

有一定的參考價值,需要的請轉載,轉載指明出處!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM