SHIHUC

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

D3树状图给指定特性的边特别显示颜色

Posted on 2016-12-09 17:43  shihuc  阅读(2429)  评论(0编辑  收藏  举报

D3作为前端图形显示的利器,功能之强,对底层技术细节要求相对比较多。

 

有一点,就是要理解其基本的数据和节点的匹配规则架构,即enter,update和exit原理,我前面的D3基础篇中有介绍过,不明白的可以再去研究下。

 

本篇博文,同样是在这个框架下,完成修改树状图中某两个节点之间的边用红色线条连接,实现表达特殊含义的目的。

背景故事: 微信朋友圈之间产品帖子相互转发,有些帖子转发后会有成交,只要有成交,则这个促成成交的节点及其之上的父节点都相应是有功劳的,这个轨迹需要用高亮的颜色表示(比如本例中,用红色表示)。

 

其实也比较简单,直接看代码, 前端部分:

  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 //Redraw for zoom
 84 function redraw() {
 85   //console.log("here", d3.event.translate, d3.event.scale);
 86   svg.attr("transform", "translate(" + d3.event.translate + ")" + " scale(" + d3.event.scale + ")");
 87 }
 88     
 89 var svg = d3.select("body").append("svg").attr("width", 1024).attr("height", 798)
 90     .call(zm = d3.behavior.zoom().scaleExtent([1,3]).on("zoom", redraw)).append("g")
 91     .attr("transform", "translate(" + 512 + "," + 50 + ")");
 92 
 93 //necessary so that zoom knows where to zoom and unzoom from
 94 zm.translate([512, 50]);
 95     
 96 //d3.json("flare.json", function(error, flare) 
 97 //  if (error) throw error;
 98   
 99 root.x0 = 0;
100 root.y0 = height / 2;
101 
102 function collapse(d) {
103     if (d.children) {
104       d._children = d.children;
105       d._children.forEach(collapse);
106       d.children = null;
107     }
108 }
109 
110 root.children.forEach(collapse);
111 update(root);
112 
113 
114 d3.select(self.frameElement).style("height", "800px");
115 
116 function update(source) {
117 
118   // Compute the new tree layout.
119   var nodes = tree.nodes(root).reverse(),
120       links = tree.links(nodes);
121 
122   // Normalize for fixed-depth.
123   nodes.forEach(function(d) { d.y = d.depth * 180; });
124 
125   // Update the nodes…
126   var node = svg.selectAll("g.node")
127       .data(nodes, function(d) { return d.id || (d.id = ++i); });
128 
129   // Enter any new nodes at the parent's previous position.
130   var nodeEnter = node.enter().append("g")
131       .attr("class", "node")
132       .attr("transform", function(d) { return "translate(" + source.x0 + "," + source.y0 + ")"; })
133       .on("click", click);
134 
135   nodeEnter.append("circle")
136       .attr("r", 1e-6)
137       .style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; });
138 
139   nodeEnter.append("text")
140       .attr("cx", function(d) { return d.children || d._children ? -10 : 10; })
141       .attr("cy", ".35em")
142       .attr("text-anchor", function(d) { return d.children || d._children ? "end" : "start"; })
143       .text(function(d) { return d.name; })
144       .style("fill-opacity", 1e-6);
145 
146   // Transition nodes to their new position.
147   var nodeUpdate = node.transition()
148       .duration(duration)
149       .attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
150 
151   nodeUpdate.select("circle")
152       .attr("r", 20)
153       .style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; });
154 
155   nodeUpdate.select("text")
156       .style("fill-opacity", 1);
157 
158   // Transition exiting nodes to the parent's new position.
159   var nodeExit = node.exit().transition()
160       .duration(duration)
161       .attr("transform", function(d) { return "translate(" + source.x + "," + source.y + ")"; })
162       .remove();
163 
164   nodeExit.select("circle")
165       .attr("r", 1e-6);
166 
167   nodeExit.select("text")
168       .style("fill-opacity", 1e-6);
169 
170   // Update the links…
171   var link = svg.selectAll("path.link")
172       .data(links, function(d) { return d.target.id; });
173 
174   // Enter any new links at the parent's previous position.
175   link.enter().insert("path", "g")
176   .attr("class", "link")
177   .attr("d", function(d) {
178     var o = {x: source.x0, y: source.y0};
179     return diagonal({source: o, target: o});
180   });
181   
182   // Transition links to their new position.
183   link.transition()
184       .duration(duration)
185       .attr("d", diagonal)
186       .attr("class", function(d){
187           if(d.source.deal != null && d.source.deal != undefined){
188             if(d.target.deal != null && d.target.deal != undefined){
189                 return "link link2";
190             }
191         }
192         return "link";
193       });
194 
195   // Transition exiting nodes to the parent's new position.
196   link.exit().transition()
197       .duration(duration)
198       .attr("d", function(d) {
199         var o = {x: source.x, y: source.y};
200         return diagonal({source: o, target: o});
201       })
202       .remove();
203 
204   // Stash the old positions for transition.
205   nodes.forEach(function(d) {
206     d.x0 = d.x;
207     d.y0 = d.y;
208   });
209 }
210 
211 
212 function getNode(){
213     var mynodes = null;
214     $.ajax({  
215         url : "./node",  
216         async : false, // 注意此处需要同步,因为返回完数据后,下面才能让结果的第一条selected  
217         type : "POST",  
218         dataType : "json",  
219         success : function(data) {            
220             mynodes = data;
221             console.log(mynodes);            
222             //nodes = JSON.parse(nodes);
223         }  
224     });  
225     return mynodes;
226 }
227 
228 // Toggle children on click.
229 function click(d) {
230       if (d.children) {
231         d._children = d.children;
232         d.children = null;
233       } else if(d._children){         
234         d.children = d._children;
235         d._children = null;
236       }else {          
237          var mnodes = getNode();
238         d.children = mnodes.children;        
239       }
240   update(d);
241 }
242 
243 </script>

整个前端的代码,重点看其中红色标识的部分,这些部分是和这个博文的内容直接相关的。 涉及到连接的红色标识。 数据中定义了deal字段,这个字段就是标识某个节点具有这个特性,只有这个特性的节点之间的边用红色标识。 另外,点击按钮,异步加载后端服务器的代码部分,也有部分数据是含有这个deal特性的,同样适用于本故事的要求。

 

在代码的186行中,link的transition(即变化,变换)过程中,去渲染节点之间的边的样式。其实,还可以在其他地方做这个样式的加载,比如在link的enter部分实现,只是这个过程,有点违背D3架构设计之enter,update和exit的大前提,不建议在enter里面实现这个功能。

 

后端的代码,只是一个示例代码,和前面D3博文的基本相同:

 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 "d3demo2";
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 }

上述代码,表明只有节点名为one的节点,给其配置deal属性值,也就是说在最终D3绘制的树状图上,名为one的节点间会出现红色link。

 

最终效果图如下,首先看默认显示的情况

 

点击显示one节点之间的状态