D3做爲前端圖形顯示的利器,功能之強,對底層技術細節要求相對比較多。html
有一點,就是要理解其基本的數據和節點的匹配規則架構,即enter,update和exit原理,我前面的D3基礎篇中有介紹過,不明白的能夠再去研究下。前端
本篇博文,一樣是在這個框架下,完成修改樹狀圖中某兩個節點之間的邊用紅色線條鏈接,實現表達特殊含義的目的。java
背景故事: 微信朋友圈之間產品帖子相互轉發,有些帖子轉發後會有成交,只要有成交,則這個促成成交的節點及其之上的父節點都相應是有功勞的,這個軌跡須要用高亮的顏色表示(好比本例中,用紅色表示)。node
其實也比較簡單,直接看代碼, 前端部分:jquery
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特性的,一樣適用於本故事的要求。web
在代碼的186行中,link的transition(即變化,變換)過程當中,去渲染節點之間的邊的樣式。其實,還能夠在其餘地方作這個樣式的加載,好比在link的enter部分實現,只是這個過程,有點違背D3架構設計之enter,update和exit的大前提,不建議在enter裏面實現這個功能。ajax
後端的代碼,只是一個示例代碼,和前面D3博文的基本相同:spring
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。json
最終效果圖以下,首先看默認顯示的狀況後端
點擊顯示one節點之間的狀態